Skip to content

Commit

Permalink
Add settings and favorites for wear so users can quickly execute acti…
Browse files Browse the repository at this point in the history
…ons 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
  • Loading branch information
dshokouhi committed Nov 9, 2021
1 parent 588e741 commit 2c5e762
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 114 deletions.
Expand Up @@ -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
Expand Down
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions wear/build.gradle.kts
Expand Up @@ -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")
}

Large diffs are not rendered by default.

Expand Up @@ -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>)
}
Expand Up @@ -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"
}

Expand Down Expand Up @@ -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)
)
}
}
Expand Down Expand Up @@ -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
}
}
}
Expand Up @@ -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())
}
6 changes: 6 additions & 0 deletions wear/src/main/res/values/strings.xml
Expand Up @@ -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>

0 comments on commit 2c5e762

Please sign in to comment.