mirror of
https://github.com/home-assistant/android
synced 2024-10-02 22:34:46 +00:00
Major Wear Cleanup (#1895)
* Better state hoisting. * Broke down compose items and removed dependant state. * Functional minus favorites.... * Favorites working, not my best solution. * Breaking more stuff down. * ktlint.
This commit is contained in:
parent
eb3585b305
commit
4fdbe7214a
|
@ -6,69 +6,13 @@ import android.os.Bundle
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.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.TextAlign
|
||||
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.ExperimentalWearMaterialApi
|
||||
import androidx.wear.compose.material.ListHeader
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
import androidx.wear.compose.material.PositionIndicator
|
||||
import androidx.wear.compose.material.Scaffold
|
||||
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 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
|
||||
import io.homeassistant.companion.android.PresenterModule
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.home.views.LoadHomePage
|
||||
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
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeActivity : ComponentActivity(), HomeView {
|
||||
|
@ -76,21 +20,10 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
@Inject
|
||||
lateinit var presenter: HomePresenter
|
||||
|
||||
private val entityViewModel by viewModels<EntityViewModel>()
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
var expandedFavorites: Boolean by mutableStateOf(true)
|
||||
var expandedInputBooleans: Boolean by mutableStateOf(true)
|
||||
var expandedLights: Boolean by mutableStateOf(true)
|
||||
var expandedScenes: Boolean by mutableStateOf(true)
|
||||
var expandedScripts: Boolean by mutableStateOf(true)
|
||||
var expandedSwitches: Boolean by mutableStateOf(true)
|
||||
private val mainViewModel by viewModels<MainViewModel>()
|
||||
|
||||
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)
|
||||
|
@ -109,11 +42,11 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
.inject(this)
|
||||
|
||||
presenter.onViewReady()
|
||||
updateEntities()
|
||||
updateFavorites(entityViewModel, presenter, mainScope)
|
||||
setContent {
|
||||
LoadHomePage(entities = entityViewModel.entitiesResponse, entityViewModel.favoriteEntities)
|
||||
LoadHomePage(mainViewModel)
|
||||
}
|
||||
|
||||
mainViewModel.init(presenter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -132,388 +65,4 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
@ExperimentalWearMaterialApi
|
||||
@Composable
|
||||
private fun LoadHomePage(entities: List<Entity<Any>>, favorites: MutableSet<String>) {
|
||||
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
if (entities.isNullOrEmpty() && favorites.isNullOrEmpty()) {
|
||||
Column {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)
|
||||
)
|
||||
SetTitle(id = R.string.loading)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.padding(top = 50.dp, start = 10.dp, end = 10.dp),
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.loading_entities),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
onClick = { /* No op */ },
|
||||
colors = setChipDefaults()
|
||||
)
|
||||
}
|
||||
} 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 =
|
||||
entities.sortedBy { it.entityId }.filter { it.entityId.split(".")[0] == "script" }
|
||||
val lights =
|
||||
entities.sortedBy { it.entityId }.filter { it.entityId.split(".")[0] == "light" }
|
||||
val inputBooleans = entities.sortedBy { it.entityId }
|
||||
.filter { it.entityId.split(".")[0] == "input_boolean" }
|
||||
val switches =
|
||||
entities.sortedBy { it.entityId }.filter { it.entityId.split(".")[0] == "switch" }
|
||||
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
RotaryEventDispatcher(scalingLazyListState)
|
||||
val swipeDismissableNavController = rememberSwipeDismissableNavController()
|
||||
|
||||
MaterialTheme {
|
||||
Scaffold(
|
||||
positionIndicator = {
|
||||
if (scalingLazyListState.isScrollInProgress)
|
||||
PositionIndicator(scalingLazyListState = scalingLazyListState)
|
||||
}
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
SwipeDismissableNavHost(
|
||||
navController = swipeDismissableNavController,
|
||||
startDestination = SCREEN_LANDING
|
||||
) {
|
||||
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()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.favorites,
|
||||
expanded = expandedFavorites
|
||||
)
|
||||
}
|
||||
val favoriteArray = favorites.toTypedArray()
|
||||
if (expandedFavorites) {
|
||||
items(favoriteArray.size) { index ->
|
||||
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) 0.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()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.input_booleans,
|
||||
expanded = expandedInputBooleans
|
||||
)
|
||||
}
|
||||
if (expandedInputBooleans) {
|
||||
items(inputBooleans.size) { index ->
|
||||
SetEntityUI(inputBooleans[index], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lights.isNotEmpty()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.lights,
|
||||
expanded = expandedLights
|
||||
)
|
||||
}
|
||||
if (expandedLights) {
|
||||
items(lights.size) { index ->
|
||||
SetEntityUI(lights[index], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scenes.isNotEmpty()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.scenes,
|
||||
expanded = expandedScenes
|
||||
)
|
||||
}
|
||||
if (expandedScenes) {
|
||||
items(scenes.size) { index ->
|
||||
SetEntityUI(scenes[index], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scripts.isNotEmpty()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.scripts,
|
||||
expanded = expandedScripts
|
||||
)
|
||||
}
|
||||
if (expandedScripts) {
|
||||
items(scripts.size) { index ->
|
||||
SetEntityUI(scripts[index], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (switches.isNotEmpty()) {
|
||||
item {
|
||||
SetListHeader(
|
||||
id = R.string.switches,
|
||||
expanded = expandedSwitches
|
||||
)
|
||||
}
|
||||
if (expandedSwitches) {
|
||||
items(switches.size) { index ->
|
||||
SetEntityUI(switches[index], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
LoadOtherSection(swipeDismissableNavController)
|
||||
}
|
||||
}
|
||||
}
|
||||
composable(SCREEN_SETTINGS) {
|
||||
ScreenSettings(
|
||||
swipeDismissableNavController,
|
||||
entityViewModel,
|
||||
presenter
|
||||
)
|
||||
}
|
||||
composable(SCREEN_SET_FAVORITES) {
|
||||
ScreenSetFavorites(
|
||||
validEntities,
|
||||
entityViewModel,
|
||||
baseContext,
|
||||
presenter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetEntityUI(entity: Entity<Any>, index: Int) {
|
||||
val attributes = entity.attributes as Map<String, String>
|
||||
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.entityId)
|
||||
updateEntities()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = if (index == 0) 0.dp else 10.dp),
|
||||
appIcon = { Image(asset = iconBitmap ?: CommunityMaterial.Icon.cmd_cellphone) },
|
||||
label = {
|
||||
Text(
|
||||
text = attributes["friendly_name"].toString(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
enabled = entity.state != "unavailable",
|
||||
toggleIcon = { ToggleChipDefaults.SwitchIcon(entity.state == "on") },
|
||||
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
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = if (index == 0) 0.dp else 10.dp),
|
||||
icon = { Image(asset = iconBitmap ?: CommunityMaterial.Icon.cmd_cellphone) },
|
||||
label = {
|
||||
Text(
|
||||
text = attributes["friendly_name"].toString(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
enabled = entity.state != "unavailable",
|
||||
onClick = {
|
||||
presenter.onEntityClicked(entity.entityId)
|
||||
updateEntities()
|
||||
},
|
||||
colors = setChipDefaults()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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() {
|
||||
mainScope.launch {
|
||||
entityViewModel.entitiesResponse = presenter.getEntities()
|
||||
delay(5000L)
|
||||
entityViewModel.entitiesResponse = presenter.getEntities()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetListHeader(id: Int, expanded: Boolean) {
|
||||
ListHeader(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
when (id) {
|
||||
R.string.favorites -> expandedFavorites = !expanded
|
||||
R.string.input_booleans -> expandedInputBooleans = !expanded
|
||||
R.string.lights -> expandedLights = !expanded
|
||||
R.string.scenes -> expandedScenes = !expanded
|
||||
R.string.scripts -> expandedScripts = !expanded
|
||||
R.string.switches -> expandedSwitches = !expanded
|
||||
}
|
||||
}
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = stringResource(id = id) + if (expanded) " -" else " +",
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
interface HomePresenter {
|
||||
|
||||
fun onViewReady()
|
||||
fun onEntityClicked(entityId: String)
|
||||
suspend fun onEntityClicked(entityId: String)
|
||||
fun onLogoutClicked()
|
||||
fun onFinish()
|
||||
suspend fun getEntities(): List<Entity<Any>>
|
||||
suspend fun getWearHomeFavorites(): Set<String>
|
||||
suspend fun setWearHomeFavorites(favorites: Set<String>)
|
||||
suspend fun getEntities(): List<Entity<*>>
|
||||
suspend fun getWearHomeFavorites(): List<String>
|
||||
suspend fun setWearHomeFavorites(favorites: List<String>)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class HomePresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getEntities(): List<Entity<Any>> {
|
||||
override suspend fun getEntities(): List<Entity<*>> {
|
||||
return try {
|
||||
integrationUseCase.getEntities()
|
||||
} catch (e: Exception) {
|
||||
|
@ -55,24 +55,20 @@ class HomePresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onEntityClicked(entityId: String) {
|
||||
override suspend fun onEntityClicked(entityId: String) {
|
||||
|
||||
if (entityId.split(".")[0] in toggleDomains) {
|
||||
mainScope.launch {
|
||||
integrationUseCase.callService(
|
||||
entityId.split(".")[0],
|
||||
"toggle",
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
integrationUseCase.callService(
|
||||
entityId.split(".")[0],
|
||||
"toggle",
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
} else {
|
||||
mainScope.launch {
|
||||
integrationUseCase.callService(
|
||||
entityId.split(".")[0],
|
||||
"turn_on",
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
integrationUseCase.callService(
|
||||
entityId.split(".")[0],
|
||||
"turn_on",
|
||||
hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,11 +99,11 @@ class HomePresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getWearHomeFavorites(): Set<String> {
|
||||
return integrationUseCase.getWearHomeFavorites()
|
||||
override suspend fun getWearHomeFavorites(): List<String> {
|
||||
return integrationUseCase.getWearHomeFavorites().toList()
|
||||
}
|
||||
|
||||
override suspend fun setWearHomeFavorites(favorites: Set<String>) {
|
||||
integrationUseCase.setWearHomeFavorites(favorites)
|
||||
override suspend fun setWearHomeFavorites(favorites: List<String>) {
|
||||
integrationUseCase.setWearHomeFavorites(favorites.toSet())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package io.homeassistant.companion.android.home
|
||||
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainViewModel : ViewModel() {
|
||||
|
||||
private lateinit var homePresenter: HomePresenter
|
||||
|
||||
// TODO: This is bad, do this instead: https://stackoverflow.com/questions/46283981/android-viewmodel-additional-arguments
|
||||
fun init(homePresenter: HomePresenter) {
|
||||
this.homePresenter = homePresenter
|
||||
loadEntities()
|
||||
}
|
||||
|
||||
var entities = mutableStateListOf<Entity<*>>()
|
||||
private set
|
||||
var favoriteEntityIds = mutableStateListOf<String>()
|
||||
private set
|
||||
|
||||
fun loadEntities() {
|
||||
viewModelScope.launch {
|
||||
entities.addAll(homePresenter.getEntities())
|
||||
favoriteEntityIds.addAll(homePresenter.getWearHomeFavorites())
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleEntity(entityId: String) {
|
||||
viewModelScope.launch {
|
||||
homePresenter.onEntityClicked(entityId)
|
||||
val updatedEntities = homePresenter.getEntities()
|
||||
// This should be better....
|
||||
for (i in updatedEntities.indices) {
|
||||
entities[i] = updatedEntities[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(entityId: String) {
|
||||
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.add(entityId)
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFavorite(entity: String) {
|
||||
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.remove(entity)
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearFavorites() {
|
||||
viewModelScope.launch {
|
||||
favoriteEntityIds.clear()
|
||||
homePresenter.setWearHomeFavorites(favoriteEntityIds)
|
||||
}
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
homePresenter.onLogoutClicked()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.material.ToggleChip
|
||||
import androidx.wear.compose.material.ToggleChipDefaults
|
||||
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.HomePresenterImpl
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
import io.homeassistant.companion.android.util.setChipDefaults
|
||||
|
||||
@Composable
|
||||
fun EntityUi(
|
||||
entity: Entity<*>,
|
||||
onEntityClicked: (String) -> Unit
|
||||
) {
|
||||
val attributes = entity.attributes as Map<*, *>
|
||||
val iconBitmap = getIcon(attributes["icon"] as String?, entity.entityId.split(".")[0], LocalContext.current)
|
||||
|
||||
if (entity.entityId.split(".")[0] in HomePresenterImpl.toggleDomains) {
|
||||
ToggleChip(
|
||||
checked = entity.state == "on",
|
||||
onCheckedChange = { onEntityClicked(entity.entityId) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
appIcon = { Image(asset = iconBitmap ?: CommunityMaterial.Icon.cmd_cellphone) },
|
||||
label = {
|
||||
Text(
|
||||
text = attributes["friendly_name"].toString(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
enabled = entity.state != "unavailable",
|
||||
toggleIcon = { ToggleChipDefaults.SwitchIcon(entity.state == "on") },
|
||||
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
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
icon = { Image(asset = iconBitmap ?: CommunityMaterial.Icon.cmd_cellphone) },
|
||||
label = {
|
||||
Text(
|
||||
text = attributes["friendly_name"].toString(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
enabled = entity.state != "unavailable",
|
||||
onClick = { onEntityClicked(entity.entityId) },
|
||||
colors = setChipDefaults()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ExperimentalWearMaterialApi
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
||||
import androidx.wear.compose.navigation.composable
|
||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.home.HomePresenterImpl
|
||||
import io.homeassistant.companion.android.home.MainViewModel
|
||||
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.SetTitle
|
||||
import io.homeassistant.companion.android.util.setChipDefaults
|
||||
|
||||
private const val SCREEN_LANDING = "landing"
|
||||
private const val SCREEN_SETTINGS = "settings"
|
||||
private const val SCREEN_SET_FAVORITES = "set_favorites"
|
||||
|
||||
@ExperimentalWearMaterialApi
|
||||
@Composable
|
||||
fun LoadHomePage(
|
||||
mainViewModel: MainViewModel
|
||||
) {
|
||||
|
||||
val rotaryEventDispatcher = RotaryEventDispatcher()
|
||||
if (mainViewModel.entities.isNullOrEmpty() && mainViewModel.favoriteEntityIds.isNullOrEmpty()) {
|
||||
Column {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 10.dp)
|
||||
)
|
||||
SetTitle(id = R.string.loading)
|
||||
Chip(
|
||||
modifier = Modifier
|
||||
.padding(top = 50.dp, start = 10.dp, end = 10.dp),
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.loading_entities),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
onClick = { /* No op */ },
|
||||
colors = setChipDefaults()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val swipeDismissableNavController = rememberSwipeDismissableNavController()
|
||||
MaterialTheme {
|
||||
CompositionLocalProvider(
|
||||
LocalRotaryEventDispatcher provides rotaryEventDispatcher
|
||||
) {
|
||||
RotaryEventHandlerSetup(rotaryEventDispatcher)
|
||||
SwipeDismissableNavHost(
|
||||
navController = swipeDismissableNavController,
|
||||
startDestination = SCREEN_LANDING
|
||||
) {
|
||||
composable(SCREEN_LANDING) {
|
||||
MainView(
|
||||
mainViewModel.entities,
|
||||
mainViewModel.favoriteEntityIds,
|
||||
{ mainViewModel.toggleEntity(it) },
|
||||
{ swipeDismissableNavController.navigate(SCREEN_SETTINGS) },
|
||||
{ mainViewModel.logout() }
|
||||
)
|
||||
}
|
||||
composable(SCREEN_SETTINGS) {
|
||||
SettingsView(
|
||||
mainViewModel.favoriteEntityIds,
|
||||
{ swipeDismissableNavController.navigate(SCREEN_SET_FAVORITES) },
|
||||
{ mainViewModel.clearFavorites() }
|
||||
)
|
||||
}
|
||||
composable(SCREEN_SET_FAVORITES) {
|
||||
val validEntities = mainViewModel.entities
|
||||
.filter { it.entityId.split(".")[0] in HomePresenterImpl.supportedDomains }
|
||||
SetFavoritesView(
|
||||
validEntities,
|
||||
mainViewModel.favoriteEntityIds
|
||||
) { entityId, isSelected ->
|
||||
if (isSelected) {
|
||||
mainViewModel.addFavorite(entityId)
|
||||
} else {
|
||||
mainViewModel.removeFavorite(entityId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.wear.compose.material.ListHeader
|
||||
import androidx.wear.compose.material.Text
|
||||
|
||||
@Composable
|
||||
fun ListHeader(
|
||||
stringId: Int,
|
||||
expanded: Boolean,
|
||||
onExpandChanged: (Boolean) -> Unit
|
||||
) {
|
||||
ListHeader(
|
||||
modifier = Modifier
|
||||
.clickable { onExpandChanged(!expanded) }
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = stringResource(id = stringId) + if (expanded) " -" else " +",
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.PositionIndicator
|
||||
import androidx.wear.compose.material.Scaffold
|
||||
import androidx.wear.compose.material.ScalingLazyColumn
|
||||
import androidx.wear.compose.material.ScalingLazyListState
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.material.rememberScalingLazyListState
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.util.RotaryEventDispatcher
|
||||
import io.homeassistant.companion.android.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.SetTitle
|
||||
import io.homeassistant.companion.android.util.setChipDefaults
|
||||
|
||||
@Composable
|
||||
fun MainView(
|
||||
entities: List<Entity<*>>,
|
||||
favoriteEntityIds: List<String>,
|
||||
onEntityClicked: (String) -> Unit,
|
||||
onSettingsClicked: () -> Unit,
|
||||
onLogoutClicked: () -> Unit
|
||||
) {
|
||||
val scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState()
|
||||
|
||||
var expandedFavorites: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedInputBooleans: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedLights: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedScenes: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedScripts: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
var expandedSwitches: Boolean by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val entityMap: Map<String, Entity<*>> = entities.map { it.entityId to it }.toMap()
|
||||
|
||||
val scenes = entities.filter { it.entityId.split(".")[0] == "scene" }
|
||||
val scripts = entities.filter { it.entityId.split(".")[0] == "script" }
|
||||
val lights = entities.filter { it.entityId.split(".")[0] == "light" }
|
||||
val inputBooleans = entities.filter { it.entityId.split(".")[0] == "input_boolean" }
|
||||
val switches = entities.filter { it.entityId.split(".")[0] == "switch" }
|
||||
|
||||
RotaryEventDispatcher(scalingLazyListState)
|
||||
RotaryEventState(scrollState = scalingLazyListState)
|
||||
|
||||
Scaffold(
|
||||
positionIndicator = {
|
||||
if (scalingLazyListState.isScrollInProgress)
|
||||
PositionIndicator(scalingLazyListState = 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 (favoriteEntityIds.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.favorites,
|
||||
expanded = expandedFavorites,
|
||||
onExpandChanged = { expandedFavorites = it }
|
||||
)
|
||||
}
|
||||
if (expandedFavorites) {
|
||||
items(favoriteEntityIds.size) { index ->
|
||||
EntityUi(
|
||||
// This is here to not break existing favorites.....
|
||||
entityMap[favoriteEntityIds[index].split(",")[0]]!!,
|
||||
onEntityClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.input_booleans,
|
||||
expanded = expandedInputBooleans,
|
||||
onExpandChanged = { expandedInputBooleans = it }
|
||||
)
|
||||
}
|
||||
if (expandedInputBooleans) {
|
||||
items(inputBooleans.size) { index ->
|
||||
EntityUi(inputBooleans[index], onEntityClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lights.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.lights,
|
||||
expanded = expandedLights,
|
||||
onExpandChanged = { expandedLights = it }
|
||||
)
|
||||
}
|
||||
if (expandedLights) {
|
||||
items(lights.size) { index ->
|
||||
EntityUi(lights[index], onEntityClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scenes.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.scenes,
|
||||
expanded = expandedScenes,
|
||||
onExpandChanged = { expandedScenes = it }
|
||||
)
|
||||
}
|
||||
if (expandedScenes) {
|
||||
items(scenes.size) { index ->
|
||||
EntityUi(scenes[index], onEntityClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scripts.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.scripts,
|
||||
expanded = expandedScripts,
|
||||
onExpandChanged = { expandedScripts = it }
|
||||
)
|
||||
}
|
||||
if (expandedScripts) {
|
||||
items(scripts.size) { index ->
|
||||
EntityUi(scripts[index], onEntityClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (switches.isNotEmpty()) {
|
||||
item {
|
||||
ListHeader(
|
||||
stringId = R.string.switches,
|
||||
expanded = expandedSwitches,
|
||||
onExpandChanged = { expandedSwitches = it }
|
||||
)
|
||||
}
|
||||
if (expandedSwitches) {
|
||||
items(switches.size) { index ->
|
||||
EntityUi(switches[index], onEntityClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
OtherSection(
|
||||
onSettingsClicked = onSettingsClicked,
|
||||
onLogoutClicked = onLogoutClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
import androidx.wear.compose.material.Text
|
||||
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.util.SetTitle
|
||||
|
||||
@Composable
|
||||
fun OtherSection(
|
||||
onSettingsClicked: () -> Unit,
|
||||
onLogoutClicked: () -> Unit
|
||||
) {
|
||||
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 = onSettingsClicked,
|
||||
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 = onLogoutClicked,
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
backgroundColor = Color.Red,
|
||||
contentColor = Color.Black
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.util.RotaryEventState
|
||||
import io.homeassistant.companion.android.util.SetTitle
|
||||
import io.homeassistant.companion.android.util.getIcon
|
||||
|
||||
@Composable
|
||||
fun SetFavoritesView(
|
||||
validEntities: List<Entity<*>>,
|
||||
favoriteEntityIds: List<String>,
|
||||
onFavoriteSelected: (entityId: String, isSelected: Boolean) -> Unit
|
||||
) {
|
||||
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<*, *>
|
||||
val iconBitmap = getIcon(
|
||||
attributes["icon"] as String?,
|
||||
validEntities[index].entityId.split(".")[0],
|
||||
LocalContext.current
|
||||
)
|
||||
if (index == 0)
|
||||
SetTitle(id = R.string.set_favorite)
|
||||
|
||||
val entityId = validEntities[index].entityId
|
||||
val checked = favoriteEntityIds.contains(entityId)
|
||||
ToggleChip(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onFavoriteSelected(entityId, it)
|
||||
},
|
||||
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,71 @@
|
|||
package io.homeassistant.companion.android.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.material.Chip
|
||||
import androidx.wear.compose.material.ChipDefaults
|
||||
import androidx.wear.compose.material.Text
|
||||
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.util.SetTitle
|
||||
|
||||
@Composable
|
||||
fun SettingsView(
|
||||
favorites: List<String>,
|
||||
onClickSetFavorites: () -> Unit,
|
||||
onClearFavorites: () -> Unit
|
||||
) {
|
||||
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 = onClickSetFavorites,
|
||||
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 = onClearFavorites,
|
||||
colors = ChipDefaults.primaryChipColors(
|
||||
contentColor = Color.Black
|
||||
),
|
||||
secondaryLabel = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.irreverisble)
|
||||
)
|
||||
},
|
||||
enabled = favorites.isNotEmpty()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,10 +17,6 @@ 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) {
|
||||
|
@ -42,14 +38,6 @@ fun setChipDefaults(): ChipColors {
|
|||
)
|
||||
}
|
||||
|
||||
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]
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package io.homeassistant.companion.android.viewModels
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
|
||||
class EntityViewModel : ViewModel() {
|
||||
|
||||
var entitiesResponse: List<Entity<Any>> by mutableStateOf(mutableListOf())
|
||||
var favoriteEntities: MutableSet<String> by mutableStateOf(mutableSetOf())
|
||||
}
|
Loading…
Reference in a new issue