Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editing wear favorites from the phone #1908

Merged
merged 20 commits into from Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Expand Up @@ -194,7 +194,8 @@ dependencies {
implementation("androidx.compose.ui:ui:1.0.5")
implementation("androidx.compose.ui:ui-tooling:1.0.5")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("com.google.android.material:compose-theme-adapter:1.0.4")
implementation("androidx.navigation:navigation-compose:2.4.0-beta02")
implementation("com.google.android.material:compose-theme-adapter:1.1.0")
implementation("com.google.accompanist:accompanist-appcompat-theme:0.20.2")

implementation("com.mikepenz:iconics-core:5.3.3")
Expand Down
Expand Up @@ -21,6 +21,7 @@ import com.google.android.gms.wearable.NodeClient
import com.google.android.gms.wearable.Wearable
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.databinding.ActivitySettingsWearBinding
import io.homeassistant.companion.android.settings.views.SettingsWearMainView
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.guava.await
Expand Down Expand Up @@ -169,20 +170,14 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC
binding.remoteOpenButton.isVisible = true
}
wearNodesWithApp.size < allConnectedNodes.size -> {
// TODO: Add your code to communicate with the wear app(s) via Wear APIs
// (MessageClient, DataClient, etc.)
Log.d(TAG, "Installed on some devices")
binding.informationTextView.text =
getString(R.string.message_some_installed, wearNodesWithApp.toString())
binding.remoteOpenButton.isVisible = true
startActivity(SettingsWearMainView.newInstance(applicationContext, wearNodesWithApp))
finish()
}
else -> {
// TODO: Add your code to communicate with the wear app(s) via Wear APIs
// (MessageClient, DataClient, etc.)
Log.d(TAG, "Installed on all devices")
binding.informationTextView.text =
getString(R.string.message_all_installed)
binding.remoteOpenButton.isInvisible = true
startActivity(SettingsWearMainView.newInstance(applicationContext, wearNodesWithApp))
finish()
}
}
}
Expand Down
@@ -0,0 +1,117 @@
package io.homeassistant.companion.android.settings

import android.app.Activity
import android.net.Uri
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.DataItem
import com.google.android.gms.wearable.DataMap
import com.google.android.gms.wearable.DataMapItem
import com.google.android.gms.wearable.PutDataMapRequest
import com.google.android.gms.wearable.Wearable
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import kotlinx.coroutines.launch

class SettingsWearViewModel : ViewModel() {

private lateinit var integrationUseCase: IntegrationRepository

private val TAG = "SettingsWearViewModel"
private val CAPABILITY_WEAR_FAVORITES = "send_home_favorites"
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved

var entities = mutableStateMapOf<String, Entity<*>>()
private set
var favoriteEntityIds = mutableStateListOf<String>()

fun init(integrationUseCase: IntegrationRepository) {
this.integrationUseCase = integrationUseCase
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
loadEntities()
}

private fun loadEntities() {
viewModelScope.launch {
integrationUseCase.getEntities().forEach {
entities[it.entityId] = it
}
}
}

fun saveHomeFavorites(data: String, item: DataItem) {
getFavorites(DataMapItem.fromDataItem(item).dataMap)
favoriteEntityIds.clear()
favoriteEntityIds.addAll(
data.removeSurrounding("[", "]").split(", ").map { it }
)
}

fun onEntitySelected(checked: Boolean, entityId: String, activity: Activity) {
if (checked)
favoriteEntityIds.add(entityId)
else
favoriteEntityIds.remove(entityId)
sendHomeFavorites(favoriteEntityIds.toList(), activity)
}

private fun sendHomeFavorites(favoritesList: List<String>, activity: Activity) = viewModelScope.launch {
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
Log.d(TAG, "sendHomeFavorites")

val putDataRequest = PutDataMapRequest.create("/save_home_favorites").run {
dataMap.putString("favorites", favoritesList.toString())
setUrgent()
asPutDataRequest()
}

Wearable.getDataClient(activity).putDataItem(putDataRequest).apply {
addOnSuccessListener { Log.d(TAG, "Successfully sent favorites to wear") }
addOnFailureListener { Log.d(TAG, "Failed to send favorites to wear") }
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
}
}

fun findExistingFavorites(activity: Activity) {
Log.d(TAG, "Finding existing favorites")
Tasks.await(Wearable.getDataClient(activity).getDataItems(Uri.parse("wear://*/home_favorites"))).apply {
Log.d(TAG, "Found existing favorites: ${this.count}")
this.forEach {
val data = getFavorites(DataMapItem.fromDataItem(this.first()).dataMap)
Log.d(TAG, "Favorites: $data")
favoriteEntityIds.clear()
favoriteEntityIds.addAll(
data.removeSurrounding("[", "]").split(", ").map { it }
)
}
}
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
}

fun getFavorites(map: DataMap): String {
map.apply {
return getString("favorites", "")
}
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
}

fun requestFavorites(activity: Activity) {
Log.d(TAG, "Requesting favorites")

val capabilityInfo = Tasks.await(
Wearable.getCapabilityClient(activity)
.getCapability(CAPABILITY_WEAR_FAVORITES, CapabilityClient.FILTER_REACHABLE)
)

capabilityInfo.nodes.forEach { node ->
Log.d(TAG, "Requesting favorite data")
Wearable.getMessageClient(activity).sendMessage(
node.id,
"/send_home_favorites",
ByteArray(0)
).apply {
addOnSuccessListener { Log.d(TAG, "Request to favorites sent successfully") }
addOnFailureListener { Log.d(TAG, "Failed to get favorites") }
}
}
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
}
}
@@ -0,0 +1,140 @@
package io.homeassistant.companion.android.settings.views

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Checkbox
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.HelpOutline
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.util.previewEntityList
import io.homeassistant.companion.android.util.previewFavoritesList

