From c9225b763139f0b4eda6dde49c84239d7b49e64e Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 21 May 2025 15:12:10 +0100 Subject: [PATCH 01/10] Add padding to avoid overwriting TimeText --- .../com/example/wear/snippets/m3/navigation/Navigation.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt index c595c54ef..83f0f58d0 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt @@ -72,10 +72,13 @@ fun MessageDetail(id: String) { ScreenScaffold( scrollState = scrollState, contentPadding = padding - ) { + ) { scaffoldPaddingValues -> // Screen content goes here // [END android_wear_navigation] - TransformingLazyColumn(state = scrollState) { + TransformingLazyColumn( + state = scrollState, + contentPadding = scaffoldPaddingValues + ) { item { Text( text = id, From 0df339bd01a57d8c46db8cd0c8d5ddbab3419e9d Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 21 May 2025 15:14:57 +0100 Subject: [PATCH 02/10] Snippets for interactive tiles --- wear/src/main/AndroidManifest.xml | 64 +++++++ .../example/wear/snippets/m3/MainActivity.kt | 22 ++- .../example/wear/snippets/m3/TileActivity.kt | 36 ++++ .../wear/snippets/m3/navigation/Navigation.kt | 18 +- .../wear/snippets/m3/tile/Interaction.kt | 165 ++++++++++++++++++ 5 files changed, 293 insertions(+), 12 deletions(-) create mode 100644 wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt create mode 100644 wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index c2740b2dc..3b959d4e2 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -35,6 +35,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt index 52e9c2eb7..dca6dad15 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt @@ -20,20 +20,26 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable +import androidx.wear.tiles.TileService import com.example.wear.snippets.m3.list.ComposeList +import com.example.wear.snippets.m3.navigation.navigation +// [START android_wear_m3_interactions_launchaction_activity] class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - setContent { - WearApp() - } - } + val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) + // clickableId will be "foo" when launched from the tile + // InteractionLaunchAction + + setContent { WearApp() } + } } +// [END android_wear_m3_interactions_launchaction_activity] @Composable fun WearApp() { - // insert here the snippet you want to test - ComposeList() + // insert here the snippet you want to test + navigation() } diff --git a/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt new file mode 100644 index 000000000..fccf15927 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.wear.snippets.m3 + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.wear.tiles.TileService +import com.example.wear.snippets.m3.navigation.navigation + +class TileActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) + // clickableId will be "foo" when launched from the tile + // InteractionLaunchAction + + setContent { navigation() } + } +} diff --git a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt index 83f0f58d0..faf891247 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt @@ -22,6 +22,7 @@ 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.navigation.navDeepLink import androidx.wear.compose.foundation.lazy.TransformingLazyColumn import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState import androidx.wear.compose.material3.AppScaffold @@ -47,13 +48,22 @@ fun navigation() { navController = navController, startDestination = "message_list" ) { - composable("message_list") { + composable( + route ="message_list", + deepLinks = listOf(navDeepLink { + uriPattern = "googleandroidsnippets://app/message_list" + })) { MessageList(onMessageClick = { id -> navController.navigate("message_detail/$id") }) } - composable("message_detail/{id}") { - MessageDetail(id = it.arguments?.getString("id")!!) + composable( + route = "message_detail/{id}", + deepLinks = listOf(navDeepLink { + uriPattern = "googleandroidsnippets://app/message_detail/{id}" + })) { + val id = it.arguments?.getString("id") ?: 0 + MessageDetail(id = "message $id") } } } @@ -81,7 +91,7 @@ fun MessageDetail(id: String) { ) { item { Text( - text = id, + text = "Detail $id", textAlign = TextAlign.Center, modifier = Modifier.fillMaxSize() ) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt new file mode 100644 index 000000000..8be137c6b --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.wear.snippets.m3.tile + +import android.app.Activity +import android.content.ComponentName +import android.content.Intent +import android.util.Log +import androidx.core.app.TaskStackBuilder +import androidx.core.net.toUri +import androidx.wear.protolayout.ActionBuilders.launchAction +import androidx.wear.protolayout.LayoutElementBuilders +import androidx.wear.protolayout.ResourceBuilders.Resources +import androidx.wear.protolayout.TimelineBuilders.Timeline +import androidx.wear.protolayout.material3.MaterialScope +import androidx.wear.protolayout.material3.Typography.BODY_LARGE +import androidx.wear.protolayout.material3.materialScope +import androidx.wear.protolayout.material3.primaryLayout +import androidx.wear.protolayout.material3.text +import androidx.wear.protolayout.material3.textButton +import androidx.wear.protolayout.modifiers.clickable +import androidx.wear.protolayout.modifiers.loadAction +import androidx.wear.protolayout.types.layoutString +import androidx.wear.tiles.RequestBuilders +import androidx.wear.tiles.RequestBuilders.ResourcesRequest +import androidx.wear.tiles.TileBuilders.Tile +import androidx.wear.tiles.TileService +import com.example.wear.snippets.m3.MainActivity +import com.example.wear.snippets.m3.TileActivity +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture +import kotlin.jvm.java + +private const val RESOURCES_VERSION = "1" + +abstract class BaseTileService : TileService() { + + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture = + Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() + ) + + override fun onTileResourcesRequest( + requestParams: ResourcesRequest + ): ListenableFuture = + Futures.immediateFuture( + Resources.Builder().setVersion(requestParams.version).build() + ) + + abstract fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ): LayoutElementBuilders.LayoutElement +} + +class HelloTileService : BaseTileService() { + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = primaryLayout(mainSlot = { text("Hello, World!".layoutString) }) +} + +class InteractionDeepLink : TileService() { + + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture { + val lastClickableId = requestParams.currentState.lastClickableId + if (lastClickableId == "foo") { + TaskStackBuilder.create(this) + .addNextIntentWithParentStack( + Intent( + Intent.ACTION_VIEW, + "googleandroidsnippets://app/message_detail/1".toUri(), + this, + TileActivity::class.java + ) + ) + .startActivities() + } + return Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() + ) + } + + override fun onTileResourcesRequest( + requestParams: ResourcesRequest + ): ListenableFuture = + Futures.immediateFuture( + Resources.Builder().setVersion(requestParams.version).build() + ) + + fun MaterialScope.tileLayout(requestParams: RequestBuilders.TileRequest) = + primaryLayout( + mainSlot = { + textButton( + labelContent = { + text("Message 1".layoutString, typography = BODY_LARGE) + }, + onClick = clickable(id = "foo", action = loadAction()), + ) + } + ) +} + +class InteractionLaunchAction : BaseTileService() { + + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + // [START android_wear_m3_interactions_launchaction_tile] + primaryLayout( + mainSlot = { + textButton( + labelContent = { + text("Tap me!".layoutString, typography = BODY_LARGE) + }, + onClick = + clickable( + id = "foo", + action = + launchAction( + ComponentName( + "com.example.wear", + "com.example.wear.snippets.m3.TileActivity", + ) + ), + ), + ) + } + ) + // [END android_wear_m3_interactions_launchaction_tile] +} From 2a5a90f2b4037cbf1e62efca664be9861023e578 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 21 May 2025 16:49:59 +0100 Subject: [PATCH 03/10] Updates --- wear/src/main/AndroidManifest.xml | 2 +- .../example/wear/snippets/m3/MainActivity.kt | 2 +- .../example/wear/snippets/m3/TileActivity.kt | 36 ---- .../wear/snippets/m3/tile/Interaction.kt | 6 +- .../wear/snippets/m3/tile/TileActivity.kt | 178 ++++++++++++++++++ wear/src/main/res/values/strings.xml | 1 + 6 files changed, 182 insertions(+), 43 deletions(-) delete mode 100644 wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt create mode 100644 wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 3b959d4e2..24aac8816 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ diff --git a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt index dca6dad15..75f68c7d0 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt @@ -41,5 +41,5 @@ class MainActivity : ComponentActivity() { @Composable fun WearApp() { // insert here the snippet you want to test - navigation() + ComposeList() } diff --git a/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt deleted file mode 100644 index fccf15927..000000000 --- a/wear/src/main/java/com/example/wear/snippets/m3/TileActivity.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.wear.snippets.m3 - -import android.os.Bundle -import android.util.Log -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.wear.tiles.TileService -import com.example.wear.snippets.m3.navigation.navigation - -class TileActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) - // clickableId will be "foo" when launched from the tile - // InteractionLaunchAction - - setContent { navigation() } - } -} diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt index 8be137c6b..e825a02a1 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -16,10 +16,8 @@ package com.example.wear.snippets.m3.tile -import android.app.Activity import android.content.ComponentName import android.content.Intent -import android.util.Log import androidx.core.app.TaskStackBuilder import androidx.core.net.toUri import androidx.wear.protolayout.ActionBuilders.launchAction @@ -39,8 +37,6 @@ import androidx.wear.tiles.RequestBuilders import androidx.wear.tiles.RequestBuilders.ResourcesRequest import androidx.wear.tiles.TileBuilders.Tile import androidx.wear.tiles.TileService -import com.example.wear.snippets.m3.MainActivity -import com.example.wear.snippets.m3.TileActivity import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import kotlin.jvm.java @@ -154,7 +150,7 @@ class InteractionLaunchAction : BaseTileService() { launchAction( ComponentName( "com.example.wear", - "com.example.wear.snippets.m3.TileActivity", + "com.example.wear.snippets.m3.tile.TileActivity", ) ), ), diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt new file mode 100644 index 000000000..17df70723 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.wear.snippets.m3.tile + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +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.navigation.navDeepLink +import androidx.wear.compose.foundation.lazy.TransformingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState +import androidx.wear.compose.material3.AppScaffold +import androidx.wear.compose.material3.Button +import androidx.wear.compose.material3.ListHeader +import androidx.wear.compose.material3.ScreenScaffold +import androidx.wear.compose.material3.Text +import androidx.wear.compose.navigation.SwipeDismissableNavHost +import androidx.wear.compose.navigation.composable +import androidx.wear.compose.navigation.rememberSwipeDismissableNavController +import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices +import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales +import androidx.wear.protolayout.types.layoutString +import androidx.wear.tiles.TileService +import com.example.wear.R +import com.google.android.horologist.compose.layout.ColumnItemType +import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding + +class TileActivity : ComponentActivity() { +// [START android_wear_m3_interactions_launchaction_activity] + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) + // clickableId will be "foo" when launched from the tile + // InteractionLaunchAction + + setContent { navigation() } + } +} +// [END android_wear_m3_interactions_launchaction_activity] + +@Composable +fun navigation() { + AppScaffold { + val navController = rememberSwipeDismissableNavController() + SwipeDismissableNavHost( + navController = navController, + startDestination = "message_list", + ) { + composable( + route = "message_list", + deepLinks = + listOf( + navDeepLink { + uriPattern = "googleandroidsnippets://app/message_list" + } + ), + ) { + MessageList( + onMessageClick = { id -> + navController.navigate("message_detail/$id") + } + ) + } + composable( + route = "message_detail/{id}", + deepLinks = + listOf( + navDeepLink { + uriPattern = "googleandroidsnippets://app/message_detail/{id}" + } + ), + ) { + val id = it.arguments?.getString("id") ?: "0" + MessageDetails(details = "message $id") + } + } + } +} + +// Implementation of one of the screens in the navigation +@Composable +fun MessageDetails(details: String) { + val scrollState = rememberTransformingLazyColumnState() + + val padding = rememberResponsiveColumnPadding(first = ColumnItemType.BodyText) + + ScreenScaffold(scrollState = scrollState, contentPadding = padding) { + scaffoldPaddingValues -> + TransformingLazyColumn( + state = scrollState, + contentPadding = scaffoldPaddingValues, + ) { + item { + ListHeader() { Text(text = stringResource(R.string.message_detail)) } + } + item { + Text( + text = details, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxSize(), + ) + } + } + } +} + +@Composable +fun MessageList(onMessageClick: (String) -> Unit) { + val scrollState = rememberTransformingLazyColumnState() + + val padding = + rememberResponsiveColumnPadding( + first = ColumnItemType.ListHeader, + last = ColumnItemType.Button, + ) + + ScreenScaffold(scrollState = scrollState, contentPadding = padding) { + contentPadding -> + TransformingLazyColumn( + state = scrollState, + contentPadding = contentPadding, + ) { + item { + ListHeader() { Text(text = stringResource(R.string.message_list)) } + } + item { + Button( + onClick = { onMessageClick("message1") }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "Message 1") + } + } + item { + Button( + onClick = { onMessageClick("message2") }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "Message 2") + } + } + } + } +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun MessageDetailPreview() { + MessageDetails("message 7") +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun MessageListPreview() { + MessageList(onMessageClick = {}) +} diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index fc59c67b8..90f5cb258 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -3,6 +3,7 @@ Voice Input Voice Text Entry Message List + Message Detail Hello Tile Hello Tile Description \ No newline at end of file From 40c30b0eca7d20312993d2dcd5fe47fb435d0abe Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 21 May 2025 16:54:11 +0100 Subject: [PATCH 04/10] Updates --- .../example/wear/snippets/m3/MainActivity.kt | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt index 75f68c7d0..52e9c2eb7 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/MainActivity.kt @@ -20,26 +20,20 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable -import androidx.wear.tiles.TileService import com.example.wear.snippets.m3.list.ComposeList -import com.example.wear.snippets.m3.navigation.navigation -// [START android_wear_m3_interactions_launchaction_activity] class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) - // clickableId will be "foo" when launched from the tile - // InteractionLaunchAction - - setContent { WearApp() } - } + setContent { + WearApp() + } + } } -// [END android_wear_m3_interactions_launchaction_activity] @Composable fun WearApp() { - // insert here the snippet you want to test - ComposeList() + // insert here the snippet you want to test + ComposeList() } From 773abd328eb4630448bd1ae1f91e03e58151f5c9 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Fri, 23 May 2025 17:29:17 +0100 Subject: [PATCH 05/10] Updates --- wear/src/main/AndroidManifest.xml | 30 +++++ .../wear/snippets/m3/tile/Interaction.kt | 120 ++++++++++++++++-- .../wear/snippets/m3/tile/TileActivity.kt | 19 ++- 3 files changed, 153 insertions(+), 16 deletions(-) diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 24aac8816..c7209c3a7 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -133,6 +133,36 @@ android:resource="@drawable/tile_preview" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt index e825a02a1..8eb587e68 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -18,12 +18,18 @@ package com.example.wear.snippets.m3.tile import android.content.ComponentName import android.content.Intent +import android.util.Log import androidx.core.app.TaskStackBuilder import androidx.core.net.toUri +import androidx.wear.protolayout.ActionBuilders import androidx.wear.protolayout.ActionBuilders.launchAction import androidx.wear.protolayout.LayoutElementBuilders import androidx.wear.protolayout.ResourceBuilders.Resources import androidx.wear.protolayout.TimelineBuilders.Timeline +import androidx.wear.protolayout.expression.dynamicDataMapOf +import androidx.wear.protolayout.expression.intAppDataKey +import androidx.wear.protolayout.expression.mapTo +import androidx.wear.protolayout.expression.stringAppDataKey import androidx.wear.protolayout.material3.MaterialScope import androidx.wear.protolayout.material3.Typography.BODY_LARGE import androidx.wear.protolayout.material3.materialScope @@ -39,7 +45,7 @@ import androidx.wear.tiles.TileBuilders.Tile import androidx.wear.tiles.TileService import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import kotlin.jvm.java +import kotlin.random.Random private const val RESOURCES_VERSION = "1" @@ -79,8 +85,31 @@ class HelloTileService : BaseTileService() { ) = primaryLayout(mainSlot = { text("Hello, World!".layoutString) }) } +class InteractionRefresh : BaseTileService() { + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + primaryLayout( + // Output a debug code so we can see the layout changing + titleSlot = { + text( + String.format("Debug %06d", Random.nextInt(0, 1_000_000)).layoutString + ) + }, + mainSlot = { + // [START android_wear_m3_interaction_refresh] + textButton( + onClick = clickable(loadAction()), + labelContent = { text("Refresh".layoutString) }, + ) + // [END android_wear_m3_interaction_refresh] + }, + ) +} + class InteractionDeepLink : TileService() { + // [START android_wear_m3_interaction_deeplink_tile] override fun onTileRequest( requestParams: RequestBuilders.TileRequest ): ListenableFuture { @@ -92,11 +121,13 @@ class InteractionDeepLink : TileService() { Intent.ACTION_VIEW, "googleandroidsnippets://app/message_detail/1".toUri(), this, - TileActivity::class.java + TileActivity::class.java, ) ) .startActivities() } + // ... User didn't tap a button (either first load or tapped somewhere else) + // [START_EXCLUDE] return Futures.immediateFuture( Tile.Builder() .setResourcesVersion(RESOURCES_VERSION) @@ -109,7 +140,9 @@ class InteractionDeepLink : TileService() { ) .build() ) + // [END_EXCLUDE] } + // [END android_wear_m3_interaction_deeplink_tile] override fun onTileResourcesRequest( requestParams: ResourcesRequest @@ -121,41 +154,110 @@ class InteractionDeepLink : TileService() { fun MaterialScope.tileLayout(requestParams: RequestBuilders.TileRequest) = primaryLayout( mainSlot = { + // [START android_wear_m3_interaction_deeplink_layout] textButton( labelContent = { - text("Message 1".layoutString, typography = BODY_LARGE) + text("Deep Link me!".layoutString, typography = BODY_LARGE) }, onClick = clickable(id = "foo", action = loadAction()), ) + // [END android_wear_m3_interaction_deeplink_layout] } ) } +class InteractionLoadAction : BaseTileService() { + + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture { + + val name: String? + val age: Int? + + // When triggered by loadAction(), "name" will be "Javier", and "age" will + // be 37. + with(requestParams.currentState.stateMap) { + name = this[stringAppDataKey("name")] + age = this[intAppDataKey("age")] + } + + Log.d("qqqqqq", "name = $name, age = $age") + + return Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() + ) + } + + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + primaryLayout( + // Output a debug code so we can see the layout changing + titleSlot = { + text( + String.format("Debug %06d", Random.nextInt(0, 1_000_000)).layoutString + ) + }, + mainSlot = { + // [START android_wear_m3_interaction_loadaction_layout] + textButton( + labelContent = { + text("loadAction()".layoutString, typography = BODY_LARGE) + }, + onClick = + clickable( + action = + loadAction( + dynamicDataMapOf( + stringAppDataKey("name") mapTo "Javier", + intAppDataKey("age") mapTo 37, + ) + ) + ), + ) + // [END android_wear_m3_interaction_loadaction_layout] + }, + ) +} + class InteractionLaunchAction : BaseTileService() { override fun MaterialScope.tileLayout( requestParams: RequestBuilders.TileRequest ) = - // [START android_wear_m3_interactions_launchaction_tile] primaryLayout( mainSlot = { + // [START android_wear_m3_interactions_launchaction] textButton( labelContent = { - text("Tap me!".layoutString, typography = BODY_LARGE) + text("launchAction()".layoutString, typography = BODY_LARGE) }, onClick = clickable( - id = "foo", action = launchAction( ComponentName( "com.example.wear", "com.example.wear.snippets.m3.tile.TileActivity", - ) - ), + ), + mapOf( + "name" to ActionBuilders.stringExtra("Bartholomew"), + "age" to ActionBuilders.intExtra(21), + ), + ) ), ) + // [END android_wear_m3_interactions_launchaction] } ) - // [END android_wear_m3_interactions_launchaction_tile] } diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt index 17df70723..813dee3a5 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt @@ -38,34 +38,37 @@ import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales -import androidx.wear.protolayout.types.layoutString -import androidx.wear.tiles.TileService import com.example.wear.R import com.google.android.horologist.compose.layout.ColumnItemType import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding class TileActivity : ComponentActivity() { -// [START android_wear_m3_interactions_launchaction_activity] + // [START android_wear_m3_interactions_launchaction_activity] override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val clickableId = intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID) - // clickableId will be "foo" when launched from the tile - // InteractionLaunchAction + // When this activity is launched from the tile InteractionLaunchAction, + // "name" will be "Bartholomew" and "age" will be 21 + val name = intent.getStringExtra("name") + val age = intent.getStringExtra("age") + // [START_EXCLUDE] setContent { navigation() } + // [END_EXCLUDE] } } // [END android_wear_m3_interactions_launchaction_activity] @Composable fun navigation() { + // [START android_wear_m3_interaction_deeplink_activity] AppScaffold { val navController = rememberSwipeDismissableNavController() SwipeDismissableNavHost( navController = navController, startDestination = "message_list", ) { + // [START_EXCLUDE] composable( route = "message_list", deepLinks = @@ -81,6 +84,7 @@ fun navigation() { } ) } + // [END_EXCLUDE] composable( route = "message_detail/{id}", deepLinks = @@ -95,6 +99,7 @@ fun navigation() { } } } + // [END android_wear_m3_interaction_deeplink_activity] } // Implementation of one of the screens in the navigation @@ -111,7 +116,7 @@ fun MessageDetails(details: String) { contentPadding = scaffoldPaddingValues, ) { item { - ListHeader() { Text(text = stringResource(R.string.message_detail)) } + ListHeader() { Text(text = stringResource(R.string.message_detail)) } } item { Text( From 92420bdb42e544ed66da1a54c6aca2571f71fcf9 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Thu, 5 Jun 2025 10:41:08 +0100 Subject: [PATCH 06/10] Updates --- .../wear/snippets/m3/tile/Interaction.kt | 18 +++++++++++++++--- .../wear/snippets/m3/tile/TileActivity.kt | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt index 8eb587e68..f8786c911 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -45,6 +45,7 @@ import androidx.wear.tiles.TileBuilders.Tile import androidx.wear.tiles.TileService import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture +import java.util.Locale import kotlin.random.Random private const val RESOURCES_VERSION = "1" @@ -93,7 +94,12 @@ class InteractionRefresh : BaseTileService() { // Output a debug code so we can see the layout changing titleSlot = { text( - String.format("Debug %06d", Random.nextInt(0, 1_000_000)).layoutString + String.format( + Locale.ENGLISH, + "Debug %06d", + Random.nextInt(0, 1_000_000), + ) + .layoutString ) }, mainSlot = { @@ -142,6 +148,7 @@ class InteractionDeepLink : TileService() { ) // [END_EXCLUDE] } + // [END android_wear_m3_interaction_deeplink_tile] override fun onTileResourcesRequest( @@ -202,10 +209,15 @@ class InteractionLoadAction : BaseTileService() { requestParams: RequestBuilders.TileRequest ) = primaryLayout( - // Output a debug code so we can see the layout changing + // Output a debug code so we can verify that the reload happens titleSlot = { text( - String.format("Debug %06d", Random.nextInt(0, 1_000_000)).layoutString + String.format( + Locale.ENGLISH, + "Debug %06d", + Random.nextInt(0, 1_000_000), + ) + .layoutString ) }, mainSlot = { diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt index 813dee3a5..5a5ef3e9d 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt @@ -53,14 +53,14 @@ class TileActivity : ComponentActivity() { val age = intent.getStringExtra("age") // [START_EXCLUDE] - setContent { navigation() } + setContent { MainContent() } // [END_EXCLUDE] } } // [END android_wear_m3_interactions_launchaction_activity] @Composable -fun navigation() { +fun MainContent() { // [START android_wear_m3_interaction_deeplink_activity] AppScaffold { val navController = rememberSwipeDismissableNavController() From 95f12662cb1077698b6cd9be9dbc82ea8585232d Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 18 Jun 2025 12:13:35 +0100 Subject: [PATCH 07/10] Revert to main version (created TileActivity instead) --- .../wear/snippets/m3/navigation/Navigation.kt | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt index faf891247..c595c54ef 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/navigation/Navigation.kt @@ -22,7 +22,6 @@ 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.navigation.navDeepLink import androidx.wear.compose.foundation.lazy.TransformingLazyColumn import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState import androidx.wear.compose.material3.AppScaffold @@ -48,22 +47,13 @@ fun navigation() { navController = navController, startDestination = "message_list" ) { - composable( - route ="message_list", - deepLinks = listOf(navDeepLink { - uriPattern = "googleandroidsnippets://app/message_list" - })) { + composable("message_list") { MessageList(onMessageClick = { id -> navController.navigate("message_detail/$id") }) } - composable( - route = "message_detail/{id}", - deepLinks = listOf(navDeepLink { - uriPattern = "googleandroidsnippets://app/message_detail/{id}" - })) { - val id = it.arguments?.getString("id") ?: 0 - MessageDetail(id = "message $id") + composable("message_detail/{id}") { + MessageDetail(id = it.arguments?.getString("id")!!) } } } @@ -82,16 +72,13 @@ fun MessageDetail(id: String) { ScreenScaffold( scrollState = scrollState, contentPadding = padding - ) { scaffoldPaddingValues -> + ) { // Screen content goes here // [END android_wear_navigation] - TransformingLazyColumn( - state = scrollState, - contentPadding = scaffoldPaddingValues - ) { + TransformingLazyColumn(state = scrollState) { item { Text( - text = "Detail $id", + text = id, textAlign = TextAlign.Center, modifier = Modifier.fillMaxSize() ) From 1184a99fc2f5b034611d96e7fd1a830f106a8112 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 18 Jun 2025 12:14:15 +0100 Subject: [PATCH 08/10] Formatting --- .../com/example/wear/snippets/m3/tile/Tile.kt | 40 +++++++++---------- .../wear/snippets/m3/tile/TileActivity.kt | 1 + 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt index 2ad9812e2..2ab8ea389 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt @@ -34,27 +34,27 @@ private const val RESOURCES_VERSION = "1" // [START android_wear_m3_tile_mytileservice] class MyTileService : TileService() { - override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = - Futures.immediateFuture( - Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - materialScope(this, requestParams.deviceConfiguration) { - primaryLayout( - mainSlot = { - text("Hello, World!".layoutString, typography = BODY_LARGE) - } - ) - } - ) - ) - .build() + override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = + Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + primaryLayout( + mainSlot = { + text("Hello, World!".layoutString, typography = BODY_LARGE) + } + ) + } + ) ) + .build() + ) - override fun onTileResourcesRequest(requestParams: ResourcesRequest) = - Futures.immediateFuture( - Resources.Builder().setVersion(RESOURCES_VERSION).build() - ) + override fun onTileResourcesRequest(requestParams: ResourcesRequest) = + Futures.immediateFuture( + Resources.Builder().setVersion(RESOURCES_VERSION).build() + ) } // [END android_wear_m3_tile_mytileservice] diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt index 5a5ef3e9d..857531357 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt @@ -57,6 +57,7 @@ class TileActivity : ComponentActivity() { // [END_EXCLUDE] } } + // [END android_wear_m3_interactions_launchaction_activity] @Composable From 328211b496f528985a3d4358b2aeae3ad3805561 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 18 Jun 2025 13:03:18 +0100 Subject: [PATCH 09/10] spotlessApply --- .../wear/snippets/m3/tile/Interaction.kt | 364 +++++++++--------- .../com/example/wear/snippets/m3/tile/Tile.kt | 40 +- .../wear/snippets/m3/tile/TileActivity.kt | 196 +++++----- 3 files changed, 299 insertions(+), 301 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt index f8786c911..5136b80df 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -52,224 +52,222 @@ private const val RESOURCES_VERSION = "1" abstract class BaseTileService : TileService() { - override fun onTileRequest( - requestParams: RequestBuilders.TileRequest - ): ListenableFuture = - Futures.immediateFuture( - Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - materialScope(this, requestParams.deviceConfiguration) { - tileLayout(requestParams) - } - ) + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture = + Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() ) - .build() - ) - override fun onTileResourcesRequest( - requestParams: ResourcesRequest - ): ListenableFuture = - Futures.immediateFuture( - Resources.Builder().setVersion(requestParams.version).build() - ) + override fun onTileResourcesRequest( + requestParams: ResourcesRequest + ): ListenableFuture = + Futures.immediateFuture( + Resources.Builder().setVersion(requestParams.version).build() + ) - abstract fun MaterialScope.tileLayout( - requestParams: RequestBuilders.TileRequest - ): LayoutElementBuilders.LayoutElement + abstract fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ): LayoutElementBuilders.LayoutElement } class HelloTileService : BaseTileService() { - override fun MaterialScope.tileLayout( - requestParams: RequestBuilders.TileRequest - ) = primaryLayout(mainSlot = { text("Hello, World!".layoutString) }) + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = primaryLayout(mainSlot = { text("Hello, World!".layoutString) }) } class InteractionRefresh : BaseTileService() { - override fun MaterialScope.tileLayout( - requestParams: RequestBuilders.TileRequest - ) = - primaryLayout( - // Output a debug code so we can see the layout changing - titleSlot = { - text( - String.format( - Locale.ENGLISH, - "Debug %06d", - Random.nextInt(0, 1_000_000), - ) - .layoutString - ) - }, - mainSlot = { - // [START android_wear_m3_interaction_refresh] - textButton( - onClick = clickable(loadAction()), - labelContent = { text("Refresh".layoutString) }, + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + primaryLayout( + // Output a debug code so we can see the layout changing + titleSlot = { + text( + String.format( + Locale.ENGLISH, + "Debug %06d", + Random.nextInt(0, 1_000_000), + ) + .layoutString + ) + }, + mainSlot = { + // [START android_wear_m3_interaction_refresh] + textButton( + onClick = clickable(loadAction()), + labelContent = { text("Refresh".layoutString) }, + ) + // [END android_wear_m3_interaction_refresh] + }, ) - // [END android_wear_m3_interaction_refresh] - }, - ) } class InteractionDeepLink : TileService() { - // [START android_wear_m3_interaction_deeplink_tile] - override fun onTileRequest( - requestParams: RequestBuilders.TileRequest - ): ListenableFuture { - val lastClickableId = requestParams.currentState.lastClickableId - if (lastClickableId == "foo") { - TaskStackBuilder.create(this) - .addNextIntentWithParentStack( - Intent( - Intent.ACTION_VIEW, - "googleandroidsnippets://app/message_detail/1".toUri(), - this, - TileActivity::class.java, - ) + // [START android_wear_m3_interaction_deeplink_tile] + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture { + val lastClickableId = requestParams.currentState.lastClickableId + if (lastClickableId == "foo") { + TaskStackBuilder.create(this) + .addNextIntentWithParentStack( + Intent( + Intent.ACTION_VIEW, + "googleandroidsnippets://app/message_detail/1".toUri(), + this, + TileActivity::class.java, + ) + ) + .startActivities() + } + // ... User didn't tap a button (either first load or tapped somewhere else) + // [START_EXCLUDE] + return Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() ) - .startActivities() + // [END_EXCLUDE] } - // ... User didn't tap a button (either first load or tapped somewhere else) - // [START_EXCLUDE] - return Futures.immediateFuture( - Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - materialScope(this, requestParams.deviceConfiguration) { - tileLayout(requestParams) - } - ) - ) - .build() - ) - // [END_EXCLUDE] - } - // [END android_wear_m3_interaction_deeplink_tile] + // [END android_wear_m3_interaction_deeplink_tile] - override fun onTileResourcesRequest( - requestParams: ResourcesRequest - ): ListenableFuture = - Futures.immediateFuture( - Resources.Builder().setVersion(requestParams.version).build() - ) + override fun onTileResourcesRequest( + requestParams: ResourcesRequest + ): ListenableFuture = + Futures.immediateFuture( + Resources.Builder().setVersion(requestParams.version).build() + ) - fun MaterialScope.tileLayout(requestParams: RequestBuilders.TileRequest) = - primaryLayout( - mainSlot = { - // [START android_wear_m3_interaction_deeplink_layout] - textButton( - labelContent = { - text("Deep Link me!".layoutString, typography = BODY_LARGE) - }, - onClick = clickable(id = "foo", action = loadAction()), + fun MaterialScope.tileLayout(requestParams: RequestBuilders.TileRequest) = + primaryLayout( + mainSlot = { + // [START android_wear_m3_interaction_deeplink_layout] + textButton( + labelContent = { + text("Deep Link me!".layoutString, typography = BODY_LARGE) + }, + onClick = clickable(id = "foo", action = loadAction()), + ) + // [END android_wear_m3_interaction_deeplink_layout] + } ) - // [END android_wear_m3_interaction_deeplink_layout] - } - ) } class InteractionLoadAction : BaseTileService() { - override fun onTileRequest( - requestParams: RequestBuilders.TileRequest - ): ListenableFuture { + override fun onTileRequest( + requestParams: RequestBuilders.TileRequest + ): ListenableFuture { - val name: String? - val age: Int? + val name: String? + val age: Int? - // When triggered by loadAction(), "name" will be "Javier", and "age" will - // be 37. - with(requestParams.currentState.stateMap) { - name = this[stringAppDataKey("name")] - age = this[intAppDataKey("age")] - } + // When triggered by loadAction(), "name" will be "Javier", and "age" will + // be 37. + with(requestParams.currentState.stateMap) { + name = this[stringAppDataKey("name")] + age = this[intAppDataKey("age")] + } - Log.d("qqqqqq", "name = $name, age = $age") - - return Futures.immediateFuture( - Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - materialScope(this, requestParams.deviceConfiguration) { - tileLayout(requestParams) - } - ) + return Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + tileLayout(requestParams) + } + ) + ) + .build() ) - .build() - ) - } + } - override fun MaterialScope.tileLayout( - requestParams: RequestBuilders.TileRequest - ) = - primaryLayout( - // Output a debug code so we can verify that the reload happens - titleSlot = { - text( - String.format( - Locale.ENGLISH, - "Debug %06d", - Random.nextInt(0, 1_000_000), - ) - .layoutString - ) - }, - mainSlot = { - // [START android_wear_m3_interaction_loadaction_layout] - textButton( - labelContent = { - text("loadAction()".layoutString, typography = BODY_LARGE) - }, - onClick = - clickable( - action = - loadAction( - dynamicDataMapOf( - stringAppDataKey("name") mapTo "Javier", - intAppDataKey("age") mapTo 37, - ) + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + primaryLayout( + // Output a debug code so we can verify that the reload happens + titleSlot = { + text( + String.format( + Locale.ENGLISH, + "Debug %06d", + Random.nextInt(0, 1_000_000), + ) + .layoutString ) - ), + }, + mainSlot = { + // [START android_wear_m3_interaction_loadaction_layout] + textButton( + labelContent = { + text("loadAction()".layoutString, typography = BODY_LARGE) + }, + onClick = + clickable( + action = + loadAction( + dynamicDataMapOf( + stringAppDataKey("name") mapTo "Javier", + intAppDataKey("age") mapTo 37, + ) + ) + ), + ) + // [END android_wear_m3_interaction_loadaction_layout] + }, ) - // [END android_wear_m3_interaction_loadaction_layout] - }, - ) } class InteractionLaunchAction : BaseTileService() { - override fun MaterialScope.tileLayout( - requestParams: RequestBuilders.TileRequest - ) = - primaryLayout( - mainSlot = { - // [START android_wear_m3_interactions_launchaction] - textButton( - labelContent = { - text("launchAction()".layoutString, typography = BODY_LARGE) - }, - onClick = - clickable( - action = - launchAction( - ComponentName( - "com.example.wear", - "com.example.wear.snippets.m3.tile.TileActivity", - ), - mapOf( - "name" to ActionBuilders.stringExtra("Bartholomew"), - "age" to ActionBuilders.intExtra(21), - ), + override fun MaterialScope.tileLayout( + requestParams: RequestBuilders.TileRequest + ) = + primaryLayout( + mainSlot = { + // [START android_wear_m3_interactions_launchaction] + textButton( + labelContent = { + text("launchAction()".layoutString, typography = BODY_LARGE) + }, + onClick = + clickable( + action = + launchAction( + ComponentName( + "com.example.wear", + "com.example.wear.snippets.m3.tile.TileActivity", + ), + mapOf( + "name" to ActionBuilders.stringExtra("Bartholomew"), + "age" to ActionBuilders.intExtra(21), + ), + ) + ), ) - ), + // [END android_wear_m3_interactions_launchaction] + } ) - // [END android_wear_m3_interactions_launchaction] - } - ) } diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt index 2ab8ea389..2ad9812e2 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Tile.kt @@ -34,27 +34,27 @@ private const val RESOURCES_VERSION = "1" // [START android_wear_m3_tile_mytileservice] class MyTileService : TileService() { - override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = - Futures.immediateFuture( - Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTileTimeline( - Timeline.fromLayoutElement( - materialScope(this, requestParams.deviceConfiguration) { - primaryLayout( - mainSlot = { - text("Hello, World!".layoutString, typography = BODY_LARGE) - } - ) - } - ) + override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = + Futures.immediateFuture( + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTileTimeline( + Timeline.fromLayoutElement( + materialScope(this, requestParams.deviceConfiguration) { + primaryLayout( + mainSlot = { + text("Hello, World!".layoutString, typography = BODY_LARGE) + } + ) + } + ) + ) + .build() ) - .build() - ) - override fun onTileResourcesRequest(requestParams: ResourcesRequest) = - Futures.immediateFuture( - Resources.Builder().setVersion(RESOURCES_VERSION).build() - ) + override fun onTileResourcesRequest(requestParams: ResourcesRequest) = + Futures.immediateFuture( + Resources.Builder().setVersion(RESOURCES_VERSION).build() + ) } // [END android_wear_m3_tile_mytileservice] diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt index 857531357..314898c39 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/TileActivity.kt @@ -43,142 +43,142 @@ import com.google.android.horologist.compose.layout.ColumnItemType import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding class TileActivity : ComponentActivity() { - // [START android_wear_m3_interactions_launchaction_activity] - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + // [START android_wear_m3_interactions_launchaction_activity] + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - // When this activity is launched from the tile InteractionLaunchAction, - // "name" will be "Bartholomew" and "age" will be 21 - val name = intent.getStringExtra("name") - val age = intent.getStringExtra("age") + // When this activity is launched from the tile InteractionLaunchAction, + // "name" will be "Bartholomew" and "age" will be 21 + val name = intent.getStringExtra("name") + val age = intent.getStringExtra("age") - // [START_EXCLUDE] - setContent { MainContent() } - // [END_EXCLUDE] - } + // [START_EXCLUDE] + setContent { MainContent() } + // [END_EXCLUDE] + } } // [END android_wear_m3_interactions_launchaction_activity] @Composable fun MainContent() { - // [START android_wear_m3_interaction_deeplink_activity] - AppScaffold { - val navController = rememberSwipeDismissableNavController() - SwipeDismissableNavHost( - navController = navController, - startDestination = "message_list", - ) { - // [START_EXCLUDE] - composable( - route = "message_list", - deepLinks = - listOf( - navDeepLink { - uriPattern = "googleandroidsnippets://app/message_list" + // [START android_wear_m3_interaction_deeplink_activity] + AppScaffold { + val navController = rememberSwipeDismissableNavController() + SwipeDismissableNavHost( + navController = navController, + startDestination = "message_list", + ) { + // [START_EXCLUDE] + composable( + route = "message_list", + deepLinks = + listOf( + navDeepLink { + uriPattern = "googleandroidsnippets://app/message_list" + } + ), + ) { + MessageList( + onMessageClick = { id -> + navController.navigate("message_detail/$id") + } + ) } - ), - ) { - MessageList( - onMessageClick = { id -> - navController.navigate("message_detail/$id") - } - ) - } - // [END_EXCLUDE] - composable( - route = "message_detail/{id}", - deepLinks = - listOf( - navDeepLink { - uriPattern = "googleandroidsnippets://app/message_detail/{id}" + // [END_EXCLUDE] + composable( + route = "message_detail/{id}", + deepLinks = + listOf( + navDeepLink { + uriPattern = "googleandroidsnippets://app/message_detail/{id}" + } + ), + ) { + val id = it.arguments?.getString("id") ?: "0" + MessageDetails(details = "message $id") } - ), - ) { - val id = it.arguments?.getString("id") ?: "0" - MessageDetails(details = "message $id") - } + } } - } - // [END android_wear_m3_interaction_deeplink_activity] + // [END android_wear_m3_interaction_deeplink_activity] } // Implementation of one of the screens in the navigation @Composable fun MessageDetails(details: String) { - val scrollState = rememberTransformingLazyColumnState() + val scrollState = rememberTransformingLazyColumnState() - val padding = rememberResponsiveColumnPadding(first = ColumnItemType.BodyText) + val padding = rememberResponsiveColumnPadding(first = ColumnItemType.BodyText) - ScreenScaffold(scrollState = scrollState, contentPadding = padding) { - scaffoldPaddingValues -> - TransformingLazyColumn( - state = scrollState, - contentPadding = scaffoldPaddingValues, - ) { - item { - ListHeader() { Text(text = stringResource(R.string.message_detail)) } - } - item { - Text( - text = details, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxSize(), - ) - } + ScreenScaffold(scrollState = scrollState, contentPadding = padding) { + scaffoldPaddingValues -> + TransformingLazyColumn( + state = scrollState, + contentPadding = scaffoldPaddingValues, + ) { + item { + ListHeader() { Text(text = stringResource(R.string.message_detail)) } + } + item { + Text( + text = details, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxSize(), + ) + } + } } - } } @Composable fun MessageList(onMessageClick: (String) -> Unit) { - val scrollState = rememberTransformingLazyColumnState() + val scrollState = rememberTransformingLazyColumnState() - val padding = - rememberResponsiveColumnPadding( - first = ColumnItemType.ListHeader, - last = ColumnItemType.Button, - ) + val padding = + rememberResponsiveColumnPadding( + first = ColumnItemType.ListHeader, + last = ColumnItemType.Button, + ) - ScreenScaffold(scrollState = scrollState, contentPadding = padding) { - contentPadding -> - TransformingLazyColumn( - state = scrollState, - contentPadding = contentPadding, - ) { - item { - ListHeader() { Text(text = stringResource(R.string.message_list)) } - } - item { - Button( - onClick = { onMessageClick("message1") }, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = "Message 1") - } - } - item { - Button( - onClick = { onMessageClick("message2") }, - modifier = Modifier.fillMaxWidth(), + ScreenScaffold(scrollState = scrollState, contentPadding = padding) { + contentPadding -> + TransformingLazyColumn( + state = scrollState, + contentPadding = contentPadding, ) { - Text(text = "Message 2") + item { + ListHeader() { Text(text = stringResource(R.string.message_list)) } + } + item { + Button( + onClick = { onMessageClick("message1") }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "Message 1") + } + } + item { + Button( + onClick = { onMessageClick("message2") }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "Message 2") + } + } } - } } - } } @WearPreviewDevices @WearPreviewFontScales @Composable fun MessageDetailPreview() { - MessageDetails("message 7") + MessageDetails("message 7") } @WearPreviewDevices @WearPreviewFontScales @Composable fun MessageListPreview() { - MessageList(onMessageClick = {}) + MessageList(onMessageClick = {}) } From 6d2c04e964fdecdb5e4ff1d855f79b0a76bfb871 Mon Sep 17 00:00:00 2001 From: Michael Stillwell Date: Wed, 18 Jun 2025 13:08:39 +0100 Subject: [PATCH 10/10] spotlessApply #2 --- .../main/java/com/example/wear/snippets/m3/tile/Interaction.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt index 5136b80df..7ef43dc6f 100644 --- a/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt +++ b/wear/src/main/java/com/example/wear/snippets/m3/tile/Interaction.kt @@ -18,7 +18,6 @@ package com.example.wear.snippets.m3.tile import android.content.ComponentName import android.content.Intent -import android.util.Log import androidx.core.app.TaskStackBuilder import androidx.core.net.toUri import androidx.wear.protolayout.ActionBuilders