Let widgets use any material icon (#745)

* Functional widgets with all material icons to pick from.

* Load icons in io thread since it takes a hot second.

* Bump DB version.

* ktlint

* Blow away widget prefs since it was a hack anyway.

* Migrate static widgets to room db.

* ktlint and tests.

* Little more cleanup.

* More ktlint.

* Initial attempt at Migrations.

* Add missing Migrations.kt

* Add full migration support from old to new widgets.
This commit is contained in:
Justin Bassett 2020-08-16 22:18:20 -04:00 committed by GitHub
parent 6bc19488b4
commit 4c91c34ec8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 339 additions and 662 deletions

View file

@ -117,6 +117,8 @@ dependencies {
implementation(project(":domain"))
implementation(Config.Dependency.Misc.blurView)
implementation(Config.Dependency.Misc.iconDialog)
implementation(Config.Dependency.Misc.iconDialogMaterial)
implementation(Config.Dependency.Misc.emoji) {
exclude(group = "org.json", module = "json")
}

View file

@ -9,6 +9,7 @@ import android.telephony.TelephonyManager
import io.homeassistant.companion.android.common.dagger.AppComponent
import io.homeassistant.companion.android.common.dagger.Graph
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.common.migrations.Migrations
import io.homeassistant.companion.android.sensors.SensorReceiver
open class HomeAssistantApplication : Application(), GraphComponentAccessor {
@ -20,6 +21,8 @@ open class HomeAssistantApplication : Application(), GraphComponentAccessor {
graph = Graph(this, 0)
Migrations(this).migrate()
val sensorReceiver = SensorReceiver()
// This will cause the sensor to be updated every time the OS broadcasts that a cable was plugged/unplugged.
// This should be nearly instantaneous allowing automations to fire immediately when a phone is plugged

View file

@ -10,20 +10,29 @@ import io.homeassistant.companion.android.database.authentication.Authentication
import io.homeassistant.companion.android.database.authentication.AuthenticationDao
import io.homeassistant.companion.android.database.sensor.Sensor
import io.homeassistant.companion.android.database.sensor.SensorDao
import io.homeassistant.companion.android.database.widget.ButtonWidget
import io.homeassistant.companion.android.database.widget.ButtonWidgetDao
import io.homeassistant.companion.android.database.widget.StaticWidget
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
@Database(
entities = [
Authentication::class,
Sensor::class
Sensor::class,
ButtonWidget::class,
StaticWidget::class
],
version = 2
version = 3
)
abstract class AppDatabase : RoomDatabase() {
abstract fun authenticationDao(): AuthenticationDao
abstract fun sensorDao(): SensorDao
abstract fun buttonWidgetDao(): ButtonWidgetDao
abstract fun staticWidgetDao(): StaticWidgetDao
companion object {
private const val DATABASE_NAME = "HomeAssistantDB"
@Volatile
private var instance: AppDatabase? = null
@ -37,18 +46,25 @@ abstract class AppDatabase : RoomDatabase() {
return Room
.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.addMigrations(
MIGRATION_1_2,
MIGRATION_2_3
)
.build()
}
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"CREATE TABLE IF NOT EXISTS `sensors` (`unique_id` TEXT NOT NULL, " +
"`enabled` INTEGER NOT NULL, `registered` INTEGER NOT NULL, " +
"`state` TEXT NOT NULL, PRIMARY KEY(`unique_id`))"
database.execSQL("CREATE TABLE IF NOT EXISTS `sensors` (`unique_id` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `registered` INTEGER NOT NULL, `state` TEXT NOT NULL, PRIMARY KEY(`unique_id`))"
)
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `button_widgets` (`id` INTEGER NOT NULL, `icon_id` INTEGER NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, PRIMARY KEY(`id`))")
database.execSQL("CREATE TABLE IF NOT EXISTS `static_widget` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, `attribute_id` TEXT, `label` TEXT, PRIMARY KEY(`id`))")
}
}
}
}

View file

@ -0,0 +1,21 @@
package io.homeassistant.companion.android.database.widget
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "button_widgets")
data class ButtonWidget(
@PrimaryKey
val id: Int,
@ColumnInfo(name = "icon_id")
val iconId: Int,
@ColumnInfo(name = "domain")
val domain: String,
@ColumnInfo(name = "service")
val service: String,
@ColumnInfo(name = "service_data")
val serviceData: String,
@ColumnInfo(name = "label")
val label: String?
)

View file

@ -0,0 +1,23 @@
package io.homeassistant.companion.android.database.widget
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
@Dao
interface ButtonWidgetDao {
@Query("SELECT * FROM button_widgets WHERE id = :id")
fun get(id: Int): ButtonWidget?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(buttonWidget: ButtonWidget)
@Update
fun update(buttonWidget: ButtonWidget)
@Query("DELETE FROM button_widgets WHERE id = :id")
fun delete(id: Int)
}

View file

@ -0,0 +1,17 @@
package io.homeassistant.companion.android.database.widget
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "static_widget")
data class StaticWidget(
@PrimaryKey
val id: Int,
@ColumnInfo(name = "entity_id")
val entityId: String,
@ColumnInfo(name = "attribute_id")
val attributeId: String?,
@ColumnInfo(name = "label")
val label: String?
)

