From cdd8a31ca6e9dfa421f2ef853470229c71e49bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joris=20Pelgr=C3=B6m?= Date: Fri, 15 Sep 2023 15:15:32 +0200 Subject: [PATCH] Cleanup: camera entities selection, empty state, preview image - On initial load also create a list of camera entities to make it possible to select them without showing up elsewhere in the app - Add text to empty state instructing the user to set a camera - Update tile preview images --- common/src/main/res/values/strings.xml | 1 + .../companion/android/home/MainViewModel.kt | 6 ++ .../companion/android/home/views/HomeView.kt | 4 +- .../android/home/views/SetCameraTileView.kt | 12 ++- .../companion/android/tiles/CameraTile.kt | 36 +++---- .../companion/android/tiles/TileViews.kt | 88 +++++++++++------- .../drawable-round/camera_tile_example.png | Bin 0 -> 3613 bytes .../main/res/drawable/camera_tile_example.png | Bin 0 -> 2702 bytes .../res/drawable/camera_tile_example.webp | Bin 4472 -> 0 bytes 9 files changed, 92 insertions(+), 55 deletions(-) create mode 100644 wear/src/main/res/drawable-round/camera_tile_example.png create mode 100644 wear/src/main/res/drawable/camera_tile_example.png delete mode 100644 wear/src/main/res/drawable/camera_tile_example.webp diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 66c8dc14c5c..45f881f590b 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -125,6 +125,7 @@ See what\'s on your camera Log in to Home Assistant to add a camera tile There are no camera tiles added yet - add one from the watch face to set it up + Edit the tile settings and select a camera to show Camera tile #%d Camera tiles Camera widgets diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/MainViewModel.kt b/wear/src/main/java/io/homeassistant/companion/android/home/MainViewModel.kt index f1d1fed6075..ed7cfd07160 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/MainViewModel.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/MainViewModel.kt @@ -92,6 +92,8 @@ class MainViewModel @Inject constructor( val shortcutEntitiesMap = mutableStateMapOf>() val cameraTiles = cameraTileDao.getAllFlow().collectAsState() + var cameraEntitiesMap = mutableStateMapOf>>() + private set var areas = mutableListOf() private set @@ -226,6 +228,10 @@ class MainViewModel @Inject constructor( getEntities.await()?.also { entities.clear() it.forEach { state -> updateEntityStates(state) } + + // Special list: camera entities + val cameraEntities = it.filter { entity -> entity.domain == "camera" } + cameraEntitiesMap["camera"] = mutableStateListOf>().apply { addAll(cameraEntities) } } if (!isFavoritesOnly) { updateEntityDomains() diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/views/HomeView.kt b/wear/src/main/java/io/homeassistant/companion/android/home/views/HomeView.kt index cf4d6554053..6f790e8731e 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/views/HomeView.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/views/HomeView.kt @@ -1,6 +1,5 @@ package io.homeassistant.companion.android.home.views -import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -200,6 +199,7 @@ fun LoadHomePage( val tileId = backStackEntry.arguments?.getInt(ARG_SCREEN_CAMERA_TILE_ID) SetCameraTileView( tile = mainViewModel.cameraTiles.value.firstOrNull { it.id == tileId }, + entities = mainViewModel.cameraEntitiesMap["camera"], onSelectEntity = { swipeDismissableNavController.navigate("$ROUTE_CAMERA_TILE/$tileId/$SCREEN_SET_CAMERA_TILE_ENTITY") }, @@ -221,7 +221,7 @@ fun LoadHomePage( val cameraFavorites = remember { mutableStateOf(emptyList()) } // There are no camera favorites ChooseEntityView( entitiesByDomainOrder = cameraDomains, - entitiesByDomain = mainViewModel.entitiesByDomain, + entitiesByDomain = mainViewModel.cameraEntitiesMap, favoriteEntityIds = cameraFavorites, onNoneClicked = {}, onEntitySelected = { entity -> diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/views/SetCameraTileView.kt b/wear/src/main/java/io/homeassistant/companion/android/home/views/SetCameraTileView.kt index 9e1ecc3f611..7911dc79c0b 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/views/SetCameraTileView.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/views/SetCameraTileView.kt @@ -15,6 +15,9 @@ 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.common.R +import io.homeassistant.companion.android.common.data.integration.Entity +import io.homeassistant.companion.android.common.data.integration.friendlyName +import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.database.wear.CameraTile import io.homeassistant.companion.android.theme.WearAppTheme import io.homeassistant.companion.android.theme.wearColorPalette @@ -27,6 +30,7 @@ import io.homeassistant.companion.android.common.R as commonR @Composable fun SetCameraTileView( tile: CameraTile?, + entities: List>?, onSelectEntity: () -> Unit, onSelectRefreshInterval: () -> Unit ) { @@ -45,11 +49,15 @@ fun SetCameraTileView( ListHeader(commonR.string.camera_tile) } item { + val entity = tile?.entityId?.let { tileEntityId -> + entities?.firstOrNull { it.entityId == tileEntityId } + } + val icon = entity?.getIcon(LocalContext.current) ?: CommunityMaterial.Icon3.cmd_video Chip( modifier = Modifier.fillMaxWidth(), icon = { Image( - asset = CommunityMaterial.Icon3.cmd_video, // TODO + asset = icon, colorFilter = ColorFilter.tint(wearColorPalette.onSurface) ) }, @@ -60,7 +68,7 @@ fun SetCameraTileView( ) }, secondaryLabel = { - Text(tile?.entityId ?: "") + Text(entity?.friendlyName ?: tile?.entityId ?: "") }, onClick = onSelectEntity ) diff --git a/wear/src/main/java/io/homeassistant/companion/android/tiles/CameraTile.kt b/wear/src/main/java/io/homeassistant/companion/android/tiles/CameraTile.kt index 8a3ee0483da..e4790b8be78 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/tiles/CameraTile.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/tiles/CameraTile.kt @@ -79,13 +79,8 @@ class CameraTile : TileService() { if (serverManager.isRegistered() && tileConfig != null) { timeline( requestParams.deviceConfiguration.screenWidthDp, - requestParams.deviceConfiguration.screenHeightDp - ) - } else if (serverManager.isRegistered()) { - // TODO emptystate - timeline( - requestParams.deviceConfiguration.screenWidthDp, - requestParams.deviceConfiguration.screenHeightDp + requestParams.deviceConfiguration.screenHeightDp, + tileConfig.entityId.isNullOrBlank() ) } else { loggedOutTimeline( @@ -192,17 +187,26 @@ class CameraTile : TileService() { serviceScope.cancel() } - private fun timeline(width: Int, height: Int): Timeline = Timeline.fromLayoutElement( + private fun timeline(width: Int, height: Int, requiresSetup: Boolean): Timeline = Timeline.fromLayoutElement( LayoutElementBuilders.Box.Builder().apply { // Camera image - addContent( - LayoutElementBuilders.Image.Builder() - .setResourceId(RESOURCE_SNAPSHOT) - .setWidth(DimensionBuilders.dp(width.toFloat())) - .setHeight(DimensionBuilders.dp(height.toFloat())) - .setContentScaleMode(CONTENT_SCALE_MODE_FIT) - .build() - ) + if (requiresSetup) { + addContent( + LayoutElementBuilders.Text.Builder() + .setText(getString(commonR.string.camera_tile_no_entity_yet)) + .setMaxLines(10) + .build() + ) + } else { + addContent( + LayoutElementBuilders.Image.Builder() + .setResourceId(RESOURCE_SNAPSHOT) + .setWidth(DimensionBuilders.dp(width.toFloat())) + .setHeight(DimensionBuilders.dp(height.toFloat())) + .setContentScaleMode(CONTENT_SCALE_MODE_FIT) + .build() + ) + } // Refresh button addContent(getRefreshButton()) // Click: refresh diff --git a/wear/src/main/java/io/homeassistant/companion/android/tiles/TileViews.kt b/wear/src/main/java/io/homeassistant/companion/android/tiles/TileViews.kt index 7419475a98d..2209efa95e6 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/tiles/TileViews.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/tiles/TileViews.kt @@ -50,6 +50,31 @@ fun loggedOutTimeline( requestParams: RequestBuilders.TileRequest, @StringRes title: Int, @StringRes text: Int +): Timeline = primaryLayoutTimeline( + context = context, + requestParams = requestParams, + title = title, + text = text, + actionText = commonR.string.login, + action = ActionBuilders.LaunchAction.Builder() + .setAndroidActivity( + ActionBuilders.AndroidActivity.Builder() + .setClassName(SplashActivity::class.java.name) + .setPackageName(context.packageName) + .build() + ).build() +) + +/** + * A [Timeline] with a single entry using the Material `PrimaryLayout`. The title is optional. + */ +fun primaryLayoutTimeline( + context: Context, + requestParams: RequestBuilders.TileRequest, + @StringRes title: Int?, + @StringRes text: Int, + @StringRes actionText: Int, + action: ActionBuilders.Action ): Timeline { val theme = Colors( ContextCompat.getColor(context, R.color.colorPrimary), // Primary @@ -59,43 +84,36 @@ fun loggedOutTimeline( ) val chipColors = ChipColors.primaryChipColors(theme) val chipAction = ModifiersBuilders.Clickable.Builder() - .setId("login") - .setOnClick( - ActionBuilders.LaunchAction.Builder() - .setAndroidActivity( - ActionBuilders.AndroidActivity.Builder() - .setClassName(SplashActivity::class.java.name) - .setPackageName(context.packageName) - .build() - ).build() - ).build() - return Timeline.fromLayoutElement( - PrimaryLayout.Builder(requestParams.deviceConfiguration) - .setPrimaryLabelTextContent( - Text.Builder(context, context.getString(title)) - .setTypography(Typography.TYPOGRAPHY_CAPTION1) - .setColor(argb(theme.primary)) - .build() - ) - .setContent( - Text.Builder(context, context.getString(text)) - .setTypography(Typography.TYPOGRAPHY_BODY1) - .setMaxLines(10) - .setColor(argb(theme.onSurface)) - .build() - ) - .setPrimaryChipContent( - CompactChip.Builder( - context, - context.getString(commonR.string.login), - chipAction, - requestParams.deviceConfiguration - ) - .setChipColors(chipColors) - .build() - ) + .setId("action") + .setOnClick(action) + .build() + val builder = PrimaryLayout.Builder(requestParams.deviceConfiguration) + if (title != null) { + builder.setPrimaryLabelTextContent( + Text.Builder(context, context.getString(title)) + .setTypography(Typography.TYPOGRAPHY_CAPTION1) + .setColor(argb(theme.primary)) + .build() + ) + } + builder.setContent( + Text.Builder(context, context.getString(text)) + .setTypography(Typography.TYPOGRAPHY_BODY1) + .setMaxLines(10) + .setColor(argb(theme.onSurface)) + .build() + ) + builder.setPrimaryChipContent( + CompactChip.Builder( + context, + context.getString(actionText), + chipAction, + requestParams.deviceConfiguration + ) + .setChipColors(chipColors) .build() ) + return Timeline.fromLayoutElement(builder.build()) } /** diff --git a/wear/src/main/res/drawable-round/camera_tile_example.png b/wear/src/main/res/drawable-round/camera_tile_example.png new file mode 100644 index 0000000000000000000000000000000000000000..114c090618c0031a8c06a578a6c13f2b9505f0a3 GIT binary patch literal 3613 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 G-TwjqaCbZa literal 0 HcmV?d00001 diff --git a/wear/src/main/res/drawable/camera_tile_example.png b/wear/src/main/res/drawable/camera_tile_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c519212aa84f0baf149d7699f8c7419c39323db2 GIT binary patch literal 2702 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