diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a4bf9ef0e92..79842dffd10 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -137,7 +137,7 @@ android:exported="false"> - + - + diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/BaseDaoWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/BaseDaoWidgetRepository.kt new file mode 100644 index 00000000000..2c27fc5e742 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/BaseDaoWidgetRepository.kt @@ -0,0 +1,25 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.WidgetEntity +import kotlinx.coroutines.flow.Flow + +interface BaseDaoWidgetRepository { + + fun get(id: Int): T? + + fun exist(appWidgetId: Int): Boolean { + return get(appWidgetId) != null + } + + suspend fun add(entity: T) + + suspend fun delete(id: Int) + + suspend fun deleteAll(ids: IntArray) + + suspend fun getAll(): List + + suspend fun getAllFlow(): Flow> + + suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepository.kt new file mode 100644 index 00000000000..e271eee6148 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepository.kt @@ -0,0 +1,5 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.ButtonWidgetEntity + +interface ButtonWidgetRepository : BaseDaoWidgetRepository diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepositoryImpl.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepositoryImpl.kt new file mode 100644 index 00000000000..5fe937ddfe1 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/ButtonWidgetRepositoryImpl.kt @@ -0,0 +1,29 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.ButtonWidgetDao +import io.homeassistant.companion.android.database.widget.ButtonWidgetEntity +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class ButtonWidgetRepositoryImpl @Inject constructor( + private val dao: ButtonWidgetDao +) : ButtonWidgetRepository { + + override fun get(id: Int): ButtonWidgetEntity? { + return dao.get(id) + } + + override suspend fun add(entity: ButtonWidgetEntity) = dao.add(entity) + + override suspend fun delete(id: Int) = dao.delete(id) + + override suspend fun deleteAll(ids: IntArray) = dao.deleteAll(ids) + + override suspend fun getAll(): List = dao.getAll() + + override suspend fun getAllFlow(): Flow> = dao.getAllFlow().flowOn(Dispatchers.IO) + + override suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) = error("Not Implemented") +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepository.kt new file mode 100644 index 00000000000..2636cbe12bd --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepository.kt @@ -0,0 +1,5 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.CameraWidgetEntity + +interface CameraWidgetRepository : BaseDaoWidgetRepository diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepositoryImpl.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepositoryImpl.kt new file mode 100644 index 00000000000..6b66772ebde --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/CameraWidgetRepositoryImpl.kt @@ -0,0 +1,25 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.CameraWidgetDao +import io.homeassistant.companion.android.database.widget.CameraWidgetEntity +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +class CameraWidgetRepositoryImpl @Inject constructor( + private val dao: CameraWidgetDao +) : CameraWidgetRepository { + + override suspend fun add(entity: CameraWidgetEntity) = dao.add(entity) + + override suspend fun delete(id: Int) = dao.delete(id) + + override suspend fun deleteAll(ids: IntArray) = dao.deleteAll(ids) + + override suspend fun getAll(): List = dao.getAll() + + override suspend fun getAllFlow(): Flow> = dao.getAllFlow() + + override suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) = error("Not Implemented") + + override fun get(id: Int): CameraWidgetEntity? = dao.get(id) +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepository.kt new file mode 100644 index 00000000000..55780c70917 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepository.kt @@ -0,0 +1,6 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetEntity + +interface MediaPlayerControlsWidgetRepository : + BaseDaoWidgetRepository diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepositoryImpl.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepositoryImpl.kt new file mode 100644 index 00000000000..7c091e2516d --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/MediaPlayerControlsWidgetRepositoryImpl.kt @@ -0,0 +1,27 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetDao +import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetEntity +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class MediaPlayerControlsWidgetRepositoryImpl @Inject constructor( + private val dao: MediaPlayerControlsWidgetDao +) : MediaPlayerControlsWidgetRepository { + + override fun get(id: Int): MediaPlayerControlsWidgetEntity? = dao.get(id) + + override suspend fun add(entity: MediaPlayerControlsWidgetEntity) = dao.add(entity) + + override suspend fun delete(id: Int) = dao.delete(id) + + override suspend fun deleteAll(ids: IntArray) = dao.deleteAll(ids) + + override suspend fun getAll(): List = dao.getAll() + + override suspend fun getAllFlow(): Flow> = dao.getAllFlow().flowOn(Dispatchers.IO) + + override suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) = error("Not Implemented") +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/RepositoryModule.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/RepositoryModule.kt new file mode 100644 index 00000000000..3ccb4af1a90 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/RepositoryModule.kt @@ -0,0 +1,36 @@ +package io.homeassistant.companion.android.repositories + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + @Binds + abstract fun bindMediaPlayerWidgetRepository( + impl: MediaPlayerControlsWidgetRepositoryImpl + ): MediaPlayerControlsWidgetRepository + + @Binds + abstract fun bindTemplateWidgetRepository( + impl: TemplateWidgetRepositoryImpl + ): TemplateWidgetRepository + + @Binds + abstract fun bindEntityWidgetRepository( + impl: StaticWidgetRepositoryImpl + ): StaticWidgetRepository + + @Binds + abstract fun bindCameraWidgetRepository( + impl: CameraWidgetRepositoryImpl + ): CameraWidgetRepository + + @Binds + abstract fun bindButtonWidgetRepository( + impl: ButtonWidgetRepositoryImpl + ): ButtonWidgetRepository +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepository.kt new file mode 100644 index 00000000000..d5890213fba --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepository.kt @@ -0,0 +1,5 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.StaticWidgetEntity + +interface StaticWidgetRepository : BaseDaoWidgetRepository diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepositoryImpl.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepositoryImpl.kt new file mode 100644 index 00000000000..1391255a425 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/StaticWidgetRepositoryImpl.kt @@ -0,0 +1,27 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.StaticWidgetDao +import io.homeassistant.companion.android.database.widget.StaticWidgetEntity +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class StaticWidgetRepositoryImpl @Inject constructor( + private val dao: StaticWidgetDao +) : StaticWidgetRepository { + + override fun get(id: Int): StaticWidgetEntity? = dao.get(id) + + override suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) = dao.updateWidgetLastUpdate(widgetId, lastUpdate) + + override suspend fun add(entity: StaticWidgetEntity) = dao.add(entity) + + override suspend fun delete(id: Int) = dao.delete(id) + + override suspend fun deleteAll(ids: IntArray) = dao.deleteAll(ids) + + override suspend fun getAll(): List = dao.getAll() + + override suspend fun getAllFlow(): Flow> = dao.getAllFlow().flowOn(Dispatchers.IO) +} diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepository.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepository.kt new file mode 100644 index 00000000000..e5ec45fed80 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepository.kt @@ -0,0 +1,5 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity + +interface TemplateWidgetRepository : BaseDaoWidgetRepository diff --git a/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepositoryImpl.kt b/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepositoryImpl.kt new file mode 100644 index 00000000000..fd59cd5ed68 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/repositories/TemplateWidgetRepositoryImpl.kt @@ -0,0 +1,27 @@ +package io.homeassistant.companion.android.repositories + +import io.homeassistant.companion.android.database.widget.TemplateWidgetDao +import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class TemplateWidgetRepositoryImpl @Inject constructor( + private val dao: TemplateWidgetDao +) : TemplateWidgetRepository { + + override fun get(id: Int): TemplateWidgetEntity? = dao.get(id) + + override suspend fun delete(id: Int) = dao.delete(id) + + override suspend fun deleteAll(ids: IntArray) = dao.deleteAll(ids) + + override suspend fun getAll(): List = dao.getAll() + + override suspend fun getAllFlow(): Flow> = dao.getAllFlow().flowOn(Dispatchers.IO) + + override suspend fun updateWidgetLastUpdate(widgetId: Int, lastUpdate: String) = dao.updateTemplateWidgetLastUpdate(widgetId, lastUpdate) + + override suspend fun add(entity: TemplateWidgetEntity) = dao.add(entity) +} diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetConfigureActivity.kt index e232ffd653f..8fafa995306 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetConfigureActivity.kt @@ -10,17 +10,18 @@ import android.widget.Toast import io.homeassistant.companion.android.BaseActivity import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.data.servers.ServerManager -import io.homeassistant.companion.android.database.widget.WidgetDao +import io.homeassistant.companion.android.repositories.BaseDaoWidgetRepository import javax.inject.Inject -abstract class BaseWidgetConfigureActivity : BaseActivity() { +abstract class BaseWidgetConfigureActivity> : BaseActivity() { @Inject lateinit var serverManager: ServerManager protected var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID - abstract val dao: WidgetDao + @Inject + lateinit var repository: T abstract val serverSelect: View abstract val serverSelectList: Spinner diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetProvider.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetProvider.kt index 275ec96eda9..bc246ed7ad2 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetProvider.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/BaseWidgetProvider.kt @@ -6,32 +6,40 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.graphics.Color import android.os.Bundle import android.util.Log import android.widget.RemoteViews import androidx.core.content.ContextCompat -import io.homeassistant.companion.android.common.data.integration.Entity +import io.homeassistant.companion.android.R import io.homeassistant.companion.android.common.data.servers.ServerManager +import io.homeassistant.companion.android.database.widget.ThemeableWidgetEntity +import io.homeassistant.companion.android.database.widget.WidgetBackgroundType +import io.homeassistant.companion.android.repositories.BaseDaoWidgetRepository +import io.homeassistant.companion.android.util.hasActiveConnection import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.isActive import kotlinx.coroutines.launch /** * A widget provider class for widgets that update based on entity state changes. */ -abstract class BaseWidgetProvider : AppWidgetProvider() { +abstract class BaseWidgetProvider, WidgetDataType> : AppWidgetProvider() { companion object { const val UPDATE_VIEW = - "io.homeassistant.companion.android.widgets.template.BaseWidgetProvider.UPDATE_VIEW" + "io.homeassistant.companion.android.widgets.UPDATE_VIEW" const val RECEIVE_DATA = - "io.homeassistant.companion.android.widgets.template.TemplateWidget.RECEIVE_DATA" + "io.homeassistant.companion.android.widgets.RECEIVE_DATA" var widgetScope: CoroutineScope? = null + var widgetWorkScope: CoroutineScope? = null val widgetEntities = mutableMapOf>() val widgetJobs = mutableMapOf() } @@ -39,7 +47,10 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { @Inject lateinit var serverManager: ServerManager - private var thisSetScope = false + @Inject + lateinit var repository: T + + protected var thisSetScope = false protected var lastIntent = "" init { @@ -49,6 +60,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { private fun setupWidgetScope() { if (widgetScope == null || !widgetScope!!.isActive) { widgetScope = CoroutineScope(Dispatchers.Main + Job()) + widgetWorkScope = CoroutineScope(Dispatchers.IO + Job()) thisSetScope = true } } @@ -61,8 +73,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { // There may be multiple widgets active, so update all of them for (appWidgetId in appWidgetIds) { widgetScope?.launch { - val views = getWidgetRemoteViews(context, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, views) + forceUpdateView(context, appWidgetId, appWidgetManager) } } } @@ -70,10 +81,11 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context, intent: Intent) { lastIntent = intent.action.toString() val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + Log.d(getWidgetProvider(context).shortClassName, "Broadcast received: " + System.lineSeparator() + "Broadcast action: " + lastIntent + System.lineSeparator() + "AppWidgetId: " + appWidgetId) super.onReceive(context, intent) when (lastIntent) { - UPDATE_VIEW -> updateView(context, appWidgetId) + UPDATE_VIEW -> forceUpdateView(context, appWidgetId) RECEIVE_DATA -> { saveEntityConfiguration( context, @@ -87,13 +99,13 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { } } - fun onScreenOn(context: Context) { + private fun onScreenOn(context: Context) { setupWidgetScope() if (!serverManager.isRegistered()) return widgetScope!!.launch { updateAllWidgets(context) - val allWidgets = getAllWidgetIdsWithEntities(context) + val allWidgets = getAllWidgetIdsWithEntities() val widgetsWithDifferentEntities = allWidgets.filter { it.value.second != widgetEntities[it.key] } if (widgetsWithDifferentEntities.isNotEmpty()) { ContextCompat.registerReceiver( @@ -109,7 +121,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { val (serverId, entities) = pair.first to pair.second val entityUpdates = if (serverManager.getServer(serverId) != null) { - serverManager.integrationRepository(serverId).getEntityUpdates(entities) + getUpdates(serverId, entities) } else { null } @@ -131,6 +143,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { private fun onScreenOff() { if (thisSetScope) { + widgetWorkScope?.cancel() widgetScope?.cancel() thisSetScope = false widgetEntities.clear() @@ -145,7 +158,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { val systemWidgetIds = AppWidgetManager.getInstance(context) .getAppWidgetIds(widgetProvider) .toSet() - val dbWidgetIds = getAllWidgetIdsWithEntities(context).keys + val dbWidgetIds = getAllWidgetIdsWithEntities().keys val invalidWidgetIds = dbWidgetIds.minus(systemWidgetIds) if (invalidWidgetIds.isNotEmpty()) { @@ -157,18 +170,21 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { } dbWidgetIds.filter { systemWidgetIds.contains(it) }.forEach { - updateView(context, it) + forceUpdateView(context, it) } } - private fun updateView( + fun forceUpdateView( context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context) ) { widgetScope?.launch { - val views = getWidgetRemoteViews(context, appWidgetId) + val hasActiveConnection = context.hasActiveConnection() + val views = getWidgetRemoteViews(context, appWidgetId, hasActiveConnection) + Log.d(getWidgetProvider(context).shortClassName, "Updating Widget View updateAppWidget() hasActiveConnection: $hasActiveConnection") appWidgetManager.updateAppWidget(appWidgetId, views) + onWidgetsViewUpdated(context, appWidgetId, appWidgetManager, views, hasActiveConnection) } } @@ -178,11 +194,50 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { widgetJobs.remove(appWidgetId) } + private suspend fun getAllWidgetIdsWithEntities(): Map>> = + repository.getAllFlow() + .first() + .associate { + it.id to (it.serverId to listOf(it.entityId.orEmpty())) + } + + fun setWidgetBackground(views: RemoteViews, layoutId: Int, widget: ThemeableWidgetEntity?) { + when (widget?.backgroundType) { + WidgetBackgroundType.TRANSPARENT -> { + views.setInt(layoutId, "setBackgroundColor", Color.TRANSPARENT) + } + + else -> { + views.setInt(layoutId, "setBackgroundResource", R.drawable.widget_button_background) + } + } + } + + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + widgetScope?.launch { + repository.deleteAll(appWidgetIds) + appWidgetIds.forEach { removeSubscription(it) } + } + } + + open fun onWidgetsViewUpdated(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager, remoteViews: RemoteViews, hasActiveConnection: Boolean) { + Log.d( + getWidgetProvider(context).shortClassName, + "onWidgetsViewUpdated() received, AppWidgetId: $appWidgetId hasActiveConnection: $hasActiveConnection" + ) + } + + open suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: WidgetDataType) { + Log.d( + getWidgetProvider(context).shortClassName, + "onEntityStateChanged(), AppWidgetId: $appWidgetId" + ) + } + abstract fun getWidgetProvider(context: Context): ComponentName - abstract suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity>? = null): RemoteViews + abstract suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, hasActiveConnection: Boolean, suggestedEntity: WidgetDataType? = null): RemoteViews + abstract suspend fun getUpdates(serverId: Int, entityIds: List): Flow? // A map of widget IDs to [server ID, list of entity IDs] - abstract suspend fun getAllWidgetIdsWithEntities(context: Context): Map>> abstract fun saveEntityConfiguration(context: Context, extras: Bundle?, appWidgetId: Int) - abstract suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: Entity<*>) } diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidget.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidget.kt index 64d4b9a76a0..1bf1cfa16dc 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidget.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidget.kt @@ -383,7 +383,7 @@ class ButtonWidget : AppWidgetProvider() { "label: " + label ) - val widget = ButtonWidgetEntity(appWidgetId, serverId, icon, domain, action, actionData, label, backgroundType, textColor, requireAuthentication) + val widget = ButtonWidgetEntity(appWidgetId, serverId, null, icon, domain, action, actionData, label, backgroundType, textColor, requireAuthentication) buttonWidgetDao.add(widget) // It is the responsibility of the configuration activity to update the app widget diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidgetConfigureActivity.kt index 943983c12c9..20b602d3338 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/button/ButtonWidgetConfigureActivity.kt @@ -36,33 +36,29 @@ import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.data.integration.Action import io.homeassistant.companion.android.common.data.integration.Entity -import io.homeassistant.companion.android.database.widget.ButtonWidgetDao import io.homeassistant.companion.android.database.widget.WidgetBackgroundType import io.homeassistant.companion.android.databinding.WidgetButtonConfigureBinding +import io.homeassistant.companion.android.repositories.ButtonWidgetRepository import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel import io.homeassistant.companion.android.util.getHexForColor import io.homeassistant.companion.android.util.icondialog.IconDialogFragment import io.homeassistant.companion.android.util.icondialog.getIconByMdiName import io.homeassistant.companion.android.util.icondialog.mdiName import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity +import io.homeassistant.companion.android.widgets.BaseWidgetProvider.Companion.RECEIVE_DATA import io.homeassistant.companion.android.widgets.common.ActionFieldBinder import io.homeassistant.companion.android.widgets.common.SingleItemArrayAdapter import io.homeassistant.companion.android.widgets.common.WidgetDynamicFieldAdapter import io.homeassistant.companion.android.widgets.common.WidgetUtils -import javax.inject.Inject import kotlinx.coroutines.launch @AndroidEntryPoint -class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity() { +class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity() { companion object { private const val TAG: String = "ButtonWidgetConfigAct" private const val PIN_WIDGET_CALLBACK = "io.homeassistant.companion.android.widgets.button.ButtonWidgetConfigureActivity.PIN_WIDGET_CALLBACK" } - @Inject - lateinit var buttonWidgetDao: ButtonWidgetDao - override val dao get() = buttonWidgetDao - private var actions = mutableMapOf>() private var entities = mutableMapOf>>() private var dynamicFields = ArrayList() @@ -139,7 +135,7 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity() { val existingActionData = mutableMapOf() val addedFields = mutableListOf() - buttonWidgetDao.get(appWidgetId)?.let { buttonWidget -> + repository.get(appWidgetId)?.let { buttonWidget -> if ( buttonWidget.serverId != selectedServerId || "${buttonWidget.domain}.${buttonWidget.service}" != actionText @@ -224,7 +220,7 @@ class ButtonWidgetConfigureActivity : BaseWidgetConfigureActivity() { return } - val buttonWidget = buttonWidgetDao.get(appWidgetId) + val buttonWidget = repository.get(appWidgetId) val backgroundTypeValues = WidgetUtils.getBackgroundOptionList(this) binding.backgroundType.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, backgroundTypeValues) diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/camera/CameraWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/camera/CameraWidgetConfigureActivity.kt index ecd7bd13497..2b4f42bf9a1 100755 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/camera/CameraWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/camera/CameraWidgetConfigureActivity.kt @@ -20,18 +20,18 @@ import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.domain -import io.homeassistant.companion.android.database.widget.CameraWidgetDao import io.homeassistant.companion.android.database.widget.WidgetTapAction import io.homeassistant.companion.android.databinding.WidgetCameraConfigureBinding +import io.homeassistant.companion.android.repositories.CameraWidgetRepository import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity +import io.homeassistant.companion.android.widgets.BaseWidgetProvider.Companion.RECEIVE_DATA import io.homeassistant.companion.android.widgets.common.SingleItemArrayAdapter -import javax.inject.Inject import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @AndroidEntryPoint -class CameraWidgetConfigureActivity : BaseWidgetConfigureActivity() { +class CameraWidgetConfigureActivity : BaseWidgetConfigureActivity() { companion object { private const val TAG: String = "CameraWidgetConfigAct" @@ -51,10 +51,6 @@ class CameraWidgetConfigureActivity : BaseWidgetConfigureActivity() { private var entities = mutableMapOf>>() private var selectedEntity: Entity? = null - @Inject - lateinit var cameraWidgetDao: CameraWidgetDao - override val dao get() = cameraWidgetDao - private var entityAdapter: SingleItemArrayAdapter>? = null public override fun onCreate(savedInstanceState: Bundle?) { @@ -109,7 +105,7 @@ class CameraWidgetConfigureActivity : BaseWidgetConfigureActivity() { } initTapActionsSpinner() - val cameraWidget = cameraWidgetDao.get(appWidgetId) + val cameraWidget = repository.get(appWidgetId) if (cameraWidget != null) { setCurrentTapAction(tapAction = cameraWidget.tapAction) binding.widgetTextConfigEntityId.setText(cameraWidget.entityId) diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidget.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidget.kt index f027af2c9d4..fcd653e9cae 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidget.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidget.kt @@ -5,7 +5,6 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.Intent -import android.graphics.Color import android.os.Bundle import android.util.Log import android.util.TypedValue @@ -23,17 +22,17 @@ import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.canSupportPrecision import io.homeassistant.companion.android.common.data.integration.friendlyState import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState -import io.homeassistant.companion.android.database.widget.StaticWidgetDao import io.homeassistant.companion.android.database.widget.StaticWidgetEntity import io.homeassistant.companion.android.database.widget.WidgetBackgroundType import io.homeassistant.companion.android.database.widget.WidgetTapAction +import io.homeassistant.companion.android.repositories.StaticWidgetRepository import io.homeassistant.companion.android.util.getAttribute import io.homeassistant.companion.android.widgets.BaseWidgetProvider -import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @AndroidEntryPoint -class EntityWidget : BaseWidgetProvider() { +class EntityWidget : BaseWidgetProvider>?>() { companion object { private const val TAG = "StaticWidget" @@ -54,14 +53,11 @@ class EntityWidget : BaseWidgetProvider() { private data class ResolvedText(val text: CharSequence?, val exception: Boolean = false) } - @Inject - lateinit var staticWidgetDao: StaticWidgetDao - override fun getWidgetProvider(context: Context): ComponentName = ComponentName(context, EntityWidget::class.java) - override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity>?): RemoteViews { - val widget = staticWidgetDao.get(appWidgetId) + override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, hasActiveConnection: Boolean, suggestedEntity: Entity>?): RemoteViews { + val widget = repository.get(appWidgetId) val intent = Intent(context, EntityWidget::class.java).apply { action = if (widget?.tapAction == WidgetTapAction.TOGGLE) TOGGLE_ENTITY else UPDATE_VIEW @@ -84,11 +80,12 @@ class EntityWidget : BaseWidgetProvider() { var textColor = context.getAttribute(R.attr.colorWidgetOnBackground, ContextCompat.getColor(context, commonR.color.colorWidgetButtonLabel)) widget.textColor?.let { textColor = it.toColorInt() } - setInt(R.id.widgetLayout, "setBackgroundColor", Color.TRANSPARENT) setTextColor(R.id.widgetText, textColor) setTextColor(R.id.widgetLabel, textColor) } + setWidgetBackground(this, R.id.widgetLayout, widget) + // Content setViewVisibility( R.id.widgetTextLayout, @@ -143,9 +140,6 @@ class EntityWidget : BaseWidgetProvider() { return views } - override suspend fun getAllWidgetIdsWithEntities(context: Context): Map>> = - staticWidgetDao.getAll().associate { it.id to (it.serverId to listOf(it.entityId)) } - private suspend fun resolveTextToShow( context: Context, serverId: Int, @@ -177,11 +171,11 @@ class EntityWidget : BaseWidgetProvider() { null } if (attributeIds == null) { - staticWidgetDao.updateWidgetLastUpdate( + repository.updateWidgetLastUpdate( appWidgetId, - entity?.friendlyState(context, entityOptions) ?: staticWidgetDao.get(appWidgetId)?.lastUpdate ?: "" + entity?.friendlyState(context, entityOptions) ?: repository.get(appWidgetId)?.lastUpdate ?: "" ) - return ResolvedText(staticWidgetDao.get(appWidgetId)?.lastUpdate, entityCaughtException) + return ResolvedText(repository.get(appWidgetId)?.lastUpdate, entityCaughtException) } try { @@ -191,12 +185,12 @@ class EntityWidget : BaseWidgetProvider() { val lastUpdate = entity?.friendlyState(context, entityOptions).plus(if (attributeValues.isNotEmpty()) stateSeparator else "") .plus(attributeValues.joinToString(attributeSeparator)) - staticWidgetDao.updateWidgetLastUpdate(appWidgetId, lastUpdate) + repository.updateWidgetLastUpdate(appWidgetId, lastUpdate) return ResolvedText(lastUpdate) } catch (e: Exception) { Log.e(TAG, "Unable to fetch entity state and attributes", e) } - return ResolvedText(staticWidgetDao.get(appWidgetId)?.lastUpdate, true) + return ResolvedText(repository.get(appWidgetId)?.lastUpdate, true) } override fun saveEntityConfiguration(context: Context, extras: Bundle?, appWidgetId: Int) { @@ -227,7 +221,7 @@ class EntityWidget : BaseWidgetProvider() { "entity id: " + entitySelection + System.lineSeparator() + "attribute: " + (attributeSelection ?: "N/A") ) - staticWidgetDao.add( + repository.add( StaticWidgetEntity( appWidgetId, serverId, @@ -238,20 +232,19 @@ class EntityWidget : BaseWidgetProvider() { stateSeparatorSelection ?: "", attributeSeparatorSelection ?: "", tapActionSelection, - staticWidgetDao.get(appWidgetId)?.lastUpdate ?: "", + repository.get(appWidgetId)?.lastUpdate ?: "", backgroundTypeSelection, textColorSelection ) ) - onUpdate(context, AppWidgetManager.getInstance(context), intArrayOf(appWidgetId)) + forceUpdateView(context, appWidgetId) } } - override suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: Entity<*>) { + override suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: Entity>?) { widgetScope?.launch { - val views = getWidgetRemoteViews(context, appWidgetId, entity as Entity>) - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views) + forceUpdateView(context, appWidgetId) } } @@ -265,7 +258,7 @@ class EntityWidget : BaseWidgetProvider() { appWidgetManager.partiallyUpdateAppWidget(appWidgetId, loadingViews) var success = false - staticWidgetDao.get(appWidgetId)?.let { + repository.get(appWidgetId)?.let { try { onEntityPressedWithoutState( it.entityId, @@ -280,8 +273,7 @@ class EntityWidget : BaseWidgetProvider() { if (!success) { Toast.makeText(context, commonR.string.action_failure, Toast.LENGTH_LONG).show() - val views = getWidgetRemoteViews(context, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, views) + forceUpdateView(context, appWidgetId) } // else update will be triggered by websocket subscription } } @@ -294,10 +286,5 @@ class EntityWidget : BaseWidgetProvider() { } } - override fun onDeleted(context: Context, appWidgetIds: IntArray) { - widgetScope?.launch { - staticWidgetDao.deleteAll(appWidgetIds) - appWidgetIds.forEach { removeSubscription(it) } - } - } + override suspend fun getUpdates(serverId: Int, entityIds: List): Flow>> = serverManager.integrationRepository(serverId).getEntityUpdates(entityIds) as Flow>> } diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidgetConfigureActivity.kt index 32e30e37450..e065352c848 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/entity/EntityWidgetConfigureActivity.kt @@ -28,32 +28,27 @@ import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.EntityExt import io.homeassistant.companion.android.common.data.integration.domain import io.homeassistant.companion.android.common.data.integration.friendlyName -import io.homeassistant.companion.android.database.widget.StaticWidgetDao import io.homeassistant.companion.android.database.widget.WidgetBackgroundType import io.homeassistant.companion.android.database.widget.WidgetTapAction import io.homeassistant.companion.android.databinding.WidgetStaticConfigureBinding +import io.homeassistant.companion.android.repositories.StaticWidgetRepository import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel import io.homeassistant.companion.android.util.getHexForColor import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity import io.homeassistant.companion.android.widgets.BaseWidgetProvider import io.homeassistant.companion.android.widgets.common.SingleItemArrayAdapter import io.homeassistant.companion.android.widgets.common.WidgetUtils -import javax.inject.Inject import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @AndroidEntryPoint -class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() { +class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() { companion object { private const val TAG: String = "StaticWidgetConfigAct" private const val PIN_WIDGET_CALLBACK = "io.homeassistant.companion.android.widgets.entity.EntityWidgetConfigureActivity.PIN_WIDGET_CALLBACK" } - @Inject - lateinit var staticWidgetDao: StaticWidgetDao - override val dao get() = staticWidgetDao - private var entities = mutableMapOf>>() private var selectedEntity: Entity? = null @@ -127,7 +122,7 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() { return } - val staticWidget = staticWidgetDao.get(appWidgetId) + val staticWidget = repository.get(appWidgetId) val tapActionValues = listOf(getString(commonR.string.widget_tap_action_toggle), getString(commonR.string.refresh)) binding.tapActionList.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, tapActionValues) diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidget.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidget.kt index 205e5f62fb9..7b17cff3b18 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidget.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidget.kt @@ -5,7 +5,6 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.Intent -import android.graphics.Color import android.os.Bundle import android.os.Handler import android.os.Looper @@ -23,23 +22,20 @@ import io.homeassistant.companion.android.BuildConfig import io.homeassistant.companion.android.R import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.data.integration.Entity -import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetDao import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetEntity import io.homeassistant.companion.android.database.widget.WidgetBackgroundType -import io.homeassistant.companion.android.util.hasActiveConnection +import io.homeassistant.companion.android.repositories.MediaPlayerControlsWidgetRepository import io.homeassistant.companion.android.widgets.BaseWidgetProvider import java.util.LinkedList -import javax.inject.Inject import kotlin.collections.HashMap +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @AndroidEntryPoint -class MediaPlayerControlsWidget : BaseWidgetProvider() { +class MediaPlayerControlsWidget : BaseWidgetProvider>>() { companion object { private const val TAG = "MediaPlayCtrlsWidget" - internal const val RECEIVE_DATA = - "io.homeassistant.companion.android.widgets.media_player_controls.MediaPlayerControlsWidget.RECEIVE_DATA" internal const val UPDATE_MEDIA_IMAGE = "io.homeassistant.companion.android.widgets.media_player_controls.MediaPlayerControlsWidget.UPDATE_MEDIA_IMAGE" internal const val CALL_PREV_TRACK = @@ -67,44 +63,10 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE" } - @Inject - lateinit var mediaPlayCtrlWidgetDao: MediaPlayerControlsWidgetDao - override fun getWidgetProvider(context: Context): ComponentName = ComponentName(context, MediaPlayerControlsWidget::class.java) - override fun onUpdate( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetIds: IntArray - ) { - // There may be multiple widgets active, so update all of them - appWidgetIds.forEach { appWidgetId -> - updateView( - context, - appWidgetId, - appWidgetManager - ) - } - } - - private fun updateView( - context: Context, - appWidgetId: Int, - appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context) - ) { - if (!context.hasActiveConnection()) { - Log.d(TAG, "Skipping widget update since network connection is not active") - return - } - widgetScope?.launch { - val views = getWidgetRemoteViews(context, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, views) - onScreenOn(context) - } - } - - override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity>?): RemoteViews { + override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, hasActiveConnection: Boolean, suggestedEntity: Entity>?): RemoteViews { val updateMediaIntent = Intent(context, MediaPlayerControlsWidget::class.java).apply { action = UPDATE_MEDIA_IMAGE putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) @@ -145,7 +107,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) } - val widget = mediaPlayCtrlWidgetDao.get(appWidgetId) + val widget = repository.get(appWidgetId) val useDynamicColors = widget?.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable() return RemoteViews(context.packageName, if (useDynamicColors) R.layout.widget_media_controls_wrapper_dynamiccolor else R.layout.widget_media_controls_wrapper_default).apply { if (widget != null) { @@ -176,9 +138,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { val album = entity?.attributes?.get("media_album_name")?.toString() val icon = entity?.attributes?.get("icon")?.toString() - if (widget.backgroundType == WidgetBackgroundType.TRANSPARENT) { - setInt(R.id.widgetLayout, "setBackgroundColor", Color.TRANSPARENT) - } + setWidgetBackground(this, R.id.widgetLayout, widget) if ((artist != null || album != null) && title != null) { setTextViewText( @@ -408,9 +368,6 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { } } - override suspend fun getAllWidgetIdsWithEntities(context: Context): Map>> = - mediaPlayCtrlWidgetDao.getAll().associate { it.id to (it.serverId to it.entityId.split(",")) } - private suspend fun getEntity(context: Context, serverId: Int, entityIds: List, suggestedEntity: Entity>?): Entity>? { val entity: Entity>? try { @@ -437,27 +394,10 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { } override fun onReceive(context: Context, intent: Intent) { - lastIntent = intent.action.toString() val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) - - Log.d( - TAG, - "Broadcast received: " + System.lineSeparator() + - "Broadcast action: " + lastIntent + System.lineSeparator() + - "AppWidgetId: " + appWidgetId - ) - super.onReceive(context, intent) when (lastIntent) { - UPDATE_VIEW -> updateView( - context, - appWidgetId - ) - RECEIVE_DATA -> { - saveEntityConfiguration(context, intent.extras, appWidgetId) - super.onScreenOn(context) - } - UPDATE_MEDIA_IMAGE -> updateView(context, appWidgetId) + UPDATE_MEDIA_IMAGE -> forceUpdateView(context, appWidgetId) CALL_PREV_TRACK -> callPreviousTrackAction(context, appWidgetId) CALL_REWIND -> callRewindAction(context, appWidgetId) CALL_PLAYPAUSE -> callPlayPauseAction(context, appWidgetId) @@ -492,7 +432,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { "Saving action call config data:" + System.lineSeparator() + "entity id: " + entitySelection + System.lineSeparator() ) - mediaPlayCtrlWidgetDao.add( + repository.add( MediaPlayerControlsWidgetEntity( appWidgetId, serverId, @@ -506,15 +446,15 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { ) ) - onUpdate(context, AppWidgetManager.getInstance(context), intArrayOf(appWidgetId)) + forceUpdateView(context, appWidgetId) } } - override suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: Entity<*>) { - mediaPlayCtrlWidgetDao.get(appWidgetId)?.let { + override suspend fun onEntityStateChanged(context: Context, appWidgetId: Int, entity: Entity>) { + super.onEntityStateChanged(context, appWidgetId, entity) + repository.get(appWidgetId)?.let { widgetScope?.launch { - val views = getWidgetRemoteViews(context, appWidgetId, getEntity(context, it.serverId, it.entityId.split(","), null)) - AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views) + forceUpdateView(context, appWidgetId) } } } @@ -522,7 +462,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callPreviousTrackAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -548,7 +488,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callRewindAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -602,7 +542,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callPlayPauseAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -632,7 +572,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callFastForwardAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -686,7 +626,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callNextTrackAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -716,7 +656,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callVolumeDownAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -746,7 +686,7 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { private fun callVolumeUpAction(context: Context, appWidgetId: Int) { widgetScope?.launch { Log.d(TAG, "Retrieving media player entity for app widget $appWidgetId") - val entity: MediaPlayerControlsWidgetEntity? = mediaPlayCtrlWidgetDao.get(appWidgetId) + val entity: MediaPlayerControlsWidgetEntity? = repository.get(appWidgetId) if (entity == null) { Log.d(TAG, "Failed to retrieve media player entity") @@ -773,10 +713,5 @@ class MediaPlayerControlsWidget : BaseWidgetProvider() { } } - override fun onDeleted(context: Context, appWidgetIds: IntArray) { - widgetScope?.launch { - mediaPlayCtrlWidgetDao.deleteAll(appWidgetIds) - appWidgetIds.forEach { removeSubscription(it) } - } - } + override suspend fun getUpdates(serverId: Int, entityIds: List): Flow>> = serverManager.integrationRepository(serverId).getEntityUpdates(entityIds) as Flow>> } diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt index fafa56fb5c5..c86d1c24566 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/mediaplayer/MediaPlayerControlsWidgetConfigureActivity.kt @@ -19,20 +19,20 @@ import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.domain -import io.homeassistant.companion.android.database.widget.MediaPlayerControlsWidgetDao import io.homeassistant.companion.android.database.widget.WidgetBackgroundType import io.homeassistant.companion.android.databinding.WidgetMediaControlsConfigureBinding +import io.homeassistant.companion.android.repositories.MediaPlayerControlsWidgetRepository import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity +import io.homeassistant.companion.android.widgets.BaseWidgetProvider.Companion.RECEIVE_DATA import io.homeassistant.companion.android.widgets.common.SingleItemArrayAdapter import io.homeassistant.companion.android.widgets.common.WidgetUtils import java.util.LinkedList -import javax.inject.Inject import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @AndroidEntryPoint -class MediaPlayerControlsWidgetConfigureActivity : BaseWidgetConfigureActivity() { +class MediaPlayerControlsWidgetConfigureActivity : BaseWidgetConfigureActivity() { companion object { private const val TAG: String = "MediaWidgetConfigAct" @@ -41,10 +41,6 @@ class MediaPlayerControlsWidgetConfigureActivity : BaseWidgetConfigureActivity() private var requestLauncherSetup = false - @Inject - lateinit var mediaPlayerControlsWidgetDao: MediaPlayerControlsWidgetDao - override val dao get() = mediaPlayerControlsWidgetDao - private lateinit var binding: WidgetMediaControlsConfigureBinding override val serverSelect: View @@ -115,7 +111,7 @@ class MediaPlayerControlsWidgetConfigureActivity : BaseWidgetConfigureActivity() return } - val mediaPlayerWidget = mediaPlayerControlsWidgetDao.get(appWidgetId) + val mediaPlayerWidget = repository.get(appWidgetId) val backgroundTypeValues = WidgetUtils.getBackgroundOptionList(this) binding.backgroundType.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, backgroundTypeValues) @@ -207,7 +203,7 @@ class MediaPlayerControlsWidgetConfigureActivity : BaseWidgetConfigureActivity() // Set up a broadcast intent and pass the service call data as extras val intent = Intent() - intent.action = MediaPlayerControlsWidget.RECEIVE_DATA + intent.action = RECEIVE_DATA intent.component = ComponentName(context, MediaPlayerControlsWidget::class.java) intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidget.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidget.kt index 528a74023c4..11c3b25ec09 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidget.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidget.kt @@ -285,6 +285,7 @@ class TemplateWidget : AppWidgetProvider() { TemplateWidgetEntity( appWidgetId, serverId, + "template_$appWidgetId", template, textSize, templateWidgetDao.get(appWidgetId)?.lastUpdate ?: "Loading", diff --git a/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidgetConfigureActivity.kt b/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidgetConfigureActivity.kt index 43b108ee005..78af90b234a 100644 --- a/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidgetConfigureActivity.kt +++ b/app/src/main/java/io/homeassistant/companion/android/widgets/template/TemplateWidgetConfigureActivity.kt @@ -21,29 +21,25 @@ import androidx.lifecycle.lifecycleScope import com.fasterxml.jackson.databind.JsonMappingException import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.common.R as commonR -import io.homeassistant.companion.android.database.widget.TemplateWidgetDao import io.homeassistant.companion.android.database.widget.WidgetBackgroundType import io.homeassistant.companion.android.databinding.WidgetTemplateConfigureBinding +import io.homeassistant.companion.android.repositories.TemplateWidgetRepository import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel import io.homeassistant.companion.android.util.getHexForColor import io.homeassistant.companion.android.widgets.BaseWidgetConfigureActivity +import io.homeassistant.companion.android.widgets.BaseWidgetProvider.Companion.RECEIVE_DATA import io.homeassistant.companion.android.widgets.common.WidgetUtils -import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @AndroidEntryPoint -class TemplateWidgetConfigureActivity : BaseWidgetConfigureActivity() { +class TemplateWidgetConfigureActivity : BaseWidgetConfigureActivity() { companion object { private const val TAG: String = "TemplateWidgetConfigAct" private const val PIN_WIDGET_CALLBACK = "io.homeassistant.companion.android.widgets.template.TemplateWidgetConfigureActivity.PIN_WIDGET_CALLBACK" } - @Inject - lateinit var templateWidgetDao: TemplateWidgetDao - override val dao get() = templateWidgetDao - private lateinit var binding: WidgetTemplateConfigureBinding override val serverSelect: View @@ -84,7 +80,7 @@ class TemplateWidgetConfigureActivity : BaseWidgetConfigureActivity() { return } - val templateWidget = templateWidgetDao.get(appWidgetId) + val templateWidget = repository.get(appWidgetId) val backgroundTypeValues = WidgetUtils.getBackgroundOptionList(this) binding.backgroundType.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, backgroundTypeValues) diff --git a/automotive/src/main/AndroidManifest.xml b/automotive/src/main/AndroidManifest.xml index 2d7678b6df9..9a45a3823fd 100644 --- a/automotive/src/main/AndroidManifest.xml +++ b/automotive/src/main/AndroidManifest.xml @@ -145,7 +145,7 @@ android:exported="false"> - + - + diff --git a/common/schemas/io.homeassistant.companion.android.database.AppDatabase/48.json b/common/schemas/io.homeassistant.companion.android.database.AppDatabase/48.json new file mode 100644 index 00000000000..f960a04a82b --- /dev/null +++ b/common/schemas/io.homeassistant.companion.android.database.AppDatabase/48.json @@ -0,0 +1,1140 @@ +{ + "formatVersion": 1, + "database": { + "version": 48, + "identityHash": "0c9247ff03d0dd1f6d7ef42c18c369ce", + "entities": [ + { + "tableName": "sensor_attributes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))", + "fields": [ + { + "fieldPath": "sensorId", + "columnName": "sensor_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "value_type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sensor_id", + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "authentication_list", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`host`))", + "fields": [ + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "host" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sensors", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL, `registered` INTEGER DEFAULT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT DEFAULT NULL, `last_sent_icon` TEXT DEFAULT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, `state_class` TEXT, `entity_category` TEXT, `core_registration` TEXT, `app_registration` TEXT, PRIMARY KEY(`id`, `server_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registered", + "columnName": "registered", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSentState", + "columnName": "last_sent_state", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "lastSentIcon", + "columnName": "last_sent_icon", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "stateType", + "columnName": "state_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deviceClass", + "columnName": "device_class", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "unitOfMeasurement", + "columnName": "unit_of_measurement", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "stateClass", + "columnName": "state_class", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "entityCategory", + "columnName": "entity_category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coreRegistration", + "columnName": "core_registration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appRegistration", + "columnName": "app_registration", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "server_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sensor_settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `entries` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))", + "fields": [ + { + "fieldPath": "sensorId", + "columnName": "sensor_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "value_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entries", + "columnName": "entries", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sensor_id", + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "button_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT, `icon_name` TEXT NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, `require_authentication` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iconName", + "columnName": "icon_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "service", + "columnName": "service", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceData", + "columnName": "service_data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "requireAuthentication", + "columnName": "require_authentication", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "camera_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `tap_action` TEXT NOT NULL DEFAULT 'REFRESH', PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tapAction", + "columnName": "tap_action", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'REFRESH'" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "media_player_controls_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `label` TEXT, `show_skip` INTEGER NOT NULL, `show_seek` INTEGER NOT NULL, `show_volume` INTEGER NOT NULL, `show_source` INTEGER NOT NULL DEFAULT false, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "showSkip", + "columnName": "show_skip", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showSeek", + "columnName": "show_seek", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showVolume", + "columnName": "show_volume", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showSource", + "columnName": "show_source", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "static_widget", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `attribute_ids` TEXT, `label` TEXT, `text_size` REAL NOT NULL, `state_separator` TEXT NOT NULL, `attribute_separator` TEXT NOT NULL, `tap_action` TEXT NOT NULL DEFAULT 'REFRESH', `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributeIds", + "columnName": "attribute_ids", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "textSize", + "columnName": "text_size", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "stateSeparator", + "columnName": "state_separator", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributeSeparator", + "columnName": "attribute_separator", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tapAction", + "columnName": "tap_action", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'REFRESH'" + }, + { + "fieldPath": "lastUpdate", + "columnName": "last_update", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "template_widgets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL DEFAULT 'template', `template` TEXT NOT NULL, `text_size` REAL NOT NULL DEFAULT 12.0, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'template'" + }, + { + "fieldPath": "template", + "columnName": "template", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "textSize", + "columnName": "text_size", + "affinity": "REAL", + "notNull": true, + "defaultValue": "12.0" + }, + { + "fieldPath": "lastUpdate", + "columnName": "last_update", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backgroundType", + "columnName": "background_type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'DAYNIGHT'" + }, + { + "fieldPath": "textColor", + "columnName": "text_color", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notification_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, `source` TEXT NOT NULL, `server_id` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "location_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `created` INTEGER NOT NULL, `trigger` TEXT NOT NULL, `result` TEXT NOT NULL, `latitude` REAL, `longitude` REAL, `location_name` TEXT, `accuracy` INTEGER, `data` TEXT, `server_id` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trigger", + "columnName": "trigger", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "result", + "columnName": "result", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "locationName", + "columnName": "location_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accuracy", + "columnName": "accuracy", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "qs_tiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tile_id` TEXT NOT NULL, `added` INTEGER NOT NULL DEFAULT 1, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_name` TEXT, `entity_id` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT, `should_vibrate` INTEGER NOT NULL DEFAULT 0, `auth_required` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tileId", + "columnName": "tile_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "serverId", + "columnName": "server_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "iconName", + "columnName": "icon_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subtitle", + "columnName": "subtitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shouldVibrate", + "columnName": "should_vibrate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "authRequired", + "columnName": "auth_required", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorites", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorite_cache", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `friendly_name` TEXT NOT NULL, `icon` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "friendlyName", + "columnName": "friendly_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "camera_tiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT, `refresh_interval` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "refreshInterval", + "columnName": "refresh_interval", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "entity_state_complications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, `show_title` INTEGER NOT NULL DEFAULT 1, `show_unit` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entityId", + "columnName": "entity_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "showTitle", + "columnName": "show_title", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "showUnit", + "columnName": "show_unit", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "servers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_name` TEXT NOT NULL, `name_override` TEXT, `_version` TEXT, `device_registry_id` TEXT, `list_order` INTEGER NOT NULL, `device_name` TEXT, `external_url` TEXT NOT NULL, `internal_url` TEXT, `cloud_url` TEXT, `webhook_id` TEXT, `secret` TEXT, `cloudhook_url` TEXT, `use_cloud` INTEGER NOT NULL, `internal_ssids` TEXT NOT NULL, `prioritize_internal` INTEGER NOT NULL, `access_token` TEXT, `refresh_token` TEXT, `token_expiration` INTEGER, `token_type` TEXT, `install_id` TEXT, `user_id` TEXT, `user_name` TEXT, `user_is_owner` INTEGER, `user_is_admin` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "_name", + "columnName": "_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "nameOverride", + "columnName": "name_override", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "_version", + "columnName": "_version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceRegistryId", + "columnName": "device_registry_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "listOrder", + "columnName": "list_order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceName", + "columnName": "device_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.externalUrl", + "columnName": "external_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "connection.internalUrl", + "columnName": "internal_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.cloudUrl", + "columnName": "cloud_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.webhookId", + "columnName": "webhook_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.secret", + "columnName": "secret", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.cloudhookUrl", + "columnName": "cloudhook_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connection.useCloud", + "columnName": "use_cloud", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "connection.internalSsids", + "columnName": "internal_ssids", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "connection.prioritizeInternal", + "columnName": "prioritize_internal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "session.accessToken", + "columnName": "access_token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.refreshToken", + "columnName": "refresh_token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.tokenExpiration", + "columnName": "token_expiration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "session.tokenType", + "columnName": "token_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "session.installId", + "columnName": "install_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.id", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.name", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.isOwner", + "columnName": "user_is_owner", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "user.isAdmin", + "columnName": "user_is_admin", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocket_setting` TEXT NOT NULL, `sensor_update_frequency` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "websocketSetting", + "columnName": "websocket_setting", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sensorUpdateFrequency", + "columnName": "sensor_update_frequency", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0c9247ff03d0dd1f6d7ef42c18c369ce')" + ] + } +} \ No newline at end of file diff --git a/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt b/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt index 3f3b64f5b60..df81f5951c6 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt @@ -97,7 +97,7 @@ import kotlinx.coroutines.runBlocking Server::class, Setting::class ], - version = 47, + version = 48, autoMigrations = [ AutoMigration(from = 24, to = 25), AutoMigration(from = 25, to = 26), @@ -120,7 +120,8 @@ import kotlinx.coroutines.runBlocking AutoMigration(from = 43, to = 44), AutoMigration(from = 44, to = 45), AutoMigration(from = 45, to = 46), - AutoMigration(from = 46, to = 47) + AutoMigration(from = 46, to = 47), + AutoMigration(from = 47, to = 48) ] ) @TypeConverters( diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/ButtonWidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/ButtonWidgetEntity.kt index 3e7d33fb139..e9d7fde7967 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/ButtonWidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/ButtonWidgetEntity.kt @@ -10,6 +10,8 @@ data class ButtonWidgetEntity( override val id: Int, @ColumnInfo(name = "server_id", defaultValue = "0") override val serverId: Int, + @ColumnInfo(name = "entity_id") + override val entityId: String?, @ColumnInfo(name = "icon_name") val iconName: String, @ColumnInfo(name = "domain") diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/CameraWidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/CameraWidgetEntity.kt index 932f6b841b7..e74c393e3ff 100755 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/CameraWidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/CameraWidgetEntity.kt @@ -11,7 +11,7 @@ data class CameraWidgetEntity( @ColumnInfo(name = "server_id", defaultValue = "0") override val serverId: Int, @ColumnInfo(name = "entity_id") - val entityId: String, + override val entityId: String, @ColumnInfo(name = "tap_action", defaultValue = "REFRESH") val tapAction: WidgetTapAction ) : WidgetEntity diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/MediaPlayerControlsWidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/MediaPlayerControlsWidgetEntity.kt index a421ed59ebc..f7f599059af 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/MediaPlayerControlsWidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/MediaPlayerControlsWidgetEntity.kt @@ -11,7 +11,7 @@ data class MediaPlayerControlsWidgetEntity( @ColumnInfo(name = "server_id", defaultValue = "0") override val serverId: Int, @ColumnInfo(name = "entity_id") - val entityId: String, + override val entityId: String, @ColumnInfo(name = "label") val label: String?, @ColumnInfo(name = "show_skip") diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/StaticWidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/StaticWidgetEntity.kt index 7aa044f8634..be0e676a49c 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/StaticWidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/StaticWidgetEntity.kt @@ -11,7 +11,7 @@ data class StaticWidgetEntity( @ColumnInfo(name = "server_id", defaultValue = "0") override val serverId: Int, @ColumnInfo(name = "entity_id") - val entityId: String, + override val entityId: String, @ColumnInfo(name = "attribute_ids") val attributeIds: String?, @ColumnInfo(name = "label") diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/TemplateWidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/TemplateWidgetEntity.kt index 097f10efe80..4ccd58c05fb 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/TemplateWidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/TemplateWidgetEntity.kt @@ -10,6 +10,8 @@ data class TemplateWidgetEntity( override val id: Int, @ColumnInfo(name = "server_id", defaultValue = "0") override val serverId: Int, + @ColumnInfo(name = "entity_id", defaultValue = "template") + override val entityId: String, @ColumnInfo(name = "template") val template: String, @ColumnInfo(name = "text_size", defaultValue = "12.0") diff --git a/common/src/main/java/io/homeassistant/companion/android/database/widget/WidgetEntity.kt b/common/src/main/java/io/homeassistant/companion/android/database/widget/WidgetEntity.kt index dafd23fe02a..d4cea678574 100644 --- a/common/src/main/java/io/homeassistant/companion/android/database/widget/WidgetEntity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/database/widget/WidgetEntity.kt @@ -3,4 +3,5 @@ package io.homeassistant.companion.android.database.widget interface WidgetEntity { val id: Int val serverId: Int + val entityId: String? }