View file

@ -0,0 +1,23 @@
package io.homeassistant.companion.android.database.widget
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
@Dao
interface StaticWidgetDao {
@Query("SELECT * FROM static_widget WHERE id = :id")
fun get(id: Int): StaticWidget?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(staticWidget: StaticWidget)
@Update
fun update(staticWidget: StaticWidget)
@Query("DELETE FROM static_widget WHERE id = :id")
fun delete(id: Int)
}

View file

@ -0,0 +1,89 @@
package io.homeassistant.companion.android.common.migrations
import android.app.Application
import android.content.Context
import android.util.Log
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.widget.ButtonWidget
import io.homeassistant.companion.android.database.widget.StaticWidget
class Migrations constructor(
private val application: Application
) {
companion object {
private const val TAG = "Migrations"
private const val PREF_NAME = "migrations"
private const val PREF_VERSION = "migration_version"
private const val LATEST_VERSION = 3
}
fun migrate() {
val preferences = application.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val version = preferences.getInt(PREF_VERSION, LATEST_VERSION)
if (version < 3) {
migration3()
}
preferences.edit().putInt(PREF_VERSION, LATEST_VERSION).apply()
}
/**
* "Migrate" to the new room db for saving settings. Hopefully the new icons are enough to
* look over the fact they had to setup widgets again...
*/
private fun migration3() {
Log.d(TAG, "Starting migration #3")
val widgetLocalStorage = application.getSharedPreferences("widget", Context.MODE_PRIVATE)
val buttonWidgetIds = widgetLocalStorage.all.keys
.filter { it.startsWith("widget_icon") }
.map { it.removePrefix("widget_icon") }
val buttonWidgetDao = AppDatabase.getInstance(application).buttonWidgetDao()
buttonWidgetIds.forEach { id ->
val icon = widgetLocalStorage.getString("widget_icon$id", "ic_flash_on_24dp")
val domain = widgetLocalStorage.getString("widget_domain$id", "")!!
val service = widgetLocalStorage.getString("widget_service$id", "")!!
val serviceData = widgetLocalStorage.getString("widget_serviceData$id", "")!!
val label = widgetLocalStorage.getString("widget_label$id", null)
val iconId = when (icon) {
"ic_flash_on_24dp" -> 988171
"ic_home_24dp" -> 62172
"ic_lightbulb_outline_24dp" -> 62261
"ic_power_settings_new_24dp" -> 62501
else -> 988171
}
buttonWidgetDao.add(ButtonWidget(
id.toInt(),
iconId,
domain,
service,
serviceData,
label
))
}
val staticWidgetIds = widgetLocalStorage.all.keys
.filter { it.startsWith("widget_entity") }
.map { it.removePrefix("widget_entity") }
val staticWidgetDao = AppDatabase.getInstance(application).staticWidgetDao()
staticWidgetIds.forEach { id ->
val entityId = widgetLocalStorage.getString("widget_entity$id", "")!!
val attribute = widgetLocalStorage.getString("widget_attribute$id", null)
val label = widgetLocalStorage.getString("widget_label$id", null)
staticWidgetDao.add(StaticWidget(
id.toInt(),
entityId,
attribute,
label
))
}
widgetLocalStorage.edit().clear().apply()
Log.d(TAG, "Finished migration #3")
}
}

View file

