mirror of
https://github.com/home-assistant/android
synced 2024-10-02 22:34:46 +00:00
Allow favorite entities to be dragged and dropped for reordering from phone settings (#1963)
* Allow favorite entities to be dragged and dropped for reordering * Switch favorites to store in DB, fix reordering * UI updates to reflect draggable items * Send a json array instead of just a string, add lock domain to settings * Only add favorites if we have them from the phone * Remove missing entities, fix initial duplicate load * Remove redundant work manager bump from phone * Fix flow of favorites and remaining issues * Build fixes * Switch back checked variable
This commit is contained in:
parent
ab6cf02671
commit
f800c85b52
|
@ -146,9 +146,9 @@ dependencies {
|
|||
implementation("com.google.dagger:hilt-android:2.40.2")
|
||||
kapt("com.google.dagger:hilt-android-compiler:2.40.2")
|
||||
|
||||
implementation("androidx.appcompat:appcompat:1.3.1")
|
||||
implementation("androidx.appcompat:appcompat:1.4.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.2")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
|
||||
|
@ -192,7 +192,9 @@ dependencies {
|
|||
implementation("com.google.accompanist:accompanist-appcompat-theme:0.20.2")
|
||||
|
||||
implementation("com.mikepenz:iconics-core:5.3.3")
|
||||
implementation("com.mikepenz:iconics-compose:5.3.3")
|
||||
implementation("com.mikepenz:community-material-typeface:6.4.95.0-kotlin@aar")
|
||||
implementation("org.burnoutcrew.composereorderable:reorderable:0.7.4")
|
||||
}
|
||||
|
||||
// Disable to fix memory leak and be compatible with the configuration cache.
|
||||
|
|
|
@ -12,7 +12,6 @@ import com.google.android.gms.wearable.CapabilityClient
|
|||
import com.google.android.gms.wearable.DataClient
|
||||
import com.google.android.gms.wearable.DataEvent
|
||||
import com.google.android.gms.wearable.DataEventBuffer
|
||||
import com.google.android.gms.wearable.DataItem
|
||||
import com.google.android.gms.wearable.DataMap
|
||||
import com.google.android.gms.wearable.DataMapItem
|
||||
import com.google.android.gms.wearable.PutDataMapRequest
|
||||
|
@ -22,6 +21,9 @@ import io.homeassistant.companion.android.HomeAssistantApplication
|
|||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import org.burnoutcrew.reorderable.move
|
||||
import org.json.JSONArray
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
|
@ -35,8 +37,8 @@ class SettingsWearViewModel @Inject constructor(
|
|||
|
||||
private val application = getApplication<HomeAssistantApplication>()
|
||||
companion object {
|
||||
private val TAG = "SettingsWearViewModel"
|
||||
private val CAPABILITY_WEAR_FAVORITES = "send_home_favorites"
|
||||
private const val TAG = "SettingsWearViewModel"
|
||||
private const val CAPABILITY_WEAR_FAVORITES = "send_home_favorites"
|
||||
}
|
||||
|
||||
var entities = mutableStateMapOf<String, Entity<*>>()
|
||||
|
@ -55,12 +57,15 @@ class SettingsWearViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun saveHomeFavorites(data: String, item: DataItem) {
|
||||
getFavorites(DataMapItem.fromDataItem(item).dataMap)
|
||||
private fun saveHomeFavorites(data: String) {
|
||||
val jsonString = JSONArray(data)
|
||||
Log.d(TAG, "saveHomeFavorites: $jsonString")
|
||||
favoriteEntityIds.clear()
|
||||
favoriteEntityIds.addAll(
|
||||
data.removeSurrounding("[", "]").split(", ").map { it }
|
||||
)
|
||||
for (favorite in 0 until jsonString.length()) {
|
||||
favoriteEntityIds.add(
|
||||
jsonString.getString(favorite)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onEntitySelected(checked: Boolean, entityId: String) {
|
||||
|
@ -71,7 +76,13 @@ class SettingsWearViewModel @Inject constructor(
|
|||
sendHomeFavorites(favoriteEntityIds.toList())
|
||||
}
|
||||
|
||||
private fun sendHomeFavorites(favoritesList: List<String>) = viewModelScope.launch {
|
||||
fun onMove(fromItem: ItemPosition, toItem: ItemPosition) {
|
||||
favoriteEntityIds.move(favoriteEntityIds.indexOfFirst { it == fromItem.key }, favoriteEntityIds.indexOfFirst { it == toItem.key })
|
||||
}
|
||||
|
||||
fun canDragOver(position: ItemPosition) = favoriteEntityIds.any { it == position.key }
|
||||
|
||||
fun sendHomeFavorites(favoritesList: List<String>) = viewModelScope.launch {
|
||||
Log.d(TAG, "sendHomeFavorites")
|
||||
|
||||
val putDataRequest = PutDataMapRequest.create("/save_home_favorites").run {
|
||||
|
@ -97,12 +108,9 @@ class SettingsWearViewModel @Inject constructor(
|
|||
Log.d(TAG, "Found existing favorites: ${dataItemBuffer.count}")
|
||||
dataItemBuffer.forEach {
|
||||
val data = getFavorites(DataMapItem.fromDataItem(it).dataMap)
|
||||
Log.d(TAG, "Favorites: $data")
|
||||
favoriteEntityIds.clear()
|
||||
favoriteEntityIds.addAll(
|
||||
data.removeSurrounding("[", "]").split(", ").map { it }
|
||||
)
|
||||
saveHomeFavorites(data)
|
||||
}
|
||||
dataItemBuffer.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,11 +164,11 @@ class SettingsWearViewModel @Inject constructor(
|
|||
event.dataItem.also { item ->
|
||||
if (item.uri.path?.compareTo("/home_favorites") == 0) {
|
||||
val data = getFavorites(DataMapItem.fromDataItem(item).dataMap)
|
||||
saveHomeFavorites(data, item)
|
||||
Log.d(TAG, "onDataChanged: Found home favorites: $data")
|
||||
saveHomeFavorites(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dataEvents.release()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ package io.homeassistant.companion.android.settings.views
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
|
@ -17,16 +19,22 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.filled.HelpOutline
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import io.homeassistant.companion.android.settings.SettingsWearViewModel
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import org.burnoutcrew.reorderable.draggedItem
|
||||
import org.burnoutcrew.reorderable.rememberReorderState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
const val WEAR_DOCS_LINK = "https://companion.home-assistant.io/docs/wear-os/wear-os"
|
||||
val supportedDomains = listOf(
|
||||
"input_boolean", "light", "switch", "script", "scene"
|
||||
"input_boolean", "light", "lock", "switch", "script", "scene"
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -34,6 +42,7 @@ fun LoadWearFavoritesSettings(
|
|||
settingsWearViewModel: SettingsWearViewModel
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val reorderState = rememberReorderState()
|
||||
|
||||
val validEntities = settingsWearViewModel.entities.filter { it.key.split(".")[0] in supportedDomains }.values.sortedBy { it.entityId }.toList()
|
||||
val favoriteEntities = settingsWearViewModel.favoriteEntityIds
|
||||
|
@ -56,8 +65,20 @@ fun LoadWearFavoritesSettings(
|
|||
}
|
||||
) {
|
||||
LazyColumn(
|
||||
state = reorderState.listState,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(top = 10.dp, start = 20.dp, end = 20.dp)
|
||||
modifier = Modifier
|
||||
.padding(top = 10.dp, start = 5.dp, end = 10.dp)
|
||||
.then(
|
||||
Modifier.reorderable(
|
||||
reorderState,
|
||||
{ from, to -> settingsWearViewModel.onMove(from, to) },
|
||||
canDragOver = { settingsWearViewModel.canDragOver(it) },
|
||||
onDragEnd = { _, _ ->
|
||||
settingsWearViewModel.sendHomeFavorites(settingsWearViewModel.favoriteEntityIds.toList())
|
||||
}
|
||||
)
|
||||
)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
|
@ -65,17 +86,24 @@ fun LoadWearFavoritesSettings(
|
|||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
items(favoriteEntities.size) { index ->
|
||||
items(favoriteEntities.size, { favoriteEntities[it] }) { index ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(15.dp)
|
||||
.padding(12.dp)
|
||||
.clickable {
|
||||
settingsWearViewModel.onEntitySelected(
|
||||
false,
|
||||
favoriteEntities[index]
|
||||
)
|
||||
}
|
||||
.draggedItem(
|
||||
reorderState.offsetByKey(favoriteEntities[index]),
|
||||
Orientation.Vertical
|
||||
)
|
||||
.detectReorderAfterLongPress(reorderState)
|
||||
) {
|
||||
val iconBitmap = IconicsDrawable(LocalContext.current, "cmd-drag_vertical").toBitmap().asImageBitmap()
|
||||
Icon(iconBitmap, "", modifier = Modifier.padding(top = 13.dp))
|
||||
Checkbox(
|
||||
checked = favoriteEntities.contains(favoriteEntities[index]),
|
||||
onCheckedChange = {
|
||||
|
@ -89,6 +117,9 @@ fun LoadWearFavoritesSettings(
|
|||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Divider()
|
||||
}
|
||||
if (!validEntities.isNullOrEmpty()) {
|
||||
items(validEntities.size) { index ->
|
||||
val item = validEntities[index]
|
||||
|
|
|
@ -59,7 +59,7 @@ dependencies {
|
|||
api("androidx.room:room-ktx:2.3.0")
|
||||
kapt("androidx.room:room-compiler:2.3.0")
|
||||
|
||||
api("androidx.work:work-runtime-ktx:2.7.0")
|
||||
api("androidx.work:work-runtime-ktx:2.7.1")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-jackson:2.9.0")
|
||||
|
|
|
@ -33,8 +33,6 @@ interface IntegrationRepository {
|
|||
suspend fun setSessionExpireMillis(value: Long)
|
||||
suspend fun getSessionExpireMillis(): Long
|
||||
|
||||
suspend fun setWearHomeFavorites(favorites: Set<String>)
|
||||
suspend fun getWearHomeFavorites(): Set<String>
|
||||
suspend fun getTileShortcuts(): List<String>
|
||||
suspend fun setTileShortcuts(entities: List<String>)
|
||||
suspend fun setWearHapticFeedback(enabled: Boolean)
|
||||
|
|
|
@ -59,7 +59,6 @@ class IntegrationRepositoryImpl @Inject constructor(
|
|||
private const val PREF_SECRET = "secret"
|
||||
|
||||
private const val PREF_CHECK_SENSOR_REGISTRATION_NEXT = "sensor_reg_last"
|
||||
private const val PREF_WEAR_HOME_FAVORITES = "wear_home_favorites"
|
||||
private const val PREF_TILE_SHORTCUTS = "tile_shortcuts_list"
|
||||
private const val PREF_WEAR_HAPTIC_FEEDBACK = "wear_haptic_feedback"
|
||||
private const val PREF_WEAR_TOAST_CONFIRMATION = "wear_toast_confirmation"
|
||||
|
@ -350,14 +349,6 @@ class IntegrationRepositoryImpl @Inject constructor(
|
|||
return localStorage.getLong(PREF_SESSION_EXPIRE) ?: 0
|
||||
}
|
||||
|
||||
override suspend fun setWearHomeFavorites(favorites: Set<String>) {
|
||||
localStorage.putStringSet(PREF_WEAR_HOME_FAVORITES, favorites)
|
||||
}
|
||||
|
||||
override suspend fun getWearHomeFavorites(): Set<String> {
|
||||
return localStorage.getStringSet(PREF_WEAR_HOME_FAVORITES) ?: setOf()
|
||||
}
|
||||
|
||||
override suspend fun getTileShortcuts(): List<String> {
|
||||
val jsonArray = JSONArray(localStorage.getString(PREF_TILE_SHORTCUTS) ?: "[]")
|
||||
return List(jsonArray.length()) {
|
||||
|
|
|
@ -30,6 +30,8 @@ import io.homeassistant.companion.android.database.sensor.EntriesTypeConverter
|
|||
import io.homeassistant.companion.android.database.sensor.Sensor
|
||||
import io.homeassistant.companion.android.database.sensor.SensorDao
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import io.homeassistant.companion.android.database.wear.Favorites
|
||||
import io.homeassistant.companion.android.database.wear.FavoritesDao
|
||||
import io.homeassistant.companion.android.database.widget.ButtonWidgetDao
|
||||
import io.homeassistant.companion.android.database.widget.ButtonWidgetEntity
|
||||
import io.homeassistant.companion.android.database.widget.CameraWidgetDao
|
||||
|
@ -55,9 +57,10 @@ import io.homeassistant.companion.android.common.R as commonR
|
|||
StaticWidgetEntity::class,
|
||||
TemplateWidgetEntity::class,
|
||||
NotificationItem::class,
|
||||
TileEntity::class
|
||||
TileEntity::class,
|
||||
Favorites::class
|
||||
],
|
||||
version = 19,
|
||||
version = 20,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(EntriesTypeConverter::class)
|
||||
|
@ -71,6 +74,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
abstract fun templateWidgetDao(): TemplateWidgetDao
|
||||
abstract fun notificationDao(): NotificationDao
|
||||
abstract fun tileDao(): TileDao
|
||||
abstract fun favoritesDao(): FavoritesDao
|
||||
|
||||
companion object {
|
||||
private const val DATABASE_NAME = "HomeAssistantDB"
|
||||
|
@ -113,7 +117,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
MIGRATION_15_16,
|
||||
MIGRATION_16_17,
|
||||
MIGRATION_17_18,
|
||||
MIGRATION_18_19
|
||||
MIGRATION_18_19,
|
||||
MIGRATION_19_20
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
|
@ -442,6 +447,12 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_19_20 = object : Migration(19, 20) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `favorites` (`id` TEXT PRIMARY KEY NOT NULL, `position` INTEGER)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package io.homeassistant.companion.android.database.wear
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "favorites")
|
||||
data class Favorites(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "id")
|
||||
var id: String,
|
||||
@ColumnInfo(name = "position")
|
||||
var position: Int?
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
package io.homeassistant.companion.android.database.wear
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface FavoritesDao {
|
||||
|
||||
@Query("SELECT * FROM favorites where id = :id")
|
||||
fun get(id: String): Favorites?
|
||||
|
||||
@Query("SELECT * FROM favorites ORDER BY position ASC")
|
||||
fun getAllFlow(): Flow<List<Favorites>>?
|
||||
|
||||
@Query("SELECT * FROM favorites ORDER BY position ASC")
|
||||
fun getAll(): List<Favorites>?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun add(favorite: Favorites)
|
||||
|
||||
@Update
|
||||
fun update(favorite: Favorites)
|
||||
|
||||
@Query("DELETE FROM favorites where id = :id")
|
||||
fun delete(id: String)
|
||||
|
||||
@Query("DELETE FROM favorites")
|
||||
fun deleteAll()
|
||||
}
|
|
@ -644,7 +644,7 @@
|
|||
<string name="wear_os_category">Wear OS</string>
|
||||
<string name="wear_os_settings_summary">Manage Wear OS App</string>
|
||||
<string name="wear_os_settings_title">Wear OS Settings</string>
|
||||
<string name="wear_set_favorites">Select your favorite entities to appear at the top of the wear home screen</string>
|
||||
<string name="wear_set_favorites">Select your favorite entities to appear at the top of the wear home screen. You can also drag and drop to change the order in which they appear.</string>
|
||||
<string name="wear_settings">Wear Device Settings</string>
|
||||
<string name="webview_error_AUTH_SCHEME">Unsupported authentication scheme (not basic or digest), please check network settings.</string>
|
||||
<string name="webview_error_AUTHENTICATION">User authentication failed on server, please check server settings.</string>
|
||||
|
|
|
@ -48,7 +48,6 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
}
|
||||
|
||||
override fun onResume() {
|
||||
mainViewModel.updateFavorites()
|
||||
super.onResume()
|
||||
SensorWorker.start(this)
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ interface HomePresenter {
|
|||
|
||||
suspend fun getEntities(): List<Entity<*>>
|
||||
suspend fun getEntityUpdates(): Flow<Entity<*>>
|
||||
suspend fun getWearHomeFavorites(): List<String>
|
||||
suspend fun setWearHomeFavorites(favorites: List<String>)
|
||||
suspend fun getTileShortcuts(): List<SimplifiedEntity>
|
||||
suspend fun setTileShortcuts(entities: List<SimplifiedEntity>)
|
||||
|
||||
|
|
|
@ -117,14 +117,6 @@ class HomePresenterImpl @Inject constructor(
|
|||
return integrationUseCase.isRegistered()
|
||||
}
|
||||
|
||||
override suspend fun getWearHomeFavorites(): List<String> {
|
||||
return integrationUseCase.getWearHomeFavorites().toList()
|
||||
}
|
||||
|
||||
override suspend fun setWearHomeFavorites(favorites: List<String>) {
|
||||
integrationUseCase.setWearHomeFavorites(favorites.toSet())
|
||||
}
|
||||
|
||||
override suspend fun getTileShortcuts(): List<SimplifiedEntity> {
|
||||
return integrationUseCase.getTileShortcuts().map { SimplifiedEntity(it) }
|
||||
}
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
package io.homeassistant.companion.android.home
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.homeassistant.companion.android.HomeAssistantApplication
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.data.SimplifiedEntity
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.wear.Favorites
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor() : ViewModel() {
|
||||
class MainViewModel @Inject constructor(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private lateinit var homePresenter: HomePresenter
|
||||
val app = getApplication<HomeAssistantApplication>()
|
||||
private val favoritesDao = AppDatabase.getInstance(app.applicationContext).favoritesDao()
|
||||
|
||||
// TODO: This is bad, do this instead: https://stackoverflow.com/questions/46283981/android-viewmodel-additional-arguments
|
||||
fun init(homePresenter: HomePresenter) {
|
||||
this.homePresenter = homePresenter
|
||||
loadEntities()
|
||||
getFavorites()
|
||||
}
|
||||
|
||||
// entities
|
||||
|
@ -52,12 +60,13 @@ class MainViewModel @Inject constructor() : ViewModel() {
|
|||
var isToastEnabled = mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private fun favorites(): Flow<List<Favorites>>? = favoritesDao.getAllFlow()
|
||||
|
||||
private fun loadEntities() {
|
||||
viewModelScope.launch {
|
||||
if (!homePresenter.isConnected()) {
|
||||
return@launch
|
||||
}
|
||||
favoriteEntityIds.addAll(homePresenter.getWearHomeFavorites())
|
||||
shortcutEntities.addAll(homePresenter.getTileShortcuts())
|
||||
isHapticEnabled.value = homePresenter.getWearHapticFeedback()
|
||||
isToastEnabled.value = homePresenter.getWearToastConfirmation()
|
||||
|
@ -94,35 +103,20 @@ class MainViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun addFavorite(entityId: String) {
|
||||
|
||||
private fun getFavorites() {
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.add(entityId)
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFavorite(entity: String) {
|
||||
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.remove(entity)
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
favorites()?.collect {
|
||||
favoriteEntityIds.clear()
|
||||
for (favorite in it) {
|
||||
favoriteEntityIds.add(favorite.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearFavorites() {
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.clear()
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove the below as we should save favorites to the DB so we can use a proper flow like above
|
||||
fun updateFavorites() {
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.clear()
|
||||
favoriteEntityIds.addAll(homePresenter.getWearHomeFavorites())
|
||||
}
|
||||
favoriteEntityIds.clear()
|
||||
favoritesDao.deleteAll()
|
||||
}
|
||||
|
||||
fun setTileShortcut(index: Int, entity: SimplifiedEntity) {
|
||||
|
@ -159,6 +153,32 @@ class MainViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun addFavorites(favorites: Favorites) {
|
||||
favoritesDao.add(favorites)
|
||||
updateFavoritePositions()
|
||||
}
|
||||
|
||||
private fun updateFavorites(favorites: Favorites) {
|
||||
favoritesDao.update(favorites)
|
||||
updateFavoritePositions()
|
||||
}
|
||||
|
||||
fun removeFavorites(id: String) {
|
||||
favoritesDao.delete(id)
|
||||
updateFavoritePositions()
|
||||
}
|
||||
|
||||
private fun updateFavoritePositions() {
|
||||
var i = 1
|
||||
viewModelScope.launch {
|
||||
favoritesDao.getAll()?.forEach { favorites ->
|
||||
if (i != i)
|
||||
updateFavorites(Favorites(favorites.id, i))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
homePresenter.onLogoutClicked()
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
@ -18,7 +17,6 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
|
@ -32,9 +30,6 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
import io.homeassistant.companion.android.data.SimplifiedEntity
|
||||
import io.homeassistant.companion.android.home.MainViewModel
|
||||
import io.homeassistant.companion.android.theme.WearAppTheme
|
||||
import io.homeassistant.companion.android.util.LocalRotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventHandlerSetup
|
||||
import io.homeassistant.companion.android.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
@ -238,19 +233,3 @@ private fun ChooseEntityChip(
|
|||
colors = ChipDefaults.secondaryChipColors()
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewChooseEntityView() {
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
CompositionLocalProvider(
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
ChooseEntityView(
|
||||
mainViewModel = MainViewModel(),
|
||||
onNoneClicked = { /*TODO*/ },
|
||||
onEntitySelected = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
|
@ -23,6 +22,7 @@ import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
|||
import androidx.wear.compose.navigation.composable
|
||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||
import androidx.wear.tiles.TileService
|
||||
import io.homeassistant.companion.android.database.wear.Favorites
|
||||
import io.homeassistant.companion.android.home.MainViewModel
|
||||
import io.homeassistant.companion.android.theme.WearAppTheme
|
||||
import io.homeassistant.companion.android.tiles.ShortcutsTile
|
||||
|
@ -88,7 +88,8 @@ fun LoadHomePage(
|
|||
swipeDismissableNavController.navigate(SCREEN_ENTITY_LIST)
|
||||
},
|
||||
mainViewModel.isHapticEnabled.value,
|
||||
mainViewModel.isToastEnabled.value
|
||||
mainViewModel.isToastEnabled.value,
|
||||
{ id -> mainViewModel.removeFavorites(id) }
|
||||
)
|
||||
}
|
||||
composable(SCREEN_ENTITY_LIST) {
|
||||
|
@ -119,11 +120,12 @@ fun LoadHomePage(
|
|||
SetFavoritesView(
|
||||
mainViewModel,
|
||||
mainViewModel.favoriteEntityIds
|
||||
) { entityId, isSelected ->
|
||||
) { entityId, position, isSelected ->
|
||||
val favorites = Favorites(entityId, position)
|
||||
if (isSelected) {
|
||||
mainViewModel.addFavorite(entityId)
|
||||
mainViewModel.addFavorites(favorites)
|
||||
} else {
|
||||
mainViewModel.removeFavorite(entityId)
|
||||
mainViewModel.removeFavorites(entityId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,11 +157,3 @@ fun LoadHomePage(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@ExperimentalWearMaterialApi
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewHomeView() {
|
||||
LoadHomePage(mainViewModel = MainViewModel())
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
@ -22,7 +21,6 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
|
@ -39,12 +37,10 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
import io.homeassistant.companion.android.home.MainViewModel
|
||||
import io.homeassistant.companion.android.theme.WearAppTheme
|
||||
import io.homeassistant.companion.android.theme.wearColorPalette
|
||||
import io.homeassistant.companion.android.util.LocalRotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.util.onEntityClickedFeedback
|
||||
import io.homeassistant.companion.android.util.previewFavoritesList
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
|
@ -57,7 +53,8 @@ fun MainView(
|
|||
onSettingsClicked: () -> Unit,
|
||||
onTestClicked: (entityLists: Map<Int, List<Entity<*>>>) -> Unit,
|
||||
isHapticEnabled: Boolean,
|
||||
isToastEnabled: Boolean
|
||||
isToastEnabled: Boolean,
|
||||
deleteFavorite: (String) -> Unit
|
||||
) {
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
|
||||
|
@ -125,12 +122,21 @@ fun MainView(
|
|||
colors = ChipDefaults.secondaryChipColors()
|
||||
)
|
||||
} else {
|
||||
EntityUi(
|
||||
mainViewModel.entities[favoriteEntityID]!!,
|
||||
onEntityClicked,
|
||||
isHapticEnabled,
|
||||
isToastEnabled
|
||||
)
|
||||
var isValidEntity = false
|
||||
for (entity in mainViewModel.entities) {
|
||||
if (entity.value.entityId == favoriteEntityID) {
|
||||
isValidEntity = true
|
||||
EntityUi(
|
||||
mainViewModel.entities[favoriteEntityID]!!,
|
||||
onEntityClicked,
|
||||
isHapticEnabled,
|
||||
isToastEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!isValidEntity) {
|
||||
deleteFavorite(favoriteEntityID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -337,25 +343,3 @@ fun MainView(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@ExperimentalWearMaterialApi
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewMainView() {
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
MainView(
|
||||
mainViewModel = MainViewModel(),
|
||||
favoriteEntityIds = previewFavoritesList,
|
||||
onEntityClicked = { _, _ -> },
|
||||
onSettingsClicked = {},
|
||||
onTestClicked = {},
|
||||
isHapticEnabled = true,
|
||||
isToastEnabled = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
@ -15,7 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.ExperimentalWearMaterialApi
|
||||
import androidx.wear.compose.material.PositionIndicator
|
||||
|
@ -29,16 +27,11 @@ import androidx.wear.compose.material.rememberScalingLazyListState
|
|||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.home.HomePresenterImpl
|
||||
import io.homeassistant.companion.android.home.MainViewModel
|
||||
import io.homeassistant.companion.android.theme.WearAppTheme
|
||||
import io.homeassistant.companion.android.theme.wearColorPalette
|
||||
import io.homeassistant.companion.android.util.LocalRotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventHandlerSetup
|
||||
import io.homeassistant.companion.android.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.util.previewFavoritesList
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
|
@ -47,7 +40,7 @@ import io.homeassistant.companion.android.common.R as commonR
|
|||
fun SetFavoritesView(
|
||||
mainViewModel: MainViewModel,
|
||||
favoriteEntityIds: List<String>,
|
||||
onFavoriteSelected: (entityId: String, isSelected: Boolean) -> Unit
|
||||
onFavoriteSelected: (entityId: String, entityPosition: Int, isSelected: Boolean) -> Unit
|
||||
) {
|
||||
var expandedInputBooleans: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedLights: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
|
@ -56,10 +49,6 @@ fun SetFavoritesView(
|
|||
var expandedScripts: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedSwitches: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val validEntities = mainViewModel.entities
|
||||
.filter { it.key.split(".")[0] in HomePresenterImpl.supportedDomains }
|
||||
val validEntityList = validEntities.values.toList().sortedBy { it.entityId }
|
||||
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
RotaryEventState(scrollState = scalingLazyListState)
|
||||
|
||||
|
@ -86,21 +75,6 @@ fun SetFavoritesView(
|
|||
item {
|
||||
ListHeader(id = commonR.string.set_favorite)
|
||||
}
|
||||
if (favoriteEntityIds.isNotEmpty()) {
|
||||
val favoriteEntities = mutableListOf<Entity<*>>()
|
||||
for (entity in validEntityList) {
|
||||
if (favoriteEntityIds.contains(entity.entityId))
|
||||
favoriteEntities += listOf(entity)
|
||||
}
|
||||
items(favoriteEntities.size) { index ->
|
||||
FavoriteToggleChip(
|
||||
entityList = favoriteEntities,
|
||||
index = index,
|
||||
favoriteEntityIds = favoriteEntityIds,
|
||||
onFavoriteSelected = onFavoriteSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
if (mainViewModel.inputBooleans.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
|
@ -230,7 +204,7 @@ private fun FavoriteToggleChip(
|
|||
entityList: List<Entity<*>>,
|
||||
index: Int,
|
||||
favoriteEntityIds: List<String>,
|
||||
onFavoriteSelected: (entityId: String, isSelected: Boolean) -> Unit
|
||||
onFavoriteSelected: (entityId: String, entityPosition: Int, isSelected: Boolean) -> Unit
|
||||
) {
|
||||
val attributes = entityList[index].attributes as Map<*, *>
|
||||
val iconBitmap = getIcon(
|
||||
|
@ -244,7 +218,7 @@ private fun FavoriteToggleChip(
|
|||
ToggleChip(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onFavoriteSelected(entityId, it)
|
||||
onFavoriteSelected(entityId, favoriteEntityIds.size + 1, it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
|
@ -264,21 +238,3 @@ private fun FavoriteToggleChip(
|
|||
toggleIcon = { ToggleChipDefaults.SwitchIcon(checked) }
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@ExperimentalWearMaterialApi
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewSetFavoriteView() {
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
CompositionLocalProvider(
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
SetFavoritesView(
|
||||
mainViewModel = MainViewModel(),
|
||||
favoriteEntityIds = previewFavoritesList,
|
||||
onFavoriteSelected = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,13 @@ import com.google.android.gms.wearable.Wearable
|
|||
import com.google.android.gms.wearable.WearableListenerService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.wear.Favorites
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONArray
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -67,10 +70,19 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
|
|||
|
||||
private fun sendHomeFavorites(nodeId: String) = mainScope.launch {
|
||||
Log.d(TAG, "sendHomeFavorites to: $nodeId")
|
||||
val currentFavorites = integrationUseCase.getWearHomeFavorites().toList()
|
||||
val currentFavorites = AppDatabase.getInstance(applicationContext).favoritesDao().getAll()
|
||||
val list = emptyList<String>().toMutableList()
|
||||
for (favorite in currentFavorites!!) {
|
||||
list += listOf(favorite.id)
|
||||
}
|
||||
val jsonArray = JSONArray(list.toString())
|
||||
val jsonString = List(jsonArray.length()) {
|
||||
jsonArray.getString(it)
|
||||
}.map { it }
|
||||
|
||||
Log.d(TAG, "new list: $jsonString")
|
||||
val putDataRequest = PutDataMapRequest.create("/home_favorites").run {
|
||||
dataMap.putString("favorites", currentFavorites.toString())
|
||||
dataMap.putString("favorites", jsonString.toString())
|
||||
setUrgent()
|
||||
asPutDataRequest()
|
||||
}
|
||||
|
@ -85,22 +97,25 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
|
|||
|
||||
private fun saveFavorites() {
|
||||
Log.d(TAG, "Finding existing favorites")
|
||||
val favoritesDao = AppDatabase.getInstance(applicationContext).favoritesDao()
|
||||
mainScope.launch {
|
||||
Wearable.getDataClient(applicationContext).getDataItems(Uri.parse("wear://*/save_home_favorites"))
|
||||
.addOnSuccessListener {
|
||||
Log.d(TAG, "Found existing favorites: ${it.count}")
|
||||
it.forEach { dataItem ->
|
||||
val data = getHomeFavorites(DataMapItem.fromDataItem(dataItem).dataMap)
|
||||
val data = getHomeFavorites(DataMapItem.fromDataItem(dataItem).dataMap).removeSurrounding("[", "]").split(", ").toList()
|
||||
Log.d(
|
||||
TAG,
|
||||
"Favorites: ${data.removeSurrounding("[", "]").split(", ").map { it }}"
|
||||
"Favorites: $data"
|
||||
)
|
||||
mainScope.launch {
|
||||
integrationUseCase.setWearHomeFavorites(
|
||||
data.removeSurrounding("[", "]").split(", ").map { it }.toSet()
|
||||
)
|
||||
favoritesDao.deleteAll()
|
||||
if (data.isNotEmpty()) {
|
||||
data.forEachIndexed { index, s ->
|
||||
favoritesDao.add(Favorites(s, index))
|
||||
}
|
||||
}
|
||||
}
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue