Support multiple Template tiles on Wear OS (#3783)

* Support multiple Template tiles on Wear OS

* Add `TemplateTileConfig` data class

* Fix migration

* `Pair` -> `TemplateTileConfig` fixes

* Fix `getAllTemplateTiles` implementation

* Initial work on companion <-> wearable device communication

* More work on phone <-> wear device communication

* Save updated template in phone app

* Get the template to render using the right method

* Fix CI complaints

* Work on Wear UI for multiple template tiles

* Update wear manifest

* Wear migration and navigation fixes

* Fix Template tile IDs in mobile app

* Make adding a new Template tile on Wear device work

* Small cleanups and TODO fixes

* Try to fix template config refresh in settings

* Fix after rebase

* Adopt blocking approach for reacting to tile events, inspired by #3974

* Use `OpenTileSettingsActivity` for template tile

* Adopt Material 3 and other UI-related changes

* Show help text in phone app if no template tiles have been added yet

* Reference the view model variable inside the function

By having the view model variable outside the block, the updated template tile
might not be propagated to the template settings view.

* Reload template tiles when opening the template tiles from settings

* Replace null key with -1 for old template tile

* Lint complaints fixes

* remove TODO

* Store error

* Scrollable list of template tiles

* Move "Configure template tile" to header

* Replace with methods with copy

* Show template as secondary text

* Fix scrolling

* Update app/src/full/java/io/homeassistant/companion/android/settings/wear/views/SettingsWearTemplateTileList.kt

Co-authored-by: Joris Pelgröm <jpelgrom@users.noreply.github.com>

* Remove unused field

* Move padding to "no tiles" text

* Add deep link

---------

Co-authored-by: Joris Pelgröm <jpelgrom@users.noreply.github.com>
This commit is contained in:
Sebastian Lövdahl 2024-01-24 17:56:39 +02:00 committed by GitHub
parent 26a472afc6
commit 5d52735e36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 571 additions and 102 deletions

View File

@ -5,7 +5,6 @@ import android.app.Application
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.core.net.toUri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
@ -23,6 +22,7 @@ import com.google.android.gms.wearable.Wearable
import dagger.hilt.android.lifecycle.HiltViewModel
import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.util.WearDataMessages
import io.homeassistant.companion.android.database.server.Server
@ -74,11 +74,9 @@ class SettingsWearViewModel @Inject constructor(
private set
var favoriteEntityIds = mutableStateListOf<String>()
private set
var templateTileContent = mutableStateOf("")
var templateTiles = mutableStateMapOf<Int, TemplateTileConfig>()
private set
var templateTileContentRendered = mutableStateOf("")
private set
var templateTileRefreshInterval = mutableStateOf(0)
var templateTilesRenderedTemplates = mutableStateMapOf<Int, String>()
private set
private val _resultSnackbar = MutableSharedFlow<String>()
@ -144,17 +142,42 @@ class SettingsWearViewModel @Inject constructor(
}
}
fun setTemplateContent(template: String) {
templateTileContent.value = template
fun setTemplateTileContent(tileId: Int, updatedTemplateTileContent: String) {
val templateTileConfig = templateTiles[tileId]
templateTileConfig?.let {
templateTiles[tileId] = it.copy(template = updatedTemplateTileContent)
renderTemplate(tileId, updatedTemplateTileContent)
}
}
fun setTemplateTileRefreshInterval(tileId: Int, refreshInterval: Int) {
val templateTileConfig = templateTiles[tileId]
templateTileConfig?.let {
templateTiles[tileId] = it.copy(refreshInterval = refreshInterval)
}
}
private fun setTemplateTiles(newTemplateTiles: Map<Int, TemplateTileConfig>) {
templateTiles.clear()
templateTilesRenderedTemplates.clear()
templateTiles.putAll(newTemplateTiles)
templateTiles.forEach {
renderTemplate(it.key, it.value.template)
}
}
private fun renderTemplate(tileId: Int, template: String) {
if (template.isNotEmpty() && serverId != 0) {
viewModelScope.launch {
try {
templateTileContentRendered.value =
serverManager.integrationRepository(serverId).renderTemplate(template, mapOf()).toString()
templateTilesRenderedTemplates[tileId] = serverManager
.integrationRepository(serverId)
.renderTemplate(template, mapOf()).toString()
} catch (e: Exception) {
Log.e(TAG, "Exception while rendering template", e)
Log.e(TAG, "Exception while rendering template for tile ID $tileId", e)
// JsonMappingException suggests that template is not a String (= error)
templateTileContentRendered.value = getApplication<Application>().getString(
templateTilesRenderedTemplates[tileId] = getApplication<Application>().getString(
if (e.cause is JsonMappingException) {
commonR.string.template_error
} else {
@ -164,7 +187,7 @@ class SettingsWearViewModel @Inject constructor(
}
}
} else {
templateTileContentRendered.value = ""
templateTilesRenderedTemplates[tileId] = ""
}
}
@ -254,9 +277,8 @@ class SettingsWearViewModel @Inject constructor(
}
fun sendTemplateTileInfo() {
val putDataRequest = PutDataMapRequest.create("/updateTemplateTile").run {
dataMap.putString(WearDataMessages.CONFIG_TEMPLATE_TILE, templateTileContent.value)
dataMap.putInt(WearDataMessages.CONFIG_TEMPLATE_TILE_REFRESH_INTERVAL, templateTileRefreshInterval.value)
val putDataRequest = PutDataMapRequest.create("/updateTemplateTiles").run {
dataMap.putString(WearDataMessages.CONFIG_TEMPLATE_TILES, objectMapper.writeValueAsString(templateTiles))
setUrgent()
asPutDataRequest()
}
@ -308,8 +330,14 @@ class SettingsWearViewModel @Inject constructor(
favoriteEntityIdList.forEach { entityId ->
favoriteEntityIds.add(entityId)
}
setTemplateContent(data.getString(WearDataMessages.CONFIG_TEMPLATE_TILE, ""))
templateTileRefreshInterval.value = data.getInt(WearDataMessages.CONFIG_TEMPLATE_TILE_REFRESH_INTERVAL, 0)
val templateTilesFromWear: Map<Int, TemplateTileConfig> = objectMapper.readValue(
data.getString(
WearDataMessages.CONFIG_TEMPLATE_TILES,
"{}"
)
)
setTemplateTiles(templateTilesFromWear)
_hasData.value = true
}

View File

@ -11,9 +11,11 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.accompanist.themeadapter.material.MdcTheme
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
@ -48,30 +50,50 @@ fun LoadSettingsHomeView(
hasData = hasData,
isAuthed = isAuthenticated,
navigateFavorites = { navController.navigate(SettingsWearMainView.FAVORITES) },
navigateTemplateTile = { navController.navigate(SettingsWearMainView.TEMPLATE) },
navigateTemplateTile = { navController.navigate(SettingsWearMainView.TEMPLATES) },
loginWearOs = loginWearOs,
onBackClicked = onStartBackClicked,
events = settingsWearViewModel.resultSnackbar
)
}
composable(SettingsWearMainView.TEMPLATE) {
SettingsWearTemplateTile(
template = settingsWearViewModel.templateTileContent.value,
renderedTemplate = settingsWearViewModel.templateTileContentRendered.value,
refreshInterval = settingsWearViewModel.templateTileRefreshInterval.value,
onContentChanged = {
settingsWearViewModel.setTemplateContent(it)
settingsWearViewModel.sendTemplateTileInfo()
},
onRefreshIntervalChanged = {
settingsWearViewModel.templateTileRefreshInterval.value = it
settingsWearViewModel.sendTemplateTileInfo()
composable(SettingsWearMainView.TEMPLATES) {
SettingsWearTemplateTileList(
templateTiles = settingsWearViewModel.templateTiles,
onTemplateTileClicked = { tileId ->
navController.navigate(SettingsWearMainView.TEMPLATE_TILE.format(tileId))
},
onBackClicked = {
navController.navigateUp()
}
)
}
composable(
route = SettingsWearMainView.TEMPLATE_TILE.format("{tileId}"),
arguments = listOf(navArgument("tileId") { type = NavType.IntType })
) { backStackEntry ->
val tileId = backStackEntry.arguments?.getInt("tileId")
val templateTile = settingsWearViewModel.templateTiles[tileId]
val renderedTemplate = settingsWearViewModel.templateTilesRenderedTemplates[tileId]
templateTile?.let {
SettingsWearTemplateTile(
template = it.template,
renderedTemplate = renderedTemplate ?: "",
refreshInterval = it.refreshInterval,
onContentChanged = { templateContent ->
settingsWearViewModel.setTemplateTileContent(tileId!!, templateContent)
settingsWearViewModel.sendTemplateTileInfo()
},
onRefreshIntervalChanged = { refreshInterval ->
settingsWearViewModel.setTemplateTileRefreshInterval(tileId!!, refreshInterval)
settingsWearViewModel.sendTemplateTileInfo()
},
onBackClicked = {
navController.navigateUp()
}
)
}
}
}
}
}

View File

@ -87,7 +87,7 @@ fun SettingWearLandingView(
onClicked = navigateFavorites
)
SettingsRow(
primaryText = stringResource(commonR.string.template_tile),
primaryText = stringResource(commonR.string.template_tiles),
secondaryText = stringResource(commonR.string.template_tile_set_on_watch),
mdiIcon = CommunityMaterial.Icon3.cmd_text_box,
enabled = true,

View File

@ -32,7 +32,8 @@ class SettingsWearMainView : AppCompatActivity() {
private var registerUrl: String? = null
const val LANDING = "Landing"
const val FAVORITES = "Favorites"
const val TEMPLATE = "Template"
const val TEMPLATES = "Templates"
const val TEMPLATE_TILE = "Template/%s"
fun newInstance(context: Context, wearNodes: Set<Node>, url: String?): Intent {
currentNodes = wearNodes

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
@ -141,3 +142,16 @@ private fun parseHtml(renderedText: String) = buildAnnotatedString {
}
}
}
@Preview
@Composable
private fun PreviewSettingsWearTemplateTile() {
SettingsWearTemplateTile(
template = "Example entity: {{ states('sensor.example_entity') }}",
renderedTemplate = "Example entity: Lorem ipsum",
refreshInterval = 300,
onContentChanged = {},
onRefreshIntervalChanged = {},
onBackClicked = {}
)
}

View File

@ -0,0 +1,117 @@
package io.homeassistant.companion.android.settings.wear.views
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.settings.views.SettingsRow
import io.homeassistant.companion.android.common.R as commonR
@Composable
fun SettingsWearTemplateTileList(
templateTiles: Map<Int, TemplateTileConfig>,
onTemplateTileClicked: (tileId: Int) -> Unit,
onBackClicked: () -> Unit
) {
Scaffold(
topBar = {
SettingsWearTopAppBar(
title = { Text(stringResource(commonR.string.template_tiles)) },
onBackClicked = onBackClicked,
docsLink = WEAR_DOCS_LINK
)
}
) { padding ->
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(padding)
) {
if (templateTiles.entries.isEmpty()) {
Text(
text = stringResource(commonR.string.template_tile_no_tiles_yet),
modifier = Modifier
.padding(all = 16.dp)
)
} else {
Row(
modifier = Modifier
.height(48.dp)
.padding(start = 72.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = commonR.string.template_tile_configure),
style = MaterialTheme.typography.body2,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary
)
}
var index = 1
for (templateTileEntry in templateTiles.entries) {
val template = templateTileEntry.value.template
SettingsRow(
primaryText = stringResource(commonR.string.template_tile_n, index++),
secondaryText = when {
template.length <= 100 -> template
else -> "${template.take(100)}"
},
mdiIcon = CommunityMaterial.Icon3.cmd_text_box,
enabled = true,
onClicked = { onTemplateTileClicked(templateTileEntry.key) }
)
}
}
}
}
}
@Preview
@Composable
private fun PreviewSettingsWearTemplateTileList() {
SettingsWearTemplateTileList(
templateTiles = mapOf(
123 to TemplateTileConfig("Example entity 1: {{ states('sensor.example_entity_1') }}", 300),
51468 to TemplateTileConfig("Example entity 2: {{ states('sensor.example_entity_2') }}", 0)
),
onTemplateTileClicked = {},
onBackClicked = {}
)
}
@Preview
@Composable
private fun PreviewSettingsWearTemplateSingleLegacyTile() {
SettingsWearTemplateTileList(
templateTiles = mapOf(
-1 to TemplateTileConfig("Example entity 1: {{ states('sensor.example_entity_1') }}", 300)
),
onTemplateTileClicked = {},
onBackClicked = {}
)
}
@Preview
@Composable
private fun PreviewSettingsWearTemplateTileListEmpty() {
SettingsWearTemplateTileList(
templateTiles = mapOf(),
onTemplateTileClicked = {},
onBackClicked = {}
)
}

View File

@ -1,5 +1,7 @@
package io.homeassistant.companion.android.common.data.prefs
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
interface WearPrefsRepository {
suspend fun getAllTileShortcuts(): Map<Int?, List<String>>
suspend fun getTileShortcutsAndSaveTileId(tileId: Int): List<String>
@ -7,10 +9,11 @@ interface WearPrefsRepository {
suspend fun removeTileShortcuts(tileId: Int?): List<String>?
suspend fun getShowShortcutText(): Boolean
suspend fun setShowShortcutTextEnabled(enabled: Boolean)
suspend fun getTemplateTileContent(): String
suspend fun setTemplateTileContent(content: String)
suspend fun getTemplateTileRefreshInterval(): Int
suspend fun setTemplateTileRefreshInterval(interval: Int)
suspend fun getAllTemplateTiles(): Map<Int, TemplateTileConfig>
suspend fun getTemplateTileAndSaveTileId(tileId: Int): TemplateTileConfig
suspend fun setAllTemplateTiles(templateTiles: Map<Int, TemplateTileConfig>)
suspend fun setTemplateTile(tileId: Int, content: String, refreshInterval: Int): TemplateTileConfig
suspend fun removeTemplateTile(tileId: Int): TemplateTileConfig?
suspend fun getWearHapticFeedback(): Boolean
suspend fun setWearHapticFeedback(enabled: Boolean)
suspend fun getWearToastConfirmation(): Boolean

View File

@ -1,6 +1,7 @@
package io.homeassistant.companion.android.common.data.prefs
import io.homeassistant.companion.android.common.data.LocalStorage
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.util.toStringList
import kotlinx.coroutines.runBlocking
import org.json.JSONArray
@ -15,19 +16,23 @@ class WearPrefsRepositoryImpl @Inject constructor(
companion object {
private const val MIGRATION_PREF = "migration"
private const val MIGRATION_VERSION = 1
private const val MIGRATION_VERSION = 2
private const val PREF_TILE_SHORTCUTS = "tile_shortcuts_list"
private const val PREF_SHOW_TILE_SHORTCUTS_TEXT = "show_tile_shortcuts_text"
private const val PREF_TILE_TEMPLATE = "tile_template"
private const val PREF_TILE_TEMPLATE_REFRESH_INTERVAL = "tile_template_refresh_interval"
private const val PREF_TILE_TEMPLATES = "tile_templates"
private const val PREF_WEAR_HAPTIC_FEEDBACK = "wear_haptic_feedback"
private const val PREF_WEAR_TOAST_CONFIRMATION = "wear_toast_confirmation"
private const val PREF_WEAR_FAVORITES_ONLY = "wear_favorites_only"
private const val UNKNOWN_TEMPLATE_TILE_ID = -1
}
init {
runBlocking {
val legacyPrefTileTemplate = "tile_template"
val legacyPrefTileTemplateRefreshInterval = "tile_template_refresh_interval"
val currentVersion = localStorage.getInt(MIGRATION_PREF)
if (currentVersion == null || currentVersion < 1) {
integrationStorage.getString(PREF_TILE_SHORTCUTS)?.let {
@ -36,11 +41,11 @@ class WearPrefsRepositoryImpl @Inject constructor(
integrationStorage.getBooleanOrNull(PREF_SHOW_TILE_SHORTCUTS_TEXT)?.let {
localStorage.putBoolean(PREF_SHOW_TILE_SHORTCUTS_TEXT, it)
}
integrationStorage.getString(PREF_TILE_TEMPLATE)?.let {
localStorage.putString(PREF_TILE_TEMPLATE, it)
integrationStorage.getString(legacyPrefTileTemplate)?.let {
localStorage.putString(legacyPrefTileTemplate, it)
}
integrationStorage.getInt(PREF_TILE_TEMPLATE_REFRESH_INTERVAL)?.let {
localStorage.putInt(PREF_TILE_TEMPLATE_REFRESH_INTERVAL, it)
integrationStorage.getInt(legacyPrefTileTemplateRefreshInterval)?.let {
localStorage.putInt(legacyPrefTileTemplateRefreshInterval, it)
}
integrationStorage.getBooleanOrNull(PREF_WEAR_HAPTIC_FEEDBACK)?.let {
localStorage.putBoolean(PREF_WEAR_HAPTIC_FEEDBACK, it)
@ -51,6 +56,26 @@ class WearPrefsRepositoryImpl @Inject constructor(
localStorage.putInt(MIGRATION_PREF, MIGRATION_VERSION)
}
if (currentVersion == null || currentVersion < 2) {
val template = localStorage.getString(legacyPrefTileTemplate)
val templateRefreshInterval = localStorage.getInt(
legacyPrefTileTemplateRefreshInterval
)
if (template != null && templateRefreshInterval != null) {
val templates = mapOf(
UNKNOWN_TEMPLATE_TILE_ID.toString() to TemplateTileConfig(template, templateRefreshInterval).toJSONObject()
)
localStorage.putString(PREF_TILE_TEMPLATES, JSONObject(templates).toString())
}
localStorage.remove(legacyPrefTileTemplate)
localStorage.remove(legacyPrefTileTemplateRefreshInterval)
localStorage.putInt(MIGRATION_PREF, MIGRATION_VERSION)
}
}
}
@ -118,10 +143,6 @@ class WearPrefsRepositoryImpl @Inject constructor(
return entities
}
override suspend fun getTemplateTileContent(): String {
return localStorage.getString(PREF_TILE_TEMPLATE) ?: ""
}
override suspend fun getShowShortcutText(): Boolean {
return localStorage.getBoolean(PREF_SHOW_TILE_SHORTCUTS_TEXT)
}
@ -130,16 +151,56 @@ class WearPrefsRepositoryImpl @Inject constructor(
localStorage.putBoolean(PREF_SHOW_TILE_SHORTCUTS_TEXT, enabled)
}
override suspend fun setTemplateTileContent(content: String) {
localStorage.putString(PREF_TILE_TEMPLATE, content)
override suspend fun getAllTemplateTiles(): Map<Int, TemplateTileConfig> {
return localStorage.getString(PREF_TILE_TEMPLATES)?.let { jsonStr ->
val jsonObject = JSONObject(jsonStr)
buildMap {
jsonObject.keys().forEach { tileId ->
val id = tileId.toInt()
val templateTileConfig = TemplateTileConfig(jsonObject.getJSONObject(tileId))
put(id, templateTileConfig)
}
}
} ?: emptyMap()
}
override suspend fun getTemplateTileRefreshInterval(): Int {
return localStorage.getInt(PREF_TILE_TEMPLATE_REFRESH_INTERVAL) ?: 0
override suspend fun getTemplateTileAndSaveTileId(tileId: Int): TemplateTileConfig {
val tileIdToTemplatesMap = getAllTemplateTiles()
return if (UNKNOWN_TEMPLATE_TILE_ID in tileIdToTemplatesMap && tileId !in tileIdToTemplatesMap) {
// if there are Templates with an unknown (-1) tileId key from a previous installation,
// and the tileId parameter is not already present in the map, associate it with that Template
val templateData = removeTemplateTile(UNKNOWN_TEMPLATE_TILE_ID)!!
setTemplateTile(tileId, templateData.template, templateData.refreshInterval)
templateData
} else {
var templateData = tileIdToTemplatesMap[tileId]
if (templateData == null) {
templateData = setTemplateTile(tileId, "", 0)
}
templateData
}
}
override suspend fun setTemplateTileRefreshInterval(interval: Int) {
localStorage.putInt(PREF_TILE_TEMPLATE_REFRESH_INTERVAL, interval)
override suspend fun setAllTemplateTiles(templateTiles: Map<Int, TemplateTileConfig>) {
val templateTilesJson = templateTiles.map { (tileId, templateTileConfig) ->
tileId.toString() to templateTileConfig.toJSONObject()
}.toMap()
val jsonStr = JSONObject(templateTilesJson).toString()
localStorage.putString(PREF_TILE_TEMPLATES, jsonStr)
}
override suspend fun setTemplateTile(tileId: Int, content: String, refreshInterval: Int): TemplateTileConfig {
val templateTileConfig = TemplateTileConfig(content, refreshInterval)
val map = getAllTemplateTiles() + mapOf(tileId to templateTileConfig)
setAllTemplateTiles(map)
return templateTileConfig
}
override suspend fun removeTemplateTile(tileId: Int): TemplateTileConfig? {
val templateTilesMap = getAllTemplateTiles().toMutableMap()
val templateTile = templateTilesMap.remove(tileId)
setAllTemplateTiles(templateTilesMap)
return templateTile
}
override suspend fun getWearHapticFeedback(): Boolean {

View File

@ -0,0 +1,28 @@
package io.homeassistant.companion.android.common.data.prefs.impl.entities
import com.fasterxml.jackson.annotation.JsonProperty
import org.json.JSONObject
const val FIELD_TEMPLATE = "template"
const val FIELD_REFRESH_INTERVAL = "refresh_interval"
data class TemplateTileConfig(
@JsonProperty(value = FIELD_TEMPLATE)
val template: String,
@JsonProperty(value = FIELD_REFRESH_INTERVAL)
val refreshInterval: Int
) {
constructor(jsonObject: JSONObject) : this(
jsonObject.getString(FIELD_TEMPLATE),
jsonObject.getInt(FIELD_REFRESH_INTERVAL)
)
fun toJSONObject(): JSONObject {
return JSONObject(
mapOf(
FIELD_TEMPLATE to template,
FIELD_REFRESH_INTERVAL to refreshInterval
)
)
}
}

View File

@ -17,8 +17,7 @@ object WearDataMessages {
const val CONFIG_SERVER_REFRESH_TOKEN = "serverRefreshToken"
const val CONFIG_SUPPORTED_DOMAINS = "supportedDomains"
const val CONFIG_FAVORITES = "favorites"
const val CONFIG_TEMPLATE_TILE = "templateTile"
const val CONFIG_TEMPLATE_TILE_REFRESH_INTERVAL = "templateTileRefreshInterval"
const val CONFIG_TEMPLATE_TILES = "templateTiles"
const val LOGIN_RESULT_EXCEPTION = "exception"
}

View File

@ -846,7 +846,12 @@
<string name="tag_reader_title">Processing tag</string>
<string name="template">Template</string>
<string name="template_tile">Template tile</string>
<string name="template_tile_set_on_watch">Set template to display on Wear OS tile</string>
<string name="template_tile_n">Template tile #%d</string>
<string name="template_tiles">Template tiles</string>
<string name="template_tile_configure">Configure Template tiles</string>
<string name="template_tile_select">Select Template tile to manage</string>
<string name="template_tile_no_tiles_yet">There are no Template tiles added yet - add one from the watch face to set it up</string>
<string name="template_tile_set_on_watch">Configure templates to use as Wear OS tiles</string>
<string name="template_tile_change_message">Change template in phone settings</string>
<string name="template_tile_content">Template tile content</string>
<string name="template_tile_desc">Renders and displays a template</string>

View File

@ -97,6 +97,11 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.google.android.clockwork.tiles.category.PROVIDER_CONFIG" />
</intent-filter>
<intent-filter>
<action android:name="ConfigTemplateTile" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.google.android.clockwork.tiles.category.PROVIDER_CONFIG" />
</intent-filter>
</activity>
<!-- To show confirmations and failures -->
@ -160,6 +165,14 @@
<meta-data android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/template_tile_example" />
<meta-data
android:name="com.google.android.clockwork.tiles.MULTI_INSTANCES_SUPPORTED"
android:value="true" /> <!-- This is supported starting from Wear OS 3 -->
<meta-data
android:name="com.google.android.clockwork.tiles.PROVIDER_CONFIG_ACTION"
android:value="ConfigTemplateTile" />
</service>
<service
android:name=".tiles.ConversationTile"
@ -252,7 +265,7 @@
<data android:scheme="wear" android:host="*"
android:path="/updateFavorites" />
<data android:scheme="wear" android:host="*"
android:path="/updateTemplateTile" />
android:path="/updateTemplateTiles" />
</intent-filter>
</service>

View File

@ -13,6 +13,7 @@ import androidx.lifecycle.repeatOnLifecycle
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.home.views.DEEPLINK_PREFIX_SET_CAMERA_TILE
import io.homeassistant.companion.android.home.views.DEEPLINK_PREFIX_SET_SHORTCUT_TILE
import io.homeassistant.companion.android.home.views.DEEPLINK_PREFIX_SET_TEMPLATE_TILE
import io.homeassistant.companion.android.home.views.LoadHomePage
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.sensors.SensorReceiver
@ -57,6 +58,16 @@ class HomeActivity : ComponentActivity(), HomeView {
context,
HomeActivity::class.java
)
fun getTemplateTileSettingsIntent(
context: Context,
tileId: Int
) = Intent(
Intent.ACTION_VIEW,
"$DEEPLINK_PREFIX_SET_TEMPLATE_TILE/$tileId".toUri(),
context,
HomeActivity::class.java
)
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,6 +1,7 @@
package io.homeassistant.companion.android.home
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryUpdatedEvent
@ -48,9 +49,9 @@ interface HomePresenter {
suspend fun setWearToastConfirmation(enabled: Boolean)
suspend fun getShowShortcutText(): Boolean
suspend fun setShowShortcutTextEnabled(enabled: Boolean)
suspend fun getTemplateTileContent(): String
suspend fun getTemplateTileRefreshInterval(): Int
suspend fun setTemplateTileRefreshInterval(interval: Int)
suspend fun getAllTemplateTiles(): Map<Int, TemplateTileConfig>
suspend fun setTemplateTileRefreshInterval(tileId: Int, interval: Int)
suspend fun getWearFavoritesOnly(): Boolean
suspend fun setWearFavoritesOnly(enabled: Boolean)
}

View File

@ -7,6 +7,7 @@ import io.homeassistant.companion.android.common.data.integration.DeviceRegistra
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
@ -270,16 +271,14 @@ class HomePresenterImpl @Inject constructor(
wearPrefsRepository.setShowShortcutTextEnabled(enabled)
}
override suspend fun getTemplateTileContent(): String {
return wearPrefsRepository.getTemplateTileContent()
override suspend fun getAllTemplateTiles(): Map<Int, TemplateTileConfig> {
return wearPrefsRepository.getAllTemplateTiles()
}
override suspend fun getTemplateTileRefreshInterval(): Int {
return wearPrefsRepository.getTemplateTileRefreshInterval()
}
override suspend fun setTemplateTileRefreshInterval(interval: Int) {
wearPrefsRepository.setTemplateTileRefreshInterval(interval)
override suspend fun setTemplateTileRefreshInterval(tileId: Int, interval: Int) {
getAllTemplateTiles()[tileId]?.let {
wearPrefsRepository.setTemplateTile(tileId, it.template, interval)
}
}
override suspend fun getWearFavoritesOnly(): Boolean {

View File

@ -19,6 +19,7 @@ import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
@ -121,9 +122,7 @@ class MainViewModel @Inject constructor(
private set
var isShowShortcutTextEnabled = mutableStateOf(false)
private set
var templateTileContent = mutableStateOf("")
private set
var templateTileRefreshInterval = mutableStateOf(0)
var templateTiles = mutableStateMapOf<Int, TemplateTileConfig>()
private set
var isFavoritesOnly by mutableStateOf(false)
private set
@ -148,8 +147,8 @@ class MainViewModel @Inject constructor(
isHapticEnabled.value = homePresenter.getWearHapticFeedback()
isToastEnabled.value = homePresenter.getWearToastConfirmation()
isShowShortcutTextEnabled.value = homePresenter.getShowShortcutText()
templateTileContent.value = homePresenter.getTemplateTileContent()
templateTileRefreshInterval.value = homePresenter.getTemplateTileRefreshInterval()
templateTiles.clear()
templateTiles.putAll(homePresenter.getAllTemplateTiles())
isFavoritesOnly = homePresenter.getWearFavoritesOnly()
val assistantAppComponent = ComponentName(
@ -171,6 +170,13 @@ class MainViewModel @Inject constructor(
}
}
fun loadTemplateTiles() {
viewModelScope.launch {
templateTiles.clear()
templateTiles.putAll(homePresenter.getAllTemplateTiles())
}
}
fun loadEntities() {
if (!homePresenter.isConnected()) return
viewModelScope.launch {
@ -485,10 +491,12 @@ class MainViewModel @Inject constructor(
}
}
fun setTemplateTileRefreshInterval(interval: Int) {
fun setTemplateTileRefreshInterval(tileId: Int, interval: Int) {
viewModelScope.launch {
homePresenter.setTemplateTileRefreshInterval(interval)
templateTileRefreshInterval.value = interval
homePresenter.setTemplateTileRefreshInterval(tileId, interval)
templateTiles[tileId]?.let {
templateTiles[tileId] = it.copy(refreshInterval = interval)
}
}
}

View File

@ -24,6 +24,7 @@ private const val ARG_SCREEN_SENSOR_MANAGER_ID = "sensorManagerId"
private const val ARG_SCREEN_CAMERA_TILE_ID = "cameraTileId"
private const val ARG_SCREEN_SHORTCUTS_TILE_ID = "shortcutsTileId"
private const val ARG_SCREEN_SHORTCUTS_TILE_ENTITY_INDEX = "shortcutsTileEntityIndex"
private const val ARG_SCREEN_TEMPLATE_TILE_ID = "templateTileId"
private const val SCREEN_LANDING = "landing"
private const val SCREEN_ENTITY_DETAIL = "entity_detail"
@ -38,7 +39,9 @@ private const val SCREEN_SET_CAMERA_TILE = "set_camera_tile"
private const val SCREEN_SET_CAMERA_TILE_ENTITY = "entity"
private const val SCREEN_SET_CAMERA_TILE_REFRESH_INTERVAL = "refresh_interval"
private const val ROUTE_SHORTCUTS_TILE = "shortcuts_tile"
private const val ROUTE_TEMPLATE_TILE = "template_tile"
private const val SCREEN_SELECT_SHORTCUTS_TILE = "select_shortcuts_tile"
private const val SCREEN_SELECT_TEMPLATE_TILE = "select_template_tile"
private const val SCREEN_SET_SHORTCUTS_TILE = "set_shortcuts_tile"
private const val SCREEN_SHORTCUTS_TILE_CHOOSE_ENTITY = "shortcuts_tile_choose_entity"
private const val SCREEN_SET_TILE_TEMPLATE = "set_tile_template"
@ -47,6 +50,7 @@ private const val SCREEN_SET_TILE_TEMPLATE_REFRESH_INTERVAL = "set_tile_template
const val DEEPLINK_SENSOR_MANAGER = "ha_wear://$SCREEN_SINGLE_SENSOR_MANAGER"
const val DEEPLINK_PREFIX_SET_CAMERA_TILE = "ha_wear://$SCREEN_SET_CAMERA_TILE"
const val DEEPLINK_PREFIX_SET_SHORTCUT_TILE = "ha_wear://$SCREEN_SET_SHORTCUTS_TILE"
const val DEEPLINK_PREFIX_SET_TEMPLATE_TILE = "ha_wear://$SCREEN_SET_TILE_TEMPLATE"
@Composable
fun LoadHomePage(
@ -161,7 +165,10 @@ fun LoadHomePage(
onClickCameraTile = {
swipeDismissableNavController.navigate("$ROUTE_CAMERA_TILE/$SCREEN_SELECT_CAMERA_TILE")
},
onClickTemplateTile = { swipeDismissableNavController.navigate(SCREEN_SET_TILE_TEMPLATE) },
onClickTemplateTiles = {
mainViewModel.loadTemplateTiles()
swipeDismissableNavController.navigate("$ROUTE_TEMPLATE_TILE/$SCREEN_SELECT_TEMPLATE_TILE")
},
onAssistantAppAllowed = mainViewModel::setAssistantApp
)
}
@ -317,21 +324,49 @@ fun LoadHomePage(
}
)
}
composable(SCREEN_SET_TILE_TEMPLATE) {
composable("$ROUTE_TEMPLATE_TILE/$SCREEN_SELECT_TEMPLATE_TILE") {
SelectTemplateTileView(
templateTiles = mainViewModel.templateTiles,
onSelectTemplateTile = { tileId ->
swipeDismissableNavController.navigate("$ROUTE_TEMPLATE_TILE/$tileId/$SCREEN_SET_TILE_TEMPLATE")
}
)
}
composable(
route = "$ROUTE_TEMPLATE_TILE/{$ARG_SCREEN_TEMPLATE_TILE_ID}/$SCREEN_SET_TILE_TEMPLATE",
arguments = listOf(
navArgument(name = ARG_SCREEN_TEMPLATE_TILE_ID) {
type = NavType.StringType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "$DEEPLINK_PREFIX_SET_TEMPLATE_TILE/{$ARG_SCREEN_TEMPLATE_TILE_ID}" }
)
) { backStackEntry ->
val tileId = backStackEntry.arguments!!.getString(ARG_SCREEN_TEMPLATE_TILE_ID)!!.toIntOrNull()
TemplateTileSettingsView(
templateContent = mainViewModel.templateTileContent.value,
refreshInterval = mainViewModel.templateTileRefreshInterval.value
templateContent = mainViewModel.templateTiles[tileId]?.template ?: "",
refreshInterval = mainViewModel.templateTiles[tileId]?.refreshInterval ?: 0
) {
swipeDismissableNavController.navigate(
SCREEN_SET_TILE_TEMPLATE_REFRESH_INTERVAL
"$ROUTE_TEMPLATE_TILE/$tileId/$SCREEN_SET_TILE_TEMPLATE_REFRESH_INTERVAL"
)
}
}
composable(SCREEN_SET_TILE_TEMPLATE_REFRESH_INTERVAL) {
composable(
route = "$ROUTE_TEMPLATE_TILE/{$ARG_SCREEN_TEMPLATE_TILE_ID}/$SCREEN_SET_TILE_TEMPLATE_REFRESH_INTERVAL",
arguments = listOf(
navArgument(name = ARG_SCREEN_TEMPLATE_TILE_ID) {
type = NavType.StringType
}
)
) { backStackEntry ->
val tileId = backStackEntry.arguments!!.getString(ARG_SCREEN_TEMPLATE_TILE_ID)!!.toInt()
RefreshIntervalPickerView(
currentInterval = mainViewModel.templateTileRefreshInterval.value
currentInterval = mainViewModel.templateTiles[tileId]?.refreshInterval ?: 0
) {
mainViewModel.setTemplateTileRefreshInterval(it)
mainViewModel.setTemplateTileRefreshInterval(tileId, it)
TileService.getUpdater(context).requestUpdate(TemplateTile::class.java)
swipeDismissableNavController.navigateUp()
}

View File

@ -0,0 +1,74 @@
package io.homeassistant.companion.android.home.views
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.wear.compose.foundation.lazy.itemsIndexed
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.Text
import androidx.wear.tooling.preview.devices.WearDevices
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.theme.getFilledTonalButtonColors
import io.homeassistant.companion.android.views.ListHeader
import io.homeassistant.companion.android.views.ThemeLazyColumn
import io.homeassistant.companion.android.common.R as commonR
@Composable
fun SelectTemplateTileView(
templateTiles: Map<Int, TemplateTileConfig>,
onSelectTemplateTile: (tileId: Int) -> Unit
) {
WearAppTheme {
ThemeLazyColumn {
item {
ListHeader(id = commonR.string.template_tiles)
}
if (templateTiles.isEmpty()) {
item {
Text(
text = stringResource(commonR.string.template_tile_no_tiles_yet),
textAlign = TextAlign.Center
)
}
} else {
itemsIndexed(templateTiles.keys.toList()) { index, templateTileId ->
Button(
modifier = Modifier
.fillMaxWidth(),
label = {
Text(stringResource(commonR.string.template_tile_n, index + 1))
},
onClick = { onSelectTemplateTile(templateTileId) },
colors = getFilledTonalButtonColors()
)
}
}
}
}
}
@Preview(device = WearDevices.LARGE_ROUND)
@Composable
private fun PreviewSelectTemplateTileView() {
SelectTemplateTileView(
templateTiles = mapOf(
-1 to TemplateTileConfig("Old template", 0),
1111 to TemplateTileConfig("New template #1", 10),
2222 to TemplateTileConfig("New template #2", 20)
),
onSelectTemplateTile = {}
)
}
@Preview(device = WearDevices.LARGE_ROUND)
@Composable
private fun PreviewSelectTemplateTileEmptyView() {
SelectTemplateTileView(
templateTiles = mapOf(),
onSelectTemplateTile = {}
)
}

View File

@ -72,7 +72,7 @@ fun SettingsView(
onToastEnabled: (Boolean) -> Unit,
setFavoritesOnly: (Boolean) -> Unit,
onClickCameraTile: () -> Unit,
onClickTemplateTile: () -> Unit,
onClickTemplateTiles: () -> Unit,
onAssistantAppAllowed: (Boolean) -> Unit
) {
WearAppTheme {
@ -187,8 +187,8 @@ fun SettingsView(
item {
SecondarySettingsChip(
icon = CommunityMaterial.Icon3.cmd_text_box,
label = stringResource(commonR.string.template_tile),
onClick = onClickTemplateTile
label = stringResource(commonR.string.template_tiles),
onClick = onClickTemplateTiles
)
}
item {
@ -272,7 +272,7 @@ private fun PreviewSettingsView() {
onToastEnabled = {},
setFavoritesOnly = {},
onClickCameraTile = {},
onClickTemplateTile = {},
onClickTemplateTiles = {},
onAssistantAppAllowed = {}
)
}

View File

@ -21,6 +21,7 @@ import io.homeassistant.companion.android.common.data.integration.DeviceRegistra
import io.homeassistant.companion.android.common.data.keychain.KeyChainRepository
import io.homeassistant.companion.android.common.data.keychain.KeyStoreRepositoryImpl
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.util.WearDataMessages
import io.homeassistant.companion.android.database.server.Server
@ -103,8 +104,7 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
}
dataMap.putString(WearDataMessages.CONFIG_SUPPORTED_DOMAINS, objectMapper.writeValueAsString(HomePresenterImpl.supportedDomains))
dataMap.putString(WearDataMessages.CONFIG_FAVORITES, objectMapper.writeValueAsString(currentFavorites))
dataMap.putString(WearDataMessages.CONFIG_TEMPLATE_TILE, wearPrefsRepository.getTemplateTileContent())
dataMap.putInt(WearDataMessages.CONFIG_TEMPLATE_TILE_REFRESH_INTERVAL, wearPrefsRepository.getTemplateTileRefreshInterval())
dataMap.putString(WearDataMessages.CONFIG_TEMPLATE_TILES, objectMapper.writeValueAsString(wearPrefsRepository.getAllTemplateTiles()))
setUrgent()
asPutDataRequest()
}
@ -129,8 +129,8 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
"/updateFavorites" -> {
saveFavorites(DataMapItem.fromDataItem(item).dataMap)
}
"/updateTemplateTile" -> {
saveTileTemplate(DataMapItem.fromDataItem(item).dataMap)
"/updateTemplateTiles" -> {
saveTemplateTiles(DataMapItem.fromDataItem(item).dataMap)
}
}
}
@ -246,11 +246,15 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
}
}
private fun saveTileTemplate(dataMap: DataMap) = mainScope.launch {
val content = dataMap.getString(WearDataMessages.CONFIG_TEMPLATE_TILE, "")
val interval = dataMap.getInt(WearDataMessages.CONFIG_TEMPLATE_TILE_REFRESH_INTERVAL, 0)
wearPrefsRepository.setTemplateTileContent(content)
wearPrefsRepository.setTemplateTileRefreshInterval(interval)
private fun saveTemplateTiles(dataMap: DataMap) = mainScope.launch {
val templateTilesFromPhone: Map<Int, TemplateTileConfig> = objectMapper.readValue(
dataMap.getString(
WearDataMessages.CONFIG_TEMPLATE_TILES,
"{}"
)
)
wearPrefsRepository.setAllTemplateTiles(templateTilesFromPhone)
}
private fun updateTiles() = mainScope.launch {

View File

@ -34,6 +34,15 @@ class OpenTileSettingsActivity : AppCompatActivity() {
tileId = it
)
}
"ConfigTemplateTile" -> {
lifecycleScope.launch {
wearPrefsRepository.getTemplateTileAndSaveTileId(tileId)
}
HomeActivity.getTemplateTileSettingsIntent(
context = this,
tileId = it
)
}
else -> null
}

View File

@ -19,6 +19,7 @@ import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
import androidx.wear.protolayout.ResourceBuilders
import androidx.wear.protolayout.ResourceBuilders.Resources
import androidx.wear.protolayout.TimelineBuilders.Timeline
import androidx.wear.tiles.EventBuilders
import androidx.wear.tiles.RequestBuilders.ResourcesRequest
import androidx.wear.tiles.RequestBuilders.TileRequest
import androidx.wear.tiles.TileBuilders.Tile
@ -28,11 +29,14 @@ import com.google.common.util.concurrent.ListenableFuture
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
import io.homeassistant.companion.android.common.data.prefs.impl.entities.TemplateTileConfig
import io.homeassistant.companion.android.common.data.servers.ServerManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.guava.future
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import javax.inject.Inject
import io.homeassistant.companion.android.common.R as commonR
@ -53,14 +57,17 @@ class TemplateTile : TileService() {
if (wearPrefsRepository.getWearHapticFeedback()) hapticClick(applicationContext)
}
val tileId = requestParams.tileId
val templateTileConfig = getTemplateTileConfig(tileId)
Tile.Builder()
.setResourcesVersion("1")
.setFreshnessIntervalMillis(
wearPrefsRepository.getTemplateTileRefreshInterval().toLong() * 1000
templateTileConfig.refreshInterval.toLong() * 1_000
)
.setTileTimeline(
if (serverManager.isRegistered()) {
timeline()
timeline(templateTileConfig)
} else {
loggedOutTimeline(
this@TemplateTile,
@ -88,17 +95,43 @@ class TemplateTile : TileService() {
.build()
}
override fun onTileAddEvent(requestParams: EventBuilders.TileAddEvent): Unit = runBlocking {
withContext(Dispatchers.IO) {
/**
* When the app is updated from an older version (which only supported a single Template Tile),
* and the user is adding a new Template Tile, we can't tell for sure if it's the 1st or 2nd Tile.
* Even though we may have the Template tile config stored in the prefs, it doesn't guarantee that
* the tile was actually added to the Tiles carousel.
* The [WearPrefsRepositoryImpl::getTemplateTileAndSaveTileId] method will handle both of the following cases:
* 1. There was no Tile added, but there was a Template tile config stored in the prefs.
* In this case, the stored config will be associated to the new tileId.
* 2. There was a single Tile added, and there was a Template tile config stored in the prefs.
* If there was a Tile update since updating the app, the tileId will be already
* associated to the config, because it also calls [getTemplateTileAndSaveTileId].
* If there was no Tile update yet, the new Tile will "steal" the config from the existing Tile,
* and the old Tile will behave as it is the new Tile. This is needed because
* we don't know if it's the 1st or 2nd Tile.
*/
wearPrefsRepository.getTemplateTileAndSaveTileId(requestParams.tileId)
}
}
override fun onTileRemoveEvent(requestParams: EventBuilders.TileRemoveEvent): Unit = runBlocking {
withContext(Dispatchers.IO) {
wearPrefsRepository.removeTemplateTile(requestParams.tileId)
}
}
override fun onDestroy() {
super.onDestroy()
// Cleans up the coroutine
serviceJob.cancel()
}
private suspend fun timeline(): Timeline {
val template = wearPrefsRepository.getTemplateTileContent()
private suspend fun timeline(templateTileConfig: TemplateTileConfig): Timeline {
val renderedText = try {
if (serverManager.isRegistered()) {
serverManager.integrationRepository().renderTemplate(template, mapOf()).toString()
serverManager.integrationRepository().renderTemplate(templateTileConfig.template, mapOf()).toString()
} else {
""
}
@ -115,6 +148,10 @@ class TemplateTile : TileService() {
return Timeline.fromLayoutElement(layout(renderedText))
}
private suspend fun getTemplateTileConfig(tileId: Int): TemplateTileConfig {
return wearPrefsRepository.getTemplateTileAndSaveTileId(tileId)
}
fun layout(renderedText: String): LayoutElement = Box.Builder().apply {
if (renderedText.isEmpty()) {
addContent(