const val WEAR_DOCS_LINK = "https://companion.home-assistant.io/docs/wear-os/wear-os"
val supportedDomains = listOf(
"input_boolean", "light", "switch", "script", "scene"
)

@Composable
fun LoadWearFavoritesSettings(
entities: Map<String, Entity<*>>,
favoritesList: List<String>,
onEntitySelected: (Boolean, String, Activity) -> Unit,
isFavoriteSelected: (Int) -> Boolean,
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
activity: Activity
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
) {
val context = LocalContext.current

val validEntities = entities.filter { it.key.split(".")[0] in supportedDomains }.values.sortedBy { it.entityId }.toList()
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.wear_favorite_entities)) },
actions = {
IconButton(onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(WEAR_DOCS_LINK))
context.startActivity(intent)
}) {
Icon(
Icons.Filled.HelpOutline,
contentDescription = stringResource(id = R.string.help)
)
}
}
)
}
) {
LazyColumn(
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(top = 10.dp, start = 20.dp, end = 20.dp)
) {
item {
Text(
text = stringResource(R.string.wear_set_favorites),
fontWeight = FontWeight.Bold
)
}
items(favoritesList.size) { index ->
Row(
modifier = Modifier
.padding(15.dp)
.clickable {
onEntitySelected(
false,
favoritesList[index],
activity
)
}
) {
Checkbox(
checked = isFavoriteSelected(index),
onCheckedChange = {
onEntitySelected(it, favoritesList[index], activity)
},
modifier = Modifier.padding(end = 5.dp)
)
Text(
text = favoritesList[index].replace("[", "").replace("]", ""),
modifier = Modifier.padding(top = 10.dp)
)
}
}
if (!validEntities.isNullOrEmpty()) {
items(validEntities.size) { index ->
val item = validEntities[index]
if (!favoritesList.contains(item.entityId)) {
Row(
modifier = Modifier
.padding(15.dp)
.clickable {
onEntitySelected(true, item.entityId, activity)
}
) {
Checkbox(
checked = false,
onCheckedChange = {
onEntitySelected(it, item.entityId, activity)
},
modifier = Modifier.padding(end = 5.dp)
)
Text(
text = item.entityId,
modifier = Modifier.padding(top = 10.dp)
)
}
}
}
}
}
}
}

@Preview
@Composable
private fun PreviewLoadWearFavoritesSettings() {
LoadWearFavoritesSettings(
entities = previewEntityList,
favoritesList = previewFavoritesList,
onEntitySelected = { _, _, _ -> },
isFavoriteSelected = { true },
Activity()
)
}
@@ -0,0 +1,42 @@
package io.homeassistant.companion.android.settings.views

import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.material.composethemeadapter.MdcTheme
import io.homeassistant.companion.android.settings.SettingsWearViewModel

@Composable
fun LoadSettingsHomeView(
settingsWearViewModel: SettingsWearViewModel,
deviceName: String,
activity: Activity
) {
val context = LocalContext.current
MdcTheme {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = SettingsWearMainView.LANDING) {
composable(SettingsWearMainView.FAVORITES) {
LoadWearFavoritesSettings(
settingsWearViewModel.entities,
settingsWearViewModel.favoriteEntityIds.toList(),
{ b: Boolean, s: String, a: Activity -> settingsWearViewModel.onEntitySelected(b, s, a) },
{
settingsWearViewModel.favoriteEntityIds.toList().contains(
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
settingsWearViewModel.favoriteEntityIds.toList()[it]
dshokouhi marked this conversation as resolved.
Show resolved Hide resolved
)
},
activity
)
}
composable(SettingsWearMainView.LANDING) {
SettingWearLandingView(context, deviceName) {
navController.navigate(SettingsWearMainView.FAVORITES)
}
}
}
}
}