mirror of
https://github.com/home-assistant/android
synced 2024-09-06 17:23:32 +00:00
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:
parent
6bc19488b4
commit
4c91c34ec8
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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?
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in a new issue