@ -5,17 +5,25 @@ import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toBitmap
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.maltaisn.icondialog.pack.IconPack
import com.maltaisn.icondialog.pack.IconPackLoader
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.widget.ButtonWidget
import io.homeassistant.companion.android.database.widget.ButtonWidgetDao
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.widgets.WidgetUseCase
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -39,8 +47,10 @@ class ButtonWidget : AppWidgetProvider() {
@Inject
lateinit var integrationUseCase: IntegrationUseCase
@Inject
lateinit var widgetStorage: WidgetUseCase
lateinit var buttonWidgetDao: ButtonWidgetDao
private var iconPack: IconPack? = null
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
@ -49,6 +59,7 @@ class ButtonWidget : AppWidgetProvider() {
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
buttonWidgetDao = AppDatabase.getInstance(context).buttonWidgetDao()
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
mainScope.launch {
@ -59,11 +70,10 @@ class ButtonWidget : AppWidgetProvider() {
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
buttonWidgetDao = AppDatabase.getInstance(context).buttonWidgetDao()
// When the user deletes the widget, delete the preference associated with it.
for (appWidgetId in appWidgetIds) {
mainScope.launch {
widgetStorage.deleteWidgetData(appWidgetId)
}
buttonWidgetDao.delete(appWidgetId)
}
}
@ -87,6 +97,8 @@ class ButtonWidget : AppWidgetProvider() {
ensureInjected(context)
buttonWidgetDao = AppDatabase.getInstance(context).buttonWidgetDao()
super.onReceive(context, intent)
when (action) {
CALL_SERVICE -> callConfiguredService(context, appWidgetId)
@ -94,7 +106,7 @@ class ButtonWidget : AppWidgetProvider() {
}
}
private suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int): RemoteViews {
private fun getWidgetRemoteViews(context: Context, appWidgetId: Int): RemoteViews {
// Every time AppWidgetManager.updateAppWidget(...) is called, the button listener
// and label need to be re-assigned, or the next time the layout updates
// (e.g home screen rotation) the widget will fall back on its default layout
@ -105,14 +117,26 @@ class ButtonWidget : AppWidgetProvider() {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val views = RemoteViews(context.packageName, R.layout.widget_button).apply {
val iconName = widgetStorage.loadIcon(appWidgetId) ?: "ic_flash_on_24dp"
val icon = context.resources.getIdentifier(iconName, "drawable", `package`)
val widget = buttonWidgetDao.get(appWidgetId)
// Create an icon pack and load all drawables.
if (iconPack == null) {
val loader = IconPackLoader(context)
iconPack = createMaterialDesignIconPack(loader)
iconPack!!.loadDrawables(loader.drawableLoader)
}
return RemoteViews(context.packageName, R.layout.widget_button).apply {
val iconId = widget?.iconId ?: 988171 // Lightning bolt
val iconDrawable = iconPack?.icons?.get(iconId)?.drawable
if (iconDrawable != null) {
val icon = DrawableCompat.wrap(iconDrawable)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
DrawableCompat.setTint(icon, context.resources.getColor(R.color.colorIcon, context.theme))
}
setImageViewBitmap(R.id.widgetImageButton, icon.toBitmap())
}
setImageViewResource(
R.id.widgetImageButton,
icon
)
setOnClickPendingIntent(
R.id.widgetImageButtonLayout,
PendingIntent.getBroadcast(
@ -124,11 +148,9 @@ class ButtonWidget : AppWidgetProvider() {
)
setTextViewText(
R.id.widgetLabel,
widgetStorage.loadLabel(appWidgetId)
widget?.label ?: ""
)
}
return views
}
private fun callConfiguredService(context: Context, appWidgetId: Int) {
@ -136,14 +158,15 @@ class ButtonWidget : AppWidgetProvider() {
// Set up progress bar as immediate feedback to show the click has been received
// Success or failure feedback will come from the mainScope coroutine
val loadingViews: RemoteViews =
RemoteViews(context.packageName, R.layout.widget_button)
val loadingViews = RemoteViews(context.packageName, R.layout.widget_button)
val appWidgetManager = AppWidgetManager.getInstance(context)
loadingViews.setViewVisibility(R.id.widgetProgressBar, View.VISIBLE)
loadingViews.setViewVisibility(R.id.widgetImageButtonLayout, View.GONE)
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, loadingViews)
val widget = buttonWidgetDao.get(appWidgetId)
mainScope.launch {
// Change color of background image for feedback
var views = getWidgetRemoteViews(context, appWidgetId)
@ -153,9 +176,9 @@ class ButtonWidget : AppWidgetProvider() {
var feedbackIcon = R.drawable.ic_clear_black
// Load the service call data from Shared Preferences
val domain = widgetStorage.loadDomain(appWidgetId)
val service = widgetStorage.loadService(appWidgetId)
val serviceDataJson = widgetStorage.loadServiceData(appWidgetId)
val domain = widget?.domain
val service = widget?.service
val serviceDataJson = widget?.serviceData
Log.d(
TAG, "Service Call Data loaded:" + System.lineSeparator() +
@ -171,7 +194,8 @@ class ButtonWidget : AppWidgetProvider() {
try {
// Convert JSON to HashMap
val serviceDataMap: HashMap<String, Any> = jacksonObjectMapper().readValue(serviceDataJson)
val serviceDataMap: HashMap<String, Any> =
jacksonObjectMapper().readValue(serviceDataJson)
integrationUseCase.callService(domain, service, serviceDataMap)
@ -230,16 +254,8 @@ class ButtonWidget : AppWidgetProvider() {
"label: " + label
)
widgetStorage.saveServiceCallData(
appWidgetId,
domain,
service,
serviceData
)
widgetStorage.saveLabel(appWidgetId, label)
val iconName = context.resources.getResourceEntryName(icon)
widgetStorage.saveIcon(appWidgetId, iconName)
val widget = ButtonWidget(appWidgetId, icon, domain, service, serviceData, label)
buttonWidgetDao.add(widget)
// It is the responsibility of the configuration activity to update the app widget
// This method is only called during the initial setup of the widget,

View file

@ -1,38 +0,0 @@
package io.homeassistant.companion.android.widgets
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import io.homeassistant.companion.android.R
import kotlinx.android.synthetic.main.widget_image_spinner.view.*
class ButtonWidgetConfigSpinnerAdaptor(
var context: Context,
var icons: IntArray
) : BaseAdapter() {
var inflator: LayoutInflater = LayoutInflater.from(context)
override fun getCount(): Int {
return icons.size
}
override fun getItem(p0: Int): Any {
return context.getDrawable(icons[p0])!!
}
override fun getItemId(p0: Int): Long {
return icons[p0].toLong()
}
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
// Inflate the view if it hasn't been inflated yet
val view: View = p1 ?: inflator.inflate(R.layout.widget_image_spinner, p2, false)
// Assign the correct icon
view.widget_config_spinner_icon.setImageResource(icons[p0])
return view
}
}

View file

@ -1,9 +1,9 @@
package io.homeassistant.companion.android.widgets
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -14,8 +14,17 @@ import android.widget.AutoCompleteTextView
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.recyclerview.widget.LinearLayoutManager
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.maltaisn.icondialog.IconDialog
import com.maltaisn.icondialog.IconDialogSettings
import com.maltaisn.icondialog.data.Icon
import com.maltaisn.icondialog.pack.IconPack
import com.maltaisn.icondialog.pack.IconPackLoader
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.domain.integration.Entity
@ -29,12 +38,19 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class ButtonWidgetConfigureActivity : Activity() {
private val TAG: String = "ButtonWidgetConfigAct"
class ButtonWidgetConfigureActivity : AppCompatActivity(), IconDialog.Callback {
companion object {
private const val TAG: String = "ButtonWidgetConfigAct"
private const val ICON_DIALOG_TAG = "icon-dialog"
}
@Inject
lateinit var integrationUseCase: IntegrationUseCase
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
private lateinit var iconPack: IconPack
private var services = HashMap<String, Service>()
private var entities = HashMap<String, Entity<Any>>()
private var dynamicFields = ArrayList<ServiceFieldBinder>()
@ -44,7 +60,7 @@ class ButtonWidgetConfigureActivity : Activity() {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
private var onClickListener = View.OnClickListener {
private var onAddWidget = View.OnClickListener {
try {
val context = this@ButtonWidgetConfigureActivity
@ -75,7 +91,7 @@ class ButtonWidgetConfigureActivity : Activity() {
)
intent.putExtra(
ButtonWidget.EXTRA_ICON,
context.widget_config_spinner.selectedItemId.toInt()
context.widget_config_icon_selector.tag as Int
)
// Analyze and send service data
@ -214,6 +230,9 @@ class ButtonWidgetConfigureActivity : Activity() {
.build()
.inject(this)
// Create an icon pack loader with application context.
val loader = IconPackLoader(this)
val serviceAdapter = SingleItemArrayAdapter<Service>(this) {
if (it != null) getServiceString(it) else ""
}
@ -253,25 +272,46 @@ class ButtonWidgetConfigureActivity : Activity() {
widget_text_config_service.addTextChangedListener(serviceTextWatcher)
add_field_button.setOnClickListener(onAddFieldListener)
add_button.setOnClickListener(onClickListener)
add_button.setOnClickListener(onAddWidget)
dynamicFieldAdapter = WidgetDynamicFieldAdapter(services, entities, dynamicFields)
widget_config_fields_layout.adapter = dynamicFieldAdapter
widget_config_fields_layout.layoutManager = LinearLayoutManager(this)
// Set up icon spinner
val icons = intArrayOf(
R.drawable.ic_flash_on_24dp,
R.drawable.ic_lightbulb_outline_24dp,
R.drawable.ic_home_24dp,
R.drawable.ic_power_settings_new_24dp
)
widget_config_spinner.adapter = ButtonWidgetConfigSpinnerAdaptor(this, icons)
// Do this off the main thread, takes a second or two...
ioScope.launch {
// Create an icon pack and load all drawables.
iconPack = createMaterialDesignIconPack(loader)
iconPack.loadDrawables(loader.drawableLoader)
val iconDialog = IconDialog.newInstance(IconDialogSettings())
onIconDialogIconsSelected(iconDialog, listOf(iconPack.icons[62017]!!))
widget_config_icon_selector.setOnClickListener {
iconDialog.show(supportFragmentManager, ICON_DIALOG_TAG)
}
}
}
override fun onDestroy() {
mainScope.cancel()
super.onDestroy()
}
override val iconDialogIconPack: IconPack?
get() = iconPack
override fun onIconDialogIconsSelected(dialog: IconDialog, icons: List<Icon>) {
Log.d(TAG, "Selected icon: ${icons.firstOrNull()}")
val selectedIcon = icons.firstOrNull()
if (selectedIcon != null) {
widget_config_icon_selector.tag = selectedIcon.id
val iconDrawable = selectedIcon.drawable
if (iconDrawable != null) {
val icon = DrawableCompat.wrap(iconDrawable)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
DrawableCompat.setTint(icon, resources.getColor(R.color.colorIcon, theme))
}
widget_config_icon_selector.setImageBitmap(icon.toBitmap())
}
}
}
}

View file

@ -10,8 +10,10 @@ import android.util.Log
import android.widget.RemoteViews
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.widget.StaticWidget
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.widgets.WidgetUseCase
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -35,8 +37,7 @@ class StaticWidget : AppWidgetProvider() {
@Inject
lateinit var integrationUseCase: IntegrationUseCase
@Inject
lateinit var widgetStorage: WidgetUseCase
lateinit var staticWidgetDao: StaticWidgetDao
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
@ -45,6 +46,7 @@ class StaticWidget : AppWidgetProvider() {
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
staticWidgetDao = AppDatabase.getInstance(context).staticWidgetDao()
// There may be multiple widgets active, so update all of them
appWidgetIds.forEach { appWidgetId ->
updateAppWidget(
@ -73,9 +75,10 @@ class StaticWidget : AppWidgetProvider() {
}
val views = RemoteViews(context.packageName, R.layout.widget_static).apply {
val entityId: String? = widgetStorage.loadEntityId(appWidgetId)
val attributeId: String? = widgetStorage.loadAttributeId(appWidgetId)
val label: String? = widgetStorage.loadLabel(appWidgetId)
val widget = staticWidgetDao.get(appWidgetId)
val entityId: String? = widget?.entityId
val attributeId: String? = widget?.attributeId
val label: String? = widget?.label
setTextViewText(
R.id.widgetText,
resolveTextToShow(entityId, attributeId)
@ -123,6 +126,8 @@ class StaticWidget : AppWidgetProvider() {
ensureInjected(context)
staticWidgetDao = AppDatabase.getInstance(context).staticWidgetDao()
super.onReceive(context, intent)
when (action) {
@ -149,13 +154,12 @@ class StaticWidget : AppWidgetProvider() {
"entity id: " + entitySelection + System.lineSeparator() +
"attribute: " + attributeSelection ?: "N/A"
)
widgetStorage.saveStaticEntityData(
staticWidgetDao.add(StaticWidget(
appWidgetId,
entitySelection,
attributeSelection
)
widgetStorage.saveLabel(appWidgetId, labelSelection)
attributeSelection,
labelSelection
))
onUpdate(context, AppWidgetManager.getInstance(context), intArrayOf(appWidgetId))
}

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/colorIcon"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/colorIcon"
android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/colorIcon"
android:pathData="M13,3h-2v10h2L13,3zM17.83,5.17l-1.42,1.42C17.99,7.86 19,9.81 19,12c0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-2.19 1.01,-4.14 2.58,-5.42L6.17,5.17C4.23,6.82 3,9.26 3,12c0,4.97 4.03,9 9,9s9,-4.03 9,-9c0,-2.74 -1.23,-5.18 -3.17,-6.83z"/>
</vector>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
@ -62,26 +62,31 @@
android:layout_marginTop="8dp"
android:text="@string/add_service_data_field" />
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/widget_config_icon_layout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:text="@string/label_icon" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/widget_config_icon_selector"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:src="@drawable/ic_flash_on_24dp"/>
<Spinner
android:id="@+id/widget_config_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/widget_image_spinner" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:id="@+id/widget_config_spinner_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:scaleType="fitCenter"
tools:src="@drawable/ic_flash_on_24dp"/>
</LinearLayout>

View file

@ -100,9 +100,10 @@ object Config {
const val sentry = "io.sentry:sentry-android:2.2.2"
const val jackson = "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.1"
const val threeTenBp = "org.threeten:threetenbp:1.4.0"
const val threeTenAbp = "com.jakewharton.threetenabp:threetenabp:1.2.1"
const val javaxInject = "javax.inject:javax.inject:1"
const val blurView = "com.eightbitlab:blurview:1.6.3"
const val iconDialog = "com.maltaisn:icondialog:3.3.0"
const val iconDialogMaterial = "com.maltaisn:iconpack-community-material:5.3.45"
const val emoji = "com.vdurmont:emoji-java:5.1.1"
}
}

View file

@ -4,7 +4,6 @@ import dagger.Component
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.url.UrlUseCase
import io.homeassistant.companion.android.domain.widgets.WidgetUseCase
@Component(dependencies = [DomainComponent::class])
interface AppComponent {
@ -14,6 +13,4 @@ interface AppComponent {
fun authenticationUseCase(): AuthenticationUseCase
fun integrationUseCase(): IntegrationUseCase
fun widgetUseCase(): WidgetUseCase
}

View file

@ -4,7 +4,6 @@ import dagger.Component
import io.homeassistant.companion.android.domain.authentication.AuthenticationRepository
import io.homeassistant.companion.android.domain.integration.IntegrationRepository
import io.homeassistant.companion.android.domain.url.UrlRepository
import io.homeassistant.companion.android.domain.widgets.WidgetRepository
@Component(modules = [DataModule::class])
interface DataComponent {
@ -14,6 +13,4 @@ interface DataComponent {
fun authenticationRepository(): AuthenticationRepository
fun integrationRepository(): IntegrationRepository
fun widgetRepository(): WidgetRepository
}

View file

@ -11,12 +11,10 @@ import io.homeassistant.companion.android.data.authentication.AuthenticationServ
import io.homeassistant.companion.android.data.integration.IntegrationRepositoryImpl
import io.homeassistant.companion.android.data.integration.IntegrationService
import io.homeassistant.companion.android.data.url.UrlRepositoryImpl
import io.homeassistant.companion.android.data.widgets.WidgetRepositoryImpl
import io.homeassistant.companion.android.data.wifi.WifiHelper
import io.homeassistant.companion.android.domain.authentication.AuthenticationRepository
import io.homeassistant.companion.android.domain.integration.IntegrationRepository
import io.homeassistant.companion.android.domain.url.UrlRepository
import io.homeassistant.companion.android.domain.widgets.WidgetRepository
import javax.inject.Named
@Module(includes = [DataModule.Declaration::class])
@ -24,7 +22,6 @@ class DataModule(
private val urlStorage: LocalStorage,
private val sessionLocalStorage: LocalStorage,
private val integrationLocalStorage: LocalStorage,
private val widgetLocalStorage: LocalStorage,
private val wifiHelper: WifiHelper,
private val deviceId: String
) {
@ -52,10 +49,6 @@ class DataModule(
@Named("integration")
fun provideIntegrationLocalStorage() = integrationLocalStorage
@Provides
@Named("widget")
fun provideWidgetLocalStorage() = widgetLocalStorage
@Provides
@Named("manufacturer")
fun provideDeviceManufacturer(): String = Build.MANUFACTURER
@ -82,8 +75,5 @@ class DataModule(
@Binds
fun bindIntegrationService(repository: IntegrationRepositoryImpl): IntegrationRepository
@Binds
fun bindWidgetRepositoryImpl(repository: WidgetRepositoryImpl): WidgetRepository
}
}

View file

@ -4,7 +4,6 @@ import dagger.Component
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.url.UrlUseCase
import io.homeassistant.companion.android.domain.widgets.WidgetUseCase
@Component(dependencies = [DataComponent::class], modules = [DomainModule::class])
interface DomainComponent {
@ -14,6 +13,4 @@ interface DomainComponent {
fun authenticationUseCase(): AuthenticationUseCase
fun integrationUseCase(): IntegrationUseCase
fun widgetUseCase(): WidgetUseCase
}

View file

@ -8,8 +8,6 @@ import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.integration.IntegrationUseCaseImpl
import io.homeassistant.companion.android.domain.url.UrlUseCase
import io.homeassistant.companion.android.domain.url.UrlUseCaseImpl
import io.homeassistant.companion.android.domain.widgets.WidgetUseCase
import io.homeassistant.companion.android.domain.widgets.WidgetUseCaseImpl
@Module
interface DomainModule {
@ -22,7 +20,4 @@ interface DomainModule {
@Binds
fun bindIntegrationUseCase(useCaseImpl: IntegrationUseCaseImpl): IntegrationUseCase
@Binds
fun bindWidgetUseCase(useCaseImpl: WidgetUseCaseImpl): WidgetUseCase
}

View file

@ -6,7 +6,6 @@ import android.content.Context
import android.net.wifi.WifiManager
import android.provider.Settings
import io.homeassistant.companion.android.common.LocalStorageImpl
import io.homeassistant.companion.android.common.migrations.Migrations
import io.homeassistant.companion.android.common.wifi.WifiHelperImpl
class Graph(
@ -19,9 +18,6 @@ class Graph(
private lateinit var domainComponent: DomainComponent
init {
Migrations(application)
.migrate()
buildComponent()
}
@ -49,12 +45,6 @@ class Graph(
Context.MODE_PRIVATE
)
),
LocalStorageImpl(
application.getSharedPreferences(
"widget",
Context.MODE_PRIVATE
)
),
WifiHelperImpl(application.getSystemService(Context.WIFI_SERVICE) as WifiManager),
Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID)
)

View file

@ -1,86 +0,0 @@
package io.homeassistant.companion.android.common.migrations
import android.app.Application
import android.content.Context
import android.util.Log
class Migrations constructor(
private val application: Application
) {
companion object {
private const val TAG = "Migrations"
private const val PREF_NAME = "migrations"
private const val PREF_VERSION = "migration_version"
}
fun migrate() {
val preferences = application.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
var version = preferences.getInt(PREF_VERSION, 0)
if (version < 1) {
migration1()
version = 1
}
if (version < 2) {
migration2()
version = 2
}
preferences.edit().putInt(PREF_VERSION, version).apply()
}
/**
* Migrate to url repository and multiple prepare for multiple instances
*/
private fun migration1() {
Log.i(TAG, "Starting Migration #1")
val auth = application.getSharedPreferences("session", Context.MODE_PRIVATE)
val integration = application.getSharedPreferences("integration", Context.MODE_PRIVATE)
val url = application.getSharedPreferences("url_0", Context.MODE_PRIVATE)
url.edit()
.putString("cloudhook_url", integration.getString("cloud_url", null))
.putString("remote_url", integration.getString("remote_ui_url", auth.getString("url", null)))
.putString("webhook_id", integration.getString("webhook_id", null))
.apply()
val newAuth = application.getSharedPreferences("session_0", Context.MODE_PRIVATE)
newAuth.edit()
.putString("access_token", auth.getString("access_token", null))
.putLong("expires_date", auth.getLong("expires_date", 0))
.putString("refresh_token", auth.getString("refresh_token", null))
.putString("token_type", auth.getString("token_type", null))
.apply()
val newIntegration = application.getSharedPreferences("integration_0", Context.MODE_PRIVATE)
newIntegration.edit()
.putString("app_version", integration.getString("app_version", null))
.putString("device_name", integration.getString("device_name", null))
.putString("push_token", integration.getString("push_token", null))
.putString("secret", integration.getString("secret", null))
.putBoolean("zone_enabled", integration.getBoolean("zone_enabled", false))
.putBoolean("background_enabled", integration.getBoolean("background_enabled", false))
.apply()
Log.i(TAG, "Completed Migration #1")
}
/**
* Migrate to use the string set for multiple ssids and remove the old ssid.
*/
private fun migration2() {
Log.i(TAG, "Starting Migration #2")
val url = application.getSharedPreferences("url_0", Context.MODE_PRIVATE)
val oldSsid = url.getString("wifi_ssid", null)
if (!oldSsid.isNullOrBlank()) {
url.edit()
.putStringSet("wifi_ssids", setOf(oldSsid))
.remove("wifi_ssid")
.apply()
}
Log.i(TAG, "Completed Migration #2")
}
}

View file

@ -1,120 +0,0 @@
package io.homeassistant.companion.android.data.widgets
import io.homeassistant.companion.android.data.LocalStorage
import io.homeassistant.companion.android.domain.widgets.WidgetRepository
import javax.inject.Inject
import javax.inject.Named
class WidgetRepositoryImpl @Inject constructor(
@Named("widget") private val localStorage: LocalStorage
) : WidgetRepository {
companion object {
internal const val PREF_PREFIX_KEY = "widget_"
internal const val PREF_KEY_DOMAIN = "domain"
internal const val PREF_KEY_ENTITY = "entity"
internal const val PREF_KEY_SERVICE = "service"
internal const val PREF_KEY_ATTRIBUTE_ID = "attribute"
internal const val PREF_KEY_SERVICE_DATA = "serviceData"
internal const val PREF_KEY_ICON = "icon"
internal const val PREF_KEY_LABEL = "label"
}
override suspend fun saveServiceCallData(
appWidgetId: Int,
domainStr: String,
serviceStr: String,
serviceDataStr: String
) {
saveStringPref(
PREF_PREFIX_KEY + PREF_KEY_DOMAIN + appWidgetId,
domainStr
)
saveStringPref(
PREF_PREFIX_KEY + PREF_KEY_SERVICE + appWidgetId,
serviceStr
)
saveStringPref(
PREF_PREFIX_KEY + PREF_KEY_SERVICE_DATA + appWidgetId,
serviceDataStr
)
}
override suspend fun saveEntityData(
appWidgetId: Int,
entityId: String,
attributeId: String?
) {
saveStringPref(
PREF_PREFIX_KEY + PREF_KEY_ENTITY + appWidgetId,
entityId
)
if (attributeId != null) saveStringPref(
PREF_PREFIX_KEY + PREF_KEY_ATTRIBUTE_ID + appWidgetId,
attributeId
)
}
override suspend fun loadDomain(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_DOMAIN + appWidgetId)
}
override suspend fun loadService(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_SERVICE + appWidgetId)
}
override suspend fun loadEntityId(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_ENTITY + appWidgetId)
}
override suspend fun loadAttributeId(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_ATTRIBUTE_ID + appWidgetId)
}
override suspend fun loadServiceData(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_SERVICE_DATA + appWidgetId)
}
override suspend fun loadIcon(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_ICON + appWidgetId)
}
override suspend fun loadLabel(appWidgetId: Int): String? {
return loadStringPref(PREF_PREFIX_KEY + PREF_KEY_LABEL + appWidgetId)
}
override suspend fun saveIcon(appWidgetId: Int, resName: String?) {
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_ICON + appWidgetId, resName)
}
override suspend fun saveLabel(appWidgetId: Int, data: String?) {
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_LABEL + appWidgetId, data)
}
override suspend fun deleteWidgetData(appWidgetId: Int) {
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_DOMAIN + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_SERVICE + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_SERVICE_DATA + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_ICON + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_LABEL + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_ENTITY + appWidgetId, null)
saveStringPref(PREF_PREFIX_KEY + PREF_KEY_ATTRIBUTE_ID + appWidgetId, null)
}
private suspend fun saveStringPref(key: String, data: String?) {
localStorage.putString(key, data)
}
private suspend fun saveBooleanPref(key: String, data: Boolean) {
localStorage.putBoolean(key, data)
}
private suspend fun loadStringPref(key: String): String? {
return localStorage.getString(key)
}
private suspend fun loadBooleanPref(key: String): Boolean {
return localStorage.getBoolean(key)
}
}

View file

@ -1,32 +0,0 @@
package io.homeassistant.companion.android.domain.widgets
interface WidgetRepository {
suspend fun saveServiceCallData(
appWidgetId: Int,
domainStr: String,
serviceStr: String,
serviceDataStr: String
)
suspend fun saveEntityData(
appWidgetId: Int,
entityId: String,
attributeId: String?
)
suspend fun loadDomain(appWidgetId: Int): String?
suspend fun loadService(appWidgetId: Int): String?
suspend fun loadEntityId(appWidgetId: Int): String?
suspend fun loadAttributeId(appWidgetId: Int): String?
suspend fun loadServiceData(appWidgetId: Int): String?
suspend fun loadIcon(appWidgetId: Int): String?
suspend fun loadLabel(appWidgetId: Int): String?
suspend fun saveIcon(appWidgetId: Int, resName: String?)
suspend fun saveLabel(appWidgetId: Int, data: String?)
suspend fun deleteWidgetData(appWidgetId: Int)
}

View file

@ -1,32 +0,0 @@
package io.homeassistant.companion.android.domain.widgets
interface WidgetUseCase {
suspend fun saveServiceCallData(
appWidgetId: Int,
domainStr: String,
serviceStr: String,
serviceData: String
)
suspend fun saveStaticEntityData(
appWidgetId: Int,
entityId: String,
attributeId: String?
)
suspend fun loadDomain(appWidgetId: Int): String?
suspend fun loadService(appWidgetId: Int): String?
suspend fun loadServiceData(appWidgetId: Int): String?
suspend fun loadEntityId(appWidgetId: Int): String?
suspend fun loadAttributeId(appWidgetId: Int): String?
suspend fun loadIcon(appWidgetId: Int): String?
suspend fun loadLabel(appWidgetId: Int): String?
suspend fun saveIcon(appWidgetId: Int, resName: String?)
suspend fun saveLabel(appWidgetId: Int, data: String?)
suspend fun deleteWidgetData(appWidgetId: Int)
}

View file

@ -1,74 +0,0 @@
package io.homeassistant.companion.android.domain.widgets
import javax.inject.Inject
class WidgetUseCaseImpl @Inject constructor(
private val widgetRepository: WidgetRepository
) : WidgetUseCase {
override suspend fun saveServiceCallData(
appWidgetId: Int,
domainStr: String,
serviceStr: String,
serviceData: String
) {
widgetRepository.saveServiceCallData(
appWidgetId,
domainStr,
serviceStr,
serviceData
)
}
override suspend fun saveStaticEntityData(
appWidgetId: Int,
entityId: String,
attributeId: String?
) {
widgetRepository.saveEntityData(
appWidgetId,
entityId,
attributeId
)
}
override suspend fun loadDomain(appWidgetId: Int): String? {
return widgetRepository.loadDomain(appWidgetId)
}
override suspend fun loadService(appWidgetId: Int): String? {
return widgetRepository.loadService(appWidgetId)
}
override suspend fun loadServiceData(appWidgetId: Int): String? {
return widgetRepository.loadServiceData(appWidgetId)
}
override suspend fun loadEntityId(appWidgetId: Int): String? {
return widgetRepository.loadEntityId(appWidgetId)
}
override suspend fun loadAttributeId(appWidgetId: Int): String? {
return widgetRepository.loadAttributeId(appWidgetId)
}
override suspend fun loadIcon(appWidgetId: Int): String? {
return widgetRepository.loadIcon(appWidgetId)
}
override suspend fun loadLabel(appWidgetId: Int): String? {
return widgetRepository.loadLabel(appWidgetId)
}
override suspend fun saveIcon(appWidgetId: Int, resName: String?) {
widgetRepository.saveIcon(appWidgetId, resName)
}
override suspend fun saveLabel(appWidgetId: Int, data: String?) {
widgetRepository.saveLabel(appWidgetId, data)
}
override suspend fun deleteWidgetData(appWidgetId: Int) {
widgetRepository.deleteWidgetData(appWidgetId)
}
}

View file

@ -1,124 +0,0 @@
package io.homeassistant.companion.android.domain.widgets
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object WidgetUseCaseImplSpec : Spek({
describe("widget use case") {
val widgetRepository by memoized {
mockk<WidgetRepository>(
relaxed = true,
relaxUnitFun = true
)
}
val useCase by memoized { WidgetUseCaseImpl(widgetRepository) }
describe("save service call data") {
beforeEachTest {
runBlocking {
useCase.saveServiceCallData(
1,
"domain",
"service",
"{\"brightness\":\"255\",\"entity_id\":\"light.dummy_light\",\"color_temp\":\"2700\"}"
)
}
}
it("should call the repository") {
coVerify {
widgetRepository.saveServiceCallData(
1,
"domain",
"service",
"{\"brightness\":\"255\",\"entity_id\":\"light.dummy_light\",\"color_temp\":\"2700\"}"
)
}
}
}
describe("load domain") {
beforeEachTest {
runBlocking { useCase.loadDomain(1) }
}
it("should call the repository") {
coVerify { widgetRepository.loadDomain(1) }
}
}
describe("load service") {
beforeEachTest {
runBlocking { useCase.loadService(1) }
}
it("should call the repository") {
coVerify { widgetRepository.loadService(1) }
}
}
describe("load service data") {
beforeEachTest {
runBlocking { useCase.loadServiceData(1) }
}
it("should call the repository") {
coVerify { widgetRepository.loadServiceData(1) }
}
}
describe("load icon") {
beforeEachTest {
runBlocking { useCase.loadIcon(1) }
}
it("should call the repository") {
coVerify { widgetRepository.loadIcon(1) }
}
}
describe("load label") {
beforeEachTest {
runBlocking { useCase.loadLabel(1) }
}
it("should call the repository") {
coVerify { widgetRepository.loadLabel(1) }
}
}
describe("save icon") {
beforeEachTest {
runBlocking { useCase.saveIcon(1, "icon_res_name") }
}
it("should call the repository") {
coVerify { widgetRepository.saveIcon(1, "icon_res_name") }
}
}
describe("save label") {
beforeEachTest {
runBlocking { useCase.saveLabel(1, "label") }
}
it("should call the repository") {
coVerify { widgetRepository.saveLabel(1, "label") }
}
}
describe("delete widget data") {
beforeEachTest {
runBlocking { useCase.deleteWidgetData(1) }
}
it("should call the repository") {
coVerify { widgetRepository.deleteWidgetData(1) }
}
}
}
})