mirror of
https://github.com/home-assistant/android
synced 2024-07-22 19:04:20 +00:00
Improve adding quick settings tile (#2860)
* Improve adding quick settings tiles - Use the new option available on Android 13 to prompt the user to add a tile to the quick settings panel, if a tile wasn't already added - Switch Toast to Snackbar - Move some logic from the manage tiles view to viewmodel * Only show subtitle field when supported - Setting a tile subtitle is only supported on Android Q+, so don't show the field on versions before that! * Deeplink into app for tiles that aren't setup - Instead of disabling a tile if it isn't setup, if the user is logged in set it up as inactive with the default icon/label and deeplink into the app settings with a notice that it needs to be setup first. This provides a better first use experience, as the user can simply tap it to setup instead of requiring them to manually go to the settings.
This commit is contained in:
parent
45cdaeb764
commit
bdc9094845
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile10Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_10"
|
||||
const val TILE_ID = "tile_10"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile11Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_11"
|
||||
const val TILE_ID = "tile_11"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile12Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_12"
|
||||
const val TILE_ID = "tile_12"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -19,6 +19,6 @@ class Tile1Service : TileExtensions() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_1"
|
||||
const val TILE_ID = "tile_1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ class Tile2Service : TileExtensions() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_2"
|
||||
const val TILE_ID = "tile_2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ class Tile3Service : TileExtensions() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_3"
|
||||
const val TILE_ID = "tile_3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile4Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_4"
|
||||
const val TILE_ID = "tile_4"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile5Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_5"
|
||||
const val TILE_ID = "tile_5"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile6Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_6"
|
||||
const val TILE_ID = "tile_6"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile7Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_7"
|
||||
const val TILE_ID = "tile_7"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile8Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_8"
|
||||
const val TILE_ID = "tile_8"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.annotation.RequiresApi
|
|||
class Tile9Service : TileExtensions() {
|
||||
|
||||
companion object {
|
||||
private const val TILE_ID = "tile_9"
|
||||
const val TILE_ID = "tile_9"
|
||||
}
|
||||
|
||||
override fun getTile(): Tile? {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.homeassistant.companion.android.qs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
|
@ -14,9 +15,16 @@ import androidx.core.graphics.drawable.toBitmap
|
|||
import com.maltaisn.icondialog.pack.IconPack
|
||||
import com.maltaisn.icondialog.pack.IconPackLoader
|
||||
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.database.qs.TileDao
|
||||
import io.homeassistant.companion.android.database.qs.TileEntity
|
||||
import io.homeassistant.companion.android.database.qs.isSetup
|
||||
import io.homeassistant.companion.android.settings.SettingsActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.cancel
|
||||
|
@ -54,11 +62,24 @@ abstract class TileExtensions : TileService() {
|
|||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
Log.d(TAG, "Tile: ${getTileId()} added")
|
||||
handleInject()
|
||||
getTile()?.let { tile ->
|
||||
mainScope.launch {
|
||||
setTileData(getTileId(), tile)
|
||||
}
|
||||
}
|
||||
MainScope().launch {
|
||||
setTileAdded(getTileId(), true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileRemoved() {
|
||||
super.onTileRemoved()
|
||||
Log.d(TAG, "Tile: ${getTileId()} removed")
|
||||
handleInject()
|
||||
MainScope().launch {
|
||||
setTileAdded(getTileId(), false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
|
@ -81,7 +102,7 @@ abstract class TileExtensions : TileService() {
|
|||
val context = applicationContext
|
||||
val tileData = tileDao.get(tileId)
|
||||
try {
|
||||
return if (tileData != null) {
|
||||
return if (tileData != null && tileData.isSetup) {
|
||||
tile.label = tileData.label
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
tile.subtitle = tileData.subtitle
|
||||
|
@ -106,8 +127,17 @@ abstract class TileExtensions : TileService() {
|
|||
tile.updateTile()
|
||||
true
|
||||
} else {
|
||||
Log.d(TAG, "No tile data found for tile ID: $tileId")
|
||||
tile.state = Tile.STATE_UNAVAILABLE
|
||||
if (tileData != null) {
|
||||
Log.d(TAG, "Tile data found but not setup for tile ID: $tileId")
|
||||
} else {
|
||||
Log.d(TAG, "No tile data found for tile ID: $tileId")
|
||||
}
|
||||
tile.state =
|
||||
if (integrationUseCase.isRegistered()) Tile.STATE_INACTIVE
|
||||
else Tile.STATE_UNAVAILABLE
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
tile.subtitle = getString(commonR.string.tile_not_setup)
|
||||
}
|
||||
tile.updateTile()
|
||||
false
|
||||
}
|
||||
|
@ -162,20 +192,38 @@ abstract class TileExtensions : TileService() {
|
|||
tile.state = Tile.STATE_INACTIVE
|
||||
tile.updateTile()
|
||||
} else {
|
||||
tile.state = Tile.STATE_UNAVAILABLE
|
||||
tile.updateTile()
|
||||
Log.d(TAG, "No tile data found for tile ID: $tileId")
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
commonR.string.tile_data_missing,
|
||||
Toast.LENGTH_SHORT
|
||||
startActivityAndCollapse(
|
||||
SettingsActivity.newInstance(context).apply {
|
||||
putExtra("fragment", "tiles/$tileId")
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
}
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setTileAdded(tileId: String, added: Boolean) {
|
||||
tileDao.get(tileId)?.let {
|
||||
tileDao.add(it.copy(added = added))
|
||||
} ?: run {
|
||||
if (added) { // Store an empty tile in the database to track added
|
||||
tileDao.add(
|
||||
TileEntity(
|
||||
tileId = tileId,
|
||||
added = added,
|
||||
iconId = null,
|
||||
entityId = "",
|
||||
label = "",
|
||||
subtitle = null
|
||||
)
|
||||
)
|
||||
} // else if it doesn't exist and is removed we don't have to save anything
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TileExtensions"
|
||||
private var iconPack: IconPack? = null
|
||||
|
@ -200,4 +248,21 @@ abstract class TileExtensions : TileService() {
|
|||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInject() {
|
||||
// onTileAdded/onTileRemoved might be called outside onCreate - onDestroy, which usually
|
||||
// handles injection. Because we need the DAO to save added/removed, inject it if required.
|
||||
if (!this::tileDao.isInitialized) {
|
||||
tileDao = EntryPointAccessors.fromApplication(
|
||||
this@TileExtensions.applicationContext,
|
||||
TileExtensionsEntryPoint::class.java
|
||||
).tileDao()
|
||||
}
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface TileExtensionsEntryPoint {
|
||||
fun tileDao(): TileDao
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import dagger.hilt.android.components.ActivityComponent
|
|||
import io.homeassistant.companion.android.BaseActivity
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.settings.notification.NotificationHistoryFragment
|
||||
import io.homeassistant.companion.android.settings.qs.ManageTilesFragment
|
||||
import io.homeassistant.companion.android.settings.sensor.SensorDetailFragment
|
||||
import io.homeassistant.companion.android.settings.websocket.WebsocketSettingFragment
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
@ -58,11 +59,15 @@ class SettingsActivity : BaseActivity() {
|
|||
settingsNavigation == "websocket" -> WebsocketSettingFragment::class.java
|
||||
settingsNavigation == "notification_history" -> NotificationHistoryFragment::class.java
|
||||
settingsNavigation?.startsWith("sensors/") == true -> SensorDetailFragment::class.java
|
||||
settingsNavigation?.startsWith("tiles/") == true -> ManageTilesFragment::class.java
|
||||
else -> SettingsFragment::class.java
|
||||
},
|
||||
if (settingsNavigation?.startsWith("sensors/") == true) {
|
||||
val sensorId = settingsNavigation.split("/")[1]
|
||||
SensorDetailFragment.newInstance(sensorId).arguments
|
||||
} else if (settingsNavigation?.startsWith("tiles/") == true) {
|
||||
val tileId = settingsNavigation.split("/")[1]
|
||||
Bundle().apply { putString("id", tileId) }
|
||||
} else null
|
||||
)
|
||||
.commit()
|
||||
|
|
|
@ -5,7 +5,6 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.FragmentFactory
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.settings.language.LanguagesProvider
|
||||
import io.homeassistant.companion.android.settings.qs.ManageTilesFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
class SettingsFragmentFactory @Inject constructor(
|
||||
|
@ -17,7 +16,6 @@ class SettingsFragmentFactory @Inject constructor(
|
|||
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
|
||||
return when (className) {
|
||||
SettingsFragment::class.java.name -> SettingsFragment(settingsPresenter, languagesProvider)
|
||||
ManageTilesFragment::class.java.name -> ManageTilesFragment(integrationRepository)
|
||||
else -> super.instantiate(classLoader, className)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,14 +17,11 @@ import com.maltaisn.icondialog.IconDialogSettings
|
|||
import com.maltaisn.icondialog.pack.IconPack
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ManageTilesFragment constructor(
|
||||
val integrationRepository: IntegrationRepository
|
||||
) : Fragment(), IconDialog.Callback {
|
||||
class ManageTilesFragment : Fragment(), IconDialog.Callback {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TileFragment"
|
||||
|
|
|
@ -1,39 +1,68 @@
|
|||
package io.homeassistant.companion.android.settings.qs
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.Toast
|
||||
import android.app.StatusBarManager
|
||||
import android.content.ComponentName
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.domain
|
||||
import io.homeassistant.companion.android.database.qs.TileDao
|
||||
import io.homeassistant.companion.android.database.qs.TileEntity
|
||||
import io.homeassistant.companion.android.database.qs.isSetup
|
||||
import io.homeassistant.companion.android.qs.Tile10Service
|
||||
import io.homeassistant.companion.android.qs.Tile11Service
|
||||
import io.homeassistant.companion.android.qs.Tile12Service
|
||||
import io.homeassistant.companion.android.qs.Tile1Service
|
||||
import io.homeassistant.companion.android.qs.Tile2Service
|
||||
import io.homeassistant.companion.android.qs.Tile3Service
|
||||
import io.homeassistant.companion.android.qs.Tile4Service
|
||||
import io.homeassistant.companion.android.qs.Tile5Service
|
||||
import io.homeassistant.companion.android.qs.Tile6Service
|
||||
import io.homeassistant.companion.android.qs.Tile7Service
|
||||
import io.homeassistant.companion.android.qs.Tile8Service
|
||||
import io.homeassistant.companion.android.qs.Tile9Service
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@HiltViewModel
|
||||
class ManageTilesViewModel @Inject constructor(
|
||||
state: SavedStateHandle,
|
||||
private val integrationUseCase: IntegrationRepository,
|
||||
private val tileDao: TileDao,
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ManageTilesViewModel"
|
||||
}
|
||||
|
||||
lateinit var iconPack: IconPack
|
||||
|
||||
private val app = application
|
||||
|
||||
val slots = loadTileSlots(application.resources)
|
||||
|
||||
var selectedTile by mutableStateOf(slots[0])
|
||||
|
@ -41,19 +70,32 @@ class ManageTilesViewModel @Inject constructor(
|
|||
|
||||
var sortedEntities by mutableStateOf<List<Entity<*>>>(emptyList())
|
||||
private set
|
||||
var selectedIcon by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
var selectedIconDrawable by mutableStateOf(AppCompatResources.getDrawable(application, R.drawable.ic_stat_ic_notification))
|
||||
private set
|
||||
var selectedTileId by mutableStateOf(0)
|
||||
var selectedIconDrawable by mutableStateOf(AppCompatResources.getDrawable(application, commonR.drawable.ic_stat_ic_notification))
|
||||
private set
|
||||
var selectedEntityId by mutableStateOf("")
|
||||
var tileLabel by mutableStateOf("")
|
||||
var tileSubtitle by mutableStateOf<String?>(null)
|
||||
var submitButtonLabel by mutableStateOf(commonR.string.tile_save)
|
||||
private set
|
||||
|
||||
private var selectedIcon: Int? = null
|
||||
private var selectedTileId = 0
|
||||
private var selectedTileAdded = false
|
||||
|
||||
private val _tileInfoSnackbar = MutableSharedFlow<Int>(replay = 1)
|
||||
var tileInfoSnackbar = _tileInfoSnackbar.asSharedFlow()
|
||||
|
||||
init {
|
||||
// Initialize fields based on the tile_1 TileEntity
|
||||
selectTile(0)
|
||||
state.get<String>("id")?.let { id ->
|
||||
selectTile(slots.indexOfFirst { it.id == id })
|
||||
viewModelScope.launch {
|
||||
// A deeplink only happens when tapping on a tile that hasn't been setup
|
||||
_tileInfoSnackbar.emit(commonR.string.tile_data_missing)
|
||||
}
|
||||
} ?: run {
|
||||
selectTile(0)
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
sortedEntities = integrationUseCase.getEntities().orEmpty()
|
||||
|
@ -72,12 +114,18 @@ class ManageTilesViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun selectTile(index: Int) {
|
||||
val tile = slots[index]
|
||||
val tile = slots[if (index == -1) 0 else index]
|
||||
selectedTile = tile
|
||||
viewModelScope.launch {
|
||||
tileDao.get(tile.id).also {
|
||||
selectedTileId = it?.id ?: 0
|
||||
it?.let { updateExistingTileFields(it) }
|
||||
selectedTileAdded = it?.added ?: false
|
||||
submitButtonLabel =
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || it?.added == true) commonR.string.tile_save
|
||||
else commonR.string.tile_add
|
||||
if (it?.isSetup == true) {
|
||||
updateExistingTileFields(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,10 +147,63 @@ class ManageTilesViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun addTile(tileData: TileEntity) {
|
||||
fun addTile() {
|
||||
viewModelScope.launch {
|
||||
val tileData = TileEntity(
|
||||
id = selectedTileId,
|
||||
tileId = selectedTile.id,
|
||||
added = selectedTileAdded,
|
||||
iconId = selectedIcon,
|
||||
entityId = selectedEntityId,
|
||||
label = tileLabel,
|
||||
subtitle = tileSubtitle
|
||||
)
|
||||
tileDao.add(tileData)
|
||||
Toast.makeText(getApplication(), R.string.tile_updated, Toast.LENGTH_SHORT).show()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !selectedTileAdded) {
|
||||
val statusBarManager = app.getSystemService<StatusBarManager>()
|
||||
val service = when (selectedTile.id) {
|
||||
Tile2Service.TILE_ID -> Tile2Service::class.java
|
||||
Tile3Service.TILE_ID -> Tile3Service::class.java
|
||||
Tile4Service.TILE_ID -> Tile4Service::class.java
|
||||
Tile5Service.TILE_ID -> Tile5Service::class.java
|
||||
Tile6Service.TILE_ID -> Tile6Service::class.java
|
||||
Tile7Service.TILE_ID -> Tile7Service::class.java
|
||||
Tile8Service.TILE_ID -> Tile8Service::class.java
|
||||
Tile9Service.TILE_ID -> Tile9Service::class.java
|
||||
Tile10Service.TILE_ID -> Tile10Service::class.java
|
||||
Tile11Service.TILE_ID -> Tile11Service::class.java
|
||||
Tile12Service.TILE_ID -> Tile12Service::class.java
|
||||
else -> Tile1Service::class.java
|
||||
}
|
||||
val icon = selectedIconDrawable?.let {
|
||||
it.toBitmapOrNull(it.intrinsicWidth, it.intrinsicHeight)?.let { bitmap ->
|
||||
android.graphics.drawable.Icon.createWithBitmap(bitmap)
|
||||
}
|
||||
} ?: android.graphics.drawable.Icon.createWithResource(app, commonR.drawable.ic_stat_ic_notification)
|
||||
|
||||
statusBarManager?.requestAddTileService(
|
||||
ComponentName(app, service),
|
||||
tileLabel,
|
||||
icon,
|
||||
Executors.newSingleThreadExecutor()
|
||||
) { result ->
|
||||
viewModelScope.launch {
|
||||
Log.d(TAG, "Adding quick settings tile, system returned: $result")
|
||||
if (result == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED ||
|
||||
result == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
|
||||
) {
|
||||
_tileInfoSnackbar.emit(commonR.string.tile_added)
|
||||
selectedTileAdded = true
|
||||
submitButtonLabel = commonR.string.tile_save
|
||||
} else { // Silently ignore error, database was still updated
|
||||
_tileInfoSnackbar.emit(commonR.string.tile_updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_tileInfoSnackbar.emit(commonR.string.tile_updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.homeassistant.companion.android.settings.qs.views
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -13,9 +14,12 @@ import androidx.compose.material.Divider
|
|||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.OutlinedButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
@ -24,124 +28,138 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.database.qs.TileEntity
|
||||
import io.homeassistant.companion.android.settings.qs.ManageTilesViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@Composable
|
||||
fun ManageTilesView(
|
||||
viewModel: ManageTilesViewModel,
|
||||
onShowIconDialog: (tag: String?) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scrollState = rememberScrollState()
|
||||
var expandedTile by remember { mutableStateOf(false) }
|
||||
var expandedEntity by remember { mutableStateOf(false) }
|
||||
|
||||
Box(modifier = Modifier.verticalScroll(scrollState)) {
|
||||
Column(modifier = Modifier.padding(all = 16.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(R.string.tile_select),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(end = 10.dp)
|
||||
)
|
||||
Box {
|
||||
OutlinedButton(onClick = { expandedTile = true }) {
|
||||
Text(viewModel.selectedTile.name)
|
||||
}
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
LaunchedEffect("snackbar") {
|
||||
viewModel.tileInfoSnackbar.onEach {
|
||||
if (it != 0) {
|
||||
scaffoldState.snackbarHostState.showSnackbar(context.getString(it))
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
DropdownMenu(expanded = expandedTile, onDismissRequest = { expandedTile = false }) {
|
||||
for ((index, slot) in viewModel.slots.withIndex()) {
|
||||
DropdownMenuItem(onClick = {
|
||||
viewModel.selectTile(index)
|
||||
expandedTile = false
|
||||
}) {
|
||||
Text(slot.name)
|
||||
Scaffold(scaffoldState = scaffoldState) { contentPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(all = 16.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(R.string.tile_select),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(end = 10.dp)
|
||||
)
|
||||
Box {
|
||||
OutlinedButton(onClick = { expandedTile = true }) {
|
||||
Text(viewModel.selectedTile.name)
|
||||
}
|
||||
|
||||
DropdownMenu(expanded = expandedTile, onDismissRequest = { expandedTile = false }) {
|
||||
for ((index, slot) in viewModel.slots.withIndex()) {
|
||||
DropdownMenuItem(onClick = {
|
||||
viewModel.selectTile(index)
|
||||
expandedTile = false
|
||||
}) {
|
||||
Text(slot.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
TextField(
|
||||
value = viewModel.tileLabel,
|
||||
onValueChange = { viewModel.tileLabel = it },
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.tile_label))
|
||||
},
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth()
|
||||
)
|
||||
|
||||
TextField(
|
||||
value = viewModel.tileSubtitle.orEmpty(),
|
||||
onValueChange = { viewModel.tileSubtitle = it },
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.tile_subtitle))
|
||||
},
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth()
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tile_icon),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(end = 10.dp)
|
||||
Divider()
|
||||
TextField(
|
||||
value = viewModel.tileLabel,
|
||||
onValueChange = { viewModel.tileLabel = it },
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.tile_label))
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
OutlinedButton(
|
||||
onClick = { onShowIconDialog(viewModel.selectedTile.id) }
|
||||
) {
|
||||
val iconBitmap = remember(viewModel.selectedIconDrawable) {
|
||||
viewModel.selectedIconDrawable?.toBitmap()?.asImageBitmap()
|
||||
}
|
||||
iconBitmap?.let {
|
||||
Image(
|
||||
iconBitmap,
|
||||
contentDescription = stringResource(id = R.string.tile_icon),
|
||||
colorFilter = ColorFilter.tint(colorResource(R.color.colorAccent))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.tile_entity),
|
||||
fontSize = 15.sp
|
||||
)
|
||||
OutlinedButton(onClick = { expandedEntity = true }) {
|
||||
Text(text = viewModel.selectedEntityId)
|
||||
}
|
||||
|
||||
DropdownMenu(expanded = expandedEntity, onDismissRequest = { expandedEntity = false }) {
|
||||
for (item in viewModel.sortedEntities) {
|
||||
DropdownMenuItem(onClick = {
|
||||
viewModel.selectedEntityId = item.entityId
|
||||
expandedEntity = false
|
||||
}) {
|
||||
Text(text = item.entityId, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
val tileData = TileEntity(
|
||||
id = viewModel.selectedTileId,
|
||||
tileId = viewModel.selectedTile.id,
|
||||
iconId = viewModel.selectedIcon,
|
||||
entityId = viewModel.selectedEntityId,
|
||||
label = viewModel.tileLabel,
|
||||
subtitle = viewModel.tileSubtitle
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
TextField(
|
||||
value = viewModel.tileSubtitle.orEmpty(),
|
||||
onValueChange = { viewModel.tileSubtitle = it },
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.tile_subtitle))
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
viewModel.addTile(tileData)
|
||||
},
|
||||
enabled = viewModel.tileLabel.isNotEmpty() && viewModel.selectedEntityId.isNotEmpty()
|
||||
) {
|
||||
Text(stringResource(id = R.string.tile_save))
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tile_icon),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(end = 10.dp)
|
||||
)
|
||||
OutlinedButton(
|
||||
onClick = { onShowIconDialog(viewModel.selectedTile.id) }
|
||||
) {
|
||||
val iconBitmap = remember(viewModel.selectedIconDrawable) {
|
||||
viewModel.selectedIconDrawable?.toBitmap()?.asImageBitmap()
|
||||
}
|
||||
iconBitmap?.let {
|
||||
Image(
|
||||
iconBitmap,
|
||||
contentDescription = stringResource(id = R.string.tile_icon),
|
||||
colorFilter = ColorFilter.tint(colorResource(R.color.colorAccent))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.tile_entity),
|
||||
fontSize = 15.sp
|
||||
)
|
||||
OutlinedButton(onClick = { expandedEntity = true }) {
|
||||
Text(text = viewModel.selectedEntityId)
|
||||
}
|
||||
|
||||
DropdownMenu(expanded = expandedEntity, onDismissRequest = { expandedEntity = false }) {
|
||||
for (item in viewModel.sortedEntities) {
|
||||
DropdownMenuItem(onClick = {
|
||||
viewModel.selectedEntityId = item.entityId
|
||||
expandedEntity = false
|
||||
}) {
|
||||
Text(text = item.entityId, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = { viewModel.addTile() },
|
||||
enabled = viewModel.tileLabel.isNotBlank() && viewModel.selectedEntityId.isNotBlank()
|
||||
) {
|
||||
Text(stringResource(viewModel.submitButtonLabel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,727 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 33,
|
||||
"identityHash": "5eab11aa0967d84bc4c14bdf053eb574",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "sensor_attributes",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "sensorId",
|
||||
"columnName": "sensor_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "valueType",
|
||||
"columnName": "value_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"sensor_id",
|
||||
"name"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Authentication_List",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `Username` TEXT NOT NULL, `Password` TEXT NOT NULL, PRIMARY KEY(`host`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "Username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "Password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"host"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sensors",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `registered` INTEGER DEFAULT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT DEFAULT NULL, `last_sent_icon` TEXT DEFAULT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, `state_class` TEXT, `entity_category` TEXT, `core_registration` TEXT, `app_registration` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enabled",
|
||||
"columnName": "enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "registered",
|
||||
"columnName": "registered",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "NULL"
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastSentState",
|
||||
"columnName": "last_sent_state",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "NULL"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastSentIcon",
|
||||
"columnName": "last_sent_icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "NULL"
|
||||
},
|
||||
{
|
||||
"fieldPath": "stateType",
|
||||
"columnName": "state_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceClass",
|
||||
"columnName": "device_class",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "unitOfMeasurement",
|
||||
"columnName": "unit_of_measurement",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "stateClass",
|
||||
"columnName": "state_class",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityCategory",
|
||||
"columnName": "entity_category",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coreRegistration",
|
||||
"columnName": "core_registration",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "appRegistration",
|
||||
"columnName": "app_registration",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sensor_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `entries` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "sensorId",
|
||||
"columnName": "sensor_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "valueType",
|
||||
"columnName": "value_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enabled",
|
||||
"columnName": "enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entries",
|
||||
"columnName": "entries",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"sensor_id",
|
||||
"name"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "button_widgets",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `icon_id` INTEGER NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, `require_authentication` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconId",
|
||||
"columnName": "icon_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domain",
|
||||
"columnName": "domain",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "service",
|
||||
"columnName": "service",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serviceData",
|
||||
"columnName": "service_data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "backgroundType",
|
||||
"columnName": "background_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'DAYNIGHT'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "textColor",
|
||||
"columnName": "text_color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "requireAuthentication",
|
||||
"columnName": "require_authentication",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "camera_widgets",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityId",
|
||||
"columnName": "entityId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "mediaplayctrls_widgets",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, `label` TEXT, `showSkip` INTEGER NOT NULL, `showSeek` INTEGER NOT NULL, `showVolume` INTEGER NOT NULL, `showSource` INTEGER NOT NULL DEFAULT false, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityId",
|
||||
"columnName": "entityId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "showSkip",
|
||||
"columnName": "showSkip",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "showSeek",
|
||||
"columnName": "showSeek",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "showVolume",
|
||||
"columnName": "showVolume",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "showSource",
|
||||
"columnName": "showSource",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "backgroundType",
|
||||
"columnName": "background_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'DAYNIGHT'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "textColor",
|
||||
"columnName": "text_color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "static_widget",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, `attribute_ids` TEXT, `label` TEXT, `text_size` REAL NOT NULL, `state_separator` TEXT NOT NULL, `attribute_separator` TEXT NOT NULL, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityId",
|
||||
"columnName": "entity_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "attributeIds",
|
||||
"columnName": "attribute_ids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "textSize",
|
||||
"columnName": "text_size",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "stateSeparator",
|
||||
"columnName": "state_separator",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "attributeSeparator",
|
||||
"columnName": "attribute_separator",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdate",
|
||||
"columnName": "last_update",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "backgroundType",
|
||||
"columnName": "background_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'DAYNIGHT'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "textColor",
|
||||
"columnName": "text_color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "template_widgets",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `template` TEXT NOT NULL, `text_size` REAL NOT NULL DEFAULT 12.0, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "template",
|
||||
"columnName": "template",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "textSize",
|
||||
"columnName": "text_size",
|
||||
"affinity": "REAL",
|
||||
"notNull": true,
|
||||
"defaultValue": "12.0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdate",
|
||||
"columnName": "last_update",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "backgroundType",
|
||||
"columnName": "background_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'DAYNIGHT'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "textColor",
|
||||
"columnName": "text_color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "notification_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, `source` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "received",
|
||||
"columnName": "received",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "source",
|
||||
"columnName": "source",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "qs_tiles",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tileId` TEXT NOT NULL, `added` INTEGER NOT NULL DEFAULT 1, `icon_id` INTEGER, `entityId` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tileId",
|
||||
"columnName": "tileId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "added",
|
||||
"columnName": "added",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconId",
|
||||
"columnName": "icon_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityId",
|
||||
"columnName": "entityId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtitle",
|
||||
"columnName": "subtitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "favorites",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "entityStateComplications",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entityId` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entityId",
|
||||
"columnName": "entityId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocketSetting` TEXT NOT NULL, `sensorUpdateFrequency` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "websocketSetting",
|
||||
"columnName": "websocketSetting",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sensorUpdateFrequency",
|
||||
"columnName": "sensorUpdateFrequency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5eab11aa0967d84bc4c14bdf053eb574')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ import io.homeassistant.companion.android.common.R as commonR
|
|||
EntityStateComplications::class,
|
||||
Setting::class
|
||||
],
|
||||
version = 32,
|
||||
version = 33,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 24, to = 25),
|
||||
AutoMigration(from = 25, to = 26),
|
||||
|
@ -84,6 +84,7 @@ import io.homeassistant.companion.android.common.R as commonR
|
|||
AutoMigration(from = 29, to = 30),
|
||||
AutoMigration(from = 30, to = 31),
|
||||
AutoMigration(from = 31, to = 32),
|
||||
AutoMigration(from = 32, to = 33),
|
||||
]
|
||||
)
|
||||
@TypeConverters(
|
||||
|
|
|
@ -10,6 +10,8 @@ data class TileEntity(
|
|||
val id: Int = 0,
|
||||
@ColumnInfo(name = "tileId")
|
||||
val tileId: String,
|
||||
@ColumnInfo(name = "added", defaultValue = "1")
|
||||
val added: Boolean,
|
||||
@ColumnInfo(name = "icon_id")
|
||||
val iconId: Int?,
|
||||
@ColumnInfo(name = "entityId")
|
||||
|
@ -19,3 +21,6 @@ data class TileEntity(
|
|||
@ColumnInfo(name = "subtitle")
|
||||
val subtitle: String?
|
||||
)
|
||||
|
||||
val TileEntity.isSetup: Boolean
|
||||
get() = this.label.isNotBlank() && this.entityId.isNotBlank()
|
||||
|
|
|
@ -735,16 +735,19 @@
|
|||
<string name="tile_7">Tile 7</string>
|
||||
<string name="tile_8">Tile 8</string>
|
||||
<string name="tile_9">Tile 9</string>
|
||||
<string name="tile_data_missing">Tile data is missing, please set it up in App Configuration</string>
|
||||
<string name="tile_add">Add Tile</string>
|
||||
<string name="tile_added">Tile added</string>
|
||||
<string name="tile_data_missing">You need to set up the tile before using it</string>
|
||||
<string name="tile_entity">Select a entity to toggle or call (required)</string>
|
||||
<string name="tile_label">Tile Label (required)</string>
|
||||
<string name="tile_list">List of Tiles</string>
|
||||
<string name="tile_missing_entity_summary">You must have one of the following domains in order to use this feature: cover, fan, input_boolean, light, remote, scene, script, switch</string>
|
||||
<string name="tile_missing_entity_title">Missing Valid Entity Domains</string>
|
||||
<string name="tile_not_setup">Requires setup</string>
|
||||
<string name="tile_save">Update Tile Data</string>
|
||||
<string name="tile_settings">Tile settings</string>
|
||||
<string name="tile_subtitle">Tile Subtitle</string>
|
||||
<string name="tile_updated">Tile Data Updated</string>
|
||||
<string name="tile_updated">Tile Data updated</string>
|
||||
<string name="tiles">Tiles</string>
|
||||
<string name="toast_message">%1$s was selected</string>
|
||||
<string name="tts_error">Unable to process notification \"%1$s\" as text to speech.</string>
|
||||
|
|
Loading…
Reference in a new issue