mirror of
https://github.com/home-assistant/android
synced 2024-10-02 22:34:46 +00:00
Add settings and favorites for wear so users can quickly execute actions they do regularly faster (#1875)
* Add settings and favorites for wear * Use proper variable for navigation * Refactoring to remove clutter from HomeActivity * Always start set favorites at the top * Review comments * Correct merge mistake * Add rotary input on set favorites screen
This commit is contained in:
parent
588e74113d
commit
2c5e762504
|
@ -32,6 +32,9 @@ interface IntegrationRepository {
|
|||
suspend fun setSessionExpireMillis(value: Long)
|
||||
suspend fun getSessionExpireMillis(): Long
|
||||
|
||||
suspend fun setWearHomeFavorites(favorites: Set<String>)
|
||||
suspend fun getWearHomeFavorites(): Set<String>
|
||||
|
||||
suspend fun getThemeColor(): String
|
||||
|
||||
suspend fun getHomeAssistantVersion(): String
|
||||
|
|
|
@ -55,7 +55,7 @@ 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_INSTALLED_APP_VERSION = "installed_app_version"
|
||||
private const val PREF_WEAR_HOME_FAVORITES = "wear_home_favorites"
|
||||
private const val PREF_HA_VERSION = "ha_version"
|
||||
private const val PREF_AUTOPLAY_VIDEO = "autoplay_video"
|
||||
private const val PREF_FULLSCREEN_ENABLED = "fullscreen_enabled"
|
||||
|
@ -343,6 +343,14 @@ 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 getNotificationRateLimits(): RateLimitResponse {
|
||||
val pushToken = localStorage.getString(PREF_PUSH_TOKEN) ?: ""
|
||||
val requestBody = RateLimitRequest(pushToken)
|
||||
|
|
|
@ -101,4 +101,5 @@ dependencies {
|
|||
implementation("androidx.compose.foundation:foundation:1.0.5")
|
||||
implementation("androidx.wear.compose:compose-foundation:1.0.0-alpha10")
|
||||
implementation("androidx.wear.compose:compose-material:1.0.0-alpha10")
|
||||
implementation("androidx.wear.compose:compose-navigation:1.0.0-alpha10")
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
|
@ -19,14 +20,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipColors
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
import androidx.wear.compose.material.ExperimentalWearMaterialApi
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
import androidx.wear.compose.material.PositionIndicator
|
||||
import androidx.wear.compose.material.Scaffold
|
||||
|
@ -36,7 +36,9 @@ import androidx.wear.compose.material.Text
|
|||
import androidx.wear.compose.material.ToggleChip
|
||||
import androidx.wear.compose.material.ToggleChipDefaults
|
||||
import androidx.wear.compose.material.rememberScalingLazyListState
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
||||
import androidx.wear.compose.navigation.composable
|
||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.DaggerPresenterComponent
|
||||
|
@ -46,10 +48,16 @@ import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
|||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.onboarding.OnboardingActivity
|
||||
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationActivity
|
||||
import io.homeassistant.companion.android.settings.ScreenSetFavorites
|
||||
import io.homeassistant.companion.android.settings.ScreenSettings
|
||||
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.SetTitle
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.util.setChipDefaults
|
||||
import io.homeassistant.companion.android.util.updateFavorites
|
||||
import io.homeassistant.companion.android.viewModels.EntityViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -68,12 +76,16 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
|
||||
companion object {
|
||||
private const val TAG = "HomeActivity"
|
||||
private const val SCREEN_LANDING = "landing"
|
||||
const val SCREEN_SETTINGS = "settings"
|
||||
const val SCREEN_SET_FAVORITES = "set_favorites"
|
||||
|
||||
fun newInstance(context: Context): Intent {
|
||||
return Intent(context, HomeActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalWearMaterialApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -86,8 +98,9 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
|
||||
presenter.onViewReady()
|
||||
updateEntities()
|
||||
updateFavorites(entityViewModel, presenter, mainScope)
|
||||
setContent {
|
||||
LoadHomePage(entities = entityViewModel.entitiesResponse)
|
||||
LoadHomePage(entities = entityViewModel.entitiesResponse, entityViewModel.favoriteEntities)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,11 +121,12 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
finish()
|
||||
}
|
||||
|
||||
@ExperimentalWearMaterialApi
|
||||
@Composable
|
||||
private fun LoadHomePage(entities: Array<Entity<Any>>) {
|
||||
private fun LoadHomePage(entities: Array<Entity<Any>>, favorites: MutableSet<String>) {
|
||||
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
if (entities.isNullOrEmpty()) {
|
||||
if (entities.isNullOrEmpty() && favorites.isNullOrEmpty()) {
|
||||
Column {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
|
@ -134,6 +148,9 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
updateFavorites(entityViewModel, presenter, mainScope)
|
||||
val validEntities =
|
||||
entities.sortedBy { it.entityId }.filter { it.entityId.split(".")[0] in HomePresenterImpl.supportedDomains }
|
||||
val scenes =
|
||||
entities.sortedBy { it.entityId }.filter { it.entityId.split(".")[0] == "scene" }
|
||||
val scripts =
|
||||
|
@ -147,6 +164,7 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
RotaryEventDispatcher(scalingLazyListState)
|
||||
val swipeDismissableNavController = rememberSwipeDismissableNavController()
|
||||
|
||||
MaterialTheme {
|
||||
Scaffold(
|
||||
|
@ -159,79 +177,156 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
RotaryEventState(scrollState = scalingLazyListState)
|
||||
ScalingLazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = 10.dp,
|
||||
start = 10.dp,
|
||||
end = 10.dp,
|
||||
bottom = 40.dp
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = scalingLazyListState
|
||||
SwipeDismissableNavHost(
|
||||
navController = swipeDismissableNavController,
|
||||
startDestination = SCREEN_LANDING
|
||||
) {
|
||||
if (inputBooleans.isNotEmpty()) {
|
||||
items(inputBooleans.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.input_booleans)
|
||||
SetEntityUI(inputBooleans[index], index)
|
||||
}
|
||||
}
|
||||
if (lights.isNotEmpty()) {
|
||||
items(lights.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.lights)
|
||||
SetEntityUI(lights[index], index)
|
||||
}
|
||||
}
|
||||
if (scenes.isNotEmpty()) {
|
||||
items(scenes.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.scenes)
|
||||
composable(SCREEN_LANDING) {
|
||||
RotaryEventState(scrollState = scalingLazyListState)
|
||||
ScalingLazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = 10.dp,
|
||||
start = 10.dp,
|
||||
end = 10.dp,
|
||||
bottom = 40.dp
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = scalingLazyListState
|
||||
) {
|
||||
if (favorites.isNotEmpty()) {
|
||||
val favoriteArray = favorites.toTypedArray()
|
||||
items(favoriteArray.size) { index ->
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
if (index == 0)
|
||||
SetTitle(id = R.string.favorites)
|
||||
val favoriteEntityID =
|
||||
favoriteArray[index].split(",")[0]
|
||||
val favoriteName = favoriteArray[index].split(",")[1]
|
||||
val favoriteIcon = favoriteArray[index].split(",")[2]
|
||||
if (entities.isNullOrEmpty()) {
|
||||
// Use a normal chip when we don't have the state of the entity
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = if (index == 0) 30.dp else 10.dp),
|
||||
icon = {
|
||||
Image(
|
||||
asset = getIcon(
|
||||
favoriteIcon,
|
||||
favoriteEntityID.split(".")[0],
|
||||
baseContext
|
||||
)
|
||||
?: CommunityMaterial.Icon.cmd_cellphone
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = favoriteName,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
presenter.onEntityClicked(
|
||||
favoriteEntityID
|
||||
)
|
||||
},
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
backgroundColor = colorResource(id = R.color.colorAccent),
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
} else {
|
||||
for (entity in entities) {
|
||||
if (entity.entityId == favoriteEntityID) {
|
||||
SetEntityUI(entity = entity, index = index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entities.isNullOrEmpty()) {
|
||||
item {
|
||||
Column {
|
||||
SetTitle(id = R.string.loading)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 10.dp,
|
||||
start = 10.dp,
|
||||
end = 10.dp
|
||||
),
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.loading_entities),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
onClick = { /* No op */ },
|
||||
colors = setChipDefaults()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inputBooleans.isNotEmpty()) {
|
||||
items(inputBooleans.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.input_booleans)
|
||||
SetEntityUI(inputBooleans[index], index)
|
||||
}
|
||||
}
|
||||
if (lights.isNotEmpty()) {
|
||||
items(lights.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.lights)
|
||||
SetEntityUI(lights[index], index)
|
||||
}
|
||||
}
|
||||
if (scenes.isNotEmpty()) {
|
||||
items(scenes.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.scenes)
|
||||
|
||||
SetEntityUI(scenes[index], index)
|
||||
}
|
||||
}
|
||||
if (scripts.isNotEmpty()) {
|
||||
items(scripts.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.scripts)
|
||||
SetEntityUI(scripts[index], index)
|
||||
}
|
||||
}
|
||||
if (switches.isNotEmpty()) {
|
||||
items(switches.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.switches)
|
||||
SetEntityUI(switches[index], index)
|
||||
}
|
||||
}
|
||||
SetEntityUI(scenes[index], index)
|
||||
}
|
||||
}
|
||||
if (scripts.isNotEmpty()) {
|
||||
items(scripts.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.scripts)
|
||||
SetEntityUI(scripts[index], index)
|
||||
}
|
||||
}
|
||||
if (switches.isNotEmpty()) {
|
||||
items(switches.size) { index ->
|
||||
if (index == 0)
|
||||
SetTitle(R.string.switches)
|
||||
SetEntityUI(switches[index], index)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Column {
|
||||
SetTitle(R.string.other)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
icon = {
|
||||
Image(asset = CommunityMaterial.Icon.cmd_exit_run)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.logout)
|
||||
)
|
||||
},
|
||||
onClick = { presenter.onLogoutClicked() },
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
backgroundColor = Color.Red,
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
item {
|
||||
LoadOtherSection(swipeDismissableNavController)
|
||||
}
|
||||
}
|
||||
}
|
||||
composable(SCREEN_SETTINGS) {
|
||||
ScreenSettings(
|
||||
swipeDismissableNavController,
|
||||
entityViewModel,
|
||||
presenter
|
||||
)
|
||||
}
|
||||
composable(SCREEN_SET_FAVORITES) {
|
||||
ScreenSetFavorites(
|
||||
validEntities,
|
||||
entityViewModel,
|
||||
baseContext,
|
||||
presenter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,25 +337,13 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
@Composable
|
||||
private fun SetEntityUI(entity: Entity<Any>, index: Int) {
|
||||
val attributes = entity.attributes as Map<String, String>
|
||||
val iconBitmap =
|
||||
if (attributes["icon"]?.startsWith("mdi") == true) {
|
||||
val icon = attributes["icon"]!!.split(":")[1]
|
||||
IconicsDrawable(baseContext, "cmd-$icon").icon
|
||||
} else {
|
||||
when (entity.entityId.split(".")[0]) {
|
||||
"input_boolean", "switch" -> CommunityMaterial.Icon2.cmd_light_switch
|
||||
"light" -> CommunityMaterial.Icon2.cmd_lightbulb
|
||||
"script" -> CommunityMaterial.Icon3.cmd_script_text_outline
|
||||
"scene" -> CommunityMaterial.Icon3.cmd_palette_outline
|
||||
else -> CommunityMaterial.Icon.cmd_cellphone
|
||||
}
|
||||
}
|
||||
val iconBitmap = getIcon(attributes["icon"], entity.entityId.split(".")[0], baseContext)
|
||||
|
||||
if (entity.entityId.split(".")[0] in HomePresenterImpl.toggleDomains) {
|
||||
ToggleChip(
|
||||
checked = entity.state == "on",
|
||||
onCheckedChange = {
|
||||
presenter.onEntityClicked(entity)
|
||||
presenter.onEntityClicked(entity.entityId)
|
||||
updateEntities()
|
||||
},
|
||||
modifier = Modifier
|
||||
|
@ -302,7 +385,7 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
},
|
||||
enabled = entity.state != "unavailable",
|
||||
onClick = {
|
||||
presenter.onEntityClicked(entity)
|
||||
presenter.onEntityClicked(entity.entityId)
|
||||
updateEntities()
|
||||
},
|
||||
colors = setChipDefaults()
|
||||
|
@ -311,23 +394,45 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SetTitle(id: Int) {
|
||||
Text(
|
||||
text = stringResource(id = id),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun setChipDefaults(): ChipColors {
|
||||
return ChipDefaults.primaryChipColors(
|
||||
backgroundColor = colorResource(id = R.color.colorAccent),
|
||||
contentColor = Color.Black
|
||||
)
|
||||
private fun LoadOtherSection(swipeDismissableNavController: NavHostController) {
|
||||
Column {
|
||||
SetTitle(R.string.other)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
icon = {
|
||||
Image(asset = CommunityMaterial.Icon.cmd_cog)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings)
|
||||
)
|
||||
},
|
||||
onClick = { swipeDismissableNavController.navigate(SCREEN_SETTINGS) },
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
icon = {
|
||||
Image(asset = CommunityMaterial.Icon.cmd_exit_run)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.logout)
|
||||
)
|
||||
},
|
||||
onClick = { presenter.onLogoutClicked() },
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
backgroundColor = Color.Red,
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEntities() {
|
||||
|
|
|
@ -5,8 +5,10 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
interface HomePresenter {
|
||||
|
||||
fun onViewReady()
|
||||
fun onEntityClicked(entity: Entity<Any>)
|
||||
fun onEntityClicked(entityId: String)
|
||||
fun onLogoutClicked()
|
||||
fun onFinish()
|
||||
suspend fun getEntities(): Array<Entity<Any>>
|
||||
suspend fun getWearHomeFavorites(): Set<String>
|
||||
suspend fun setWearHomeFavorites(favorites: Set<String>)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ class HomePresenterImpl @Inject constructor(
|
|||
"cover", "fan", "humidifier", "input_boolean", "light",
|
||||
"media_player", "remote", "siren", "switch"
|
||||
)
|
||||
val supportedDomains = listOf(
|
||||
"input_boolean", "light", "switch", "script", "scene"
|
||||
)
|
||||
const val TAG = "HomePresenter"
|
||||
}
|
||||
|
||||
|
@ -52,22 +55,22 @@ class HomePresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onEntityClicked(entity: Entity<Any>) {
|
||||
override fun onEntityClicked(entityId: String) {
|
||||
|
||||
if (entity.entityId.split(".")[0] in toggleDomains) {
|
||||
if (entityId.split(".")[0] in toggleDomains) {
|
||||
mainScope.launch {
|
||||
integrationUseCase.callService(
|
||||
entity.entityId.split(".")[0],
|
||||
entityId.split(".")[0],
|
||||
"toggle",
|
||||
hashMapOf("entity_id" to entity.entityId)
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mainScope.launch {
|
||||
integrationUseCase.callService(
|
||||
entity.entityId.split(".")[0],
|
||||
entityId.split(".")[0],
|
||||
"turn_on",
|
||||
hashMapOf("entity_id" to entity.entityId)
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -99,4 +102,12 @@ class HomePresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWearHomeFavorites(): Set<String> {
|
||||
return integrationUseCase.getWearHomeFavorites()
|
||||
}
|
||||
|
||||
override suspend fun setWearHomeFavorites(favorites: Set<String>) {
|
||||
integrationUseCase.setWearHomeFavorites(favorites)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
package io.homeassistant.companion.android.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
import androidx.wear.compose.material.ScalingLazyColumn
|
||||
import androidx.wear.compose.material.ScalingLazyListState
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.material.ToggleChip
|
||||
import androidx.wear.compose.material.ToggleChipDefaults
|
||||
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.R
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.home.HomeActivity
|
||||
import io.homeassistant.companion.android.home.HomePresenter
|
||||
import io.homeassistant.companion.android.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.SetTitle
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.util.saveFavorites
|
||||
import io.homeassistant.companion.android.viewModels.EntityViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
@Composable
|
||||
fun ScreenSettings(swipeDismissableNavController: NavHostController, entityViewModel: EntityViewModel, presenter: HomePresenter) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
SetTitle(id = R.string.settings)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 20.dp),
|
||||
icon = {
|
||||
Image(asset = CommunityMaterial.Icon3.cmd_star)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.favorite)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
swipeDismissableNavController.navigate(
|
||||
HomeActivity.SCREEN_SET_FAVORITES
|
||||
)
|
||||
},
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp),
|
||||
icon = {
|
||||
Image(asset = CommunityMaterial.Icon.cmd_delete)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.clear_favorites),
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
entityViewModel.favoriteEntities = mutableSetOf()
|
||||
saveFavorites(entityViewModel.favoriteEntities.toMutableSet(), presenter, mainScope)
|
||||
},
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
contentColor = Color.Black
|
||||
),
|
||||
secondaryLabel = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.irreverisble)
|
||||
)
|
||||
},
|
||||
enabled = entityViewModel.favoriteEntities.isNotEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScreenSetFavorites(
|
||||
validEntities: List<Entity<Any>>,
|
||||
entityViewModel: EntityViewModel,
|
||||
context: Context,
|
||||
presenter: HomePresenter
|
||||
) {
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
RotaryEventState(scrollState = scalingLazyListState)
|
||||
ScalingLazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = 10.dp,
|
||||
start = 10.dp,
|
||||
end = 10.dp,
|
||||
bottom = 40.dp
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = scalingLazyListState
|
||||
) {
|
||||
items(validEntities.size) { index ->
|
||||
val attributes = validEntities[index].attributes as Map<String, String>
|
||||
val iconBitmap = getIcon(attributes["icon"], validEntities[index].entityId.split(".")[0], context)
|
||||
if (index == 0)
|
||||
SetTitle(id = R.string.set_favorite)
|
||||
val elementString = "${validEntities[index].entityId},${attributes["friendly_name"]},${attributes["icon"]}"
|
||||
var checked by rememberSaveable { mutableStateOf(entityViewModel.favoriteEntities.contains(elementString)) }
|
||||
ToggleChip(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
if (it) {
|
||||
entityViewModel.favoriteEntities.add(elementString)
|
||||
} else {
|
||||
entityViewModel.favoriteEntities.remove(elementString)
|
||||
}
|
||||
saveFavorites(entityViewModel.favoriteEntities.toMutableSet(), presenter, mainScope)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = if (index == 0) 30.dp else 10.dp),
|
||||
appIcon = { Image(asset = iconBitmap ?: CommunityMaterial.Icon.cmd_cellphone) },
|
||||
label = {
|
||||
Text(
|
||||
text = attributes["friendly_name"].toString(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
toggleIcon = { ToggleChipDefaults.SwitchIcon(checked) },
|
||||
colors = ToggleChipDefaults.toggleChipColors(
|
||||
checkedStartBackgroundColor = colorResource(id = R.color.colorAccent),
|
||||
checkedEndBackgroundColor = colorResource(id = R.color.colorAccent),
|
||||
uncheckedStartBackgroundColor = colorResource(id = R.color.colorAccent),
|
||||
uncheckedEndBackgroundColor = colorResource(id = R.color.colorAccent),
|
||||
checkedContentColor = Color.Black,
|
||||
uncheckedContentColor = Color.Black,
|
||||
checkedToggleIconTintColor = Color.Yellow,
|
||||
uncheckedToggleIconTintColor = Color.DarkGray
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package io.homeassistant.companion.android.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.wear.compose.material.ChipColors
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
import androidx.wear.compose.material.Text
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.IIcon
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.home.HomePresenter
|
||||
import io.homeassistant.companion.android.viewModels.EntityViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SetTitle(id: Int) {
|
||||
Text(
|
||||
text = stringResource(id = id),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun setChipDefaults(): ChipColors {
|
||||
return ChipDefaults.primaryChipColors(
|
||||
backgroundColor = colorResource(id = R.color.colorAccent),
|
||||
contentColor = Color.Black
|
||||
)
|
||||
}
|
||||
|
||||
fun updateFavorites(entityViewModel: EntityViewModel, presenter: HomePresenter, mainScope: CoroutineScope) {
|
||||
mainScope.launch { entityViewModel.favoriteEntities = presenter.getWearHomeFavorites().toMutableSet() }
|
||||
}
|
||||
|
||||
fun saveFavorites(favorites: Set<String>, presenter: HomePresenter, mainScope: CoroutineScope) {
|
||||
mainScope.launch { presenter.setWearHomeFavorites(favorites.toSet()) }
|
||||
}
|
||||
|
||||
fun getIcon(icon: String?, domain: String, context: Context): IIcon? {
|
||||
return if (icon?.startsWith("mdi") == true) {
|
||||
val mdiIcon = icon.split(":")[1]
|
||||
IconicsDrawable(context, "cmd-$mdiIcon").icon
|
||||
} else {
|
||||
when (domain) {
|
||||
"input_boolean", "switch" -> CommunityMaterial.Icon2.cmd_light_switch
|
||||
"light" -> CommunityMaterial.Icon2.cmd_lightbulb
|
||||
"script" -> CommunityMaterial.Icon3.cmd_script_text_outline
|
||||
"scene" -> CommunityMaterial.Icon3.cmd_palette_outline
|
||||
else -> CommunityMaterial.Icon.cmd_cellphone
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,4 +9,5 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
class EntityViewModel : ViewModel() {
|
||||
|
||||
var entitiesResponse: Array<Entity<Any>> by mutableStateOf(arrayOf())
|
||||
var favoriteEntities: MutableSet<String> by mutableStateOf(mutableSetOf())
|
||||
}
|
||||
|
|
|
@ -34,4 +34,10 @@
|
|||
<string name="input_booleans">Input Booleans</string>
|
||||
<string name="switches">Switches</string>
|
||||
<string name="loading_entities">Please wait while we load your entities</string>
|
||||
<string name="favorite">Set Favorite Entities</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="favorites">Favorites</string>
|
||||
<string name="set_favorite">Set Favorites</string>
|
||||
<string name="clear_favorites">Clear Favorites</string>
|
||||
<string name="irreverisble">This action is irreversible</string>
|
||||
</resources>
|
Loading…
Reference in a new issue