From 568344b3d440b13612130dc40b7425258a5e9c89 Mon Sep 17 00:00:00 2001 From: Simona <35065668+simona-anomis@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:32:45 +0100 Subject: [PATCH 01/23] Update libs.versions.toml (#251) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 173db1d6..356661c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ accompanist = "0.32.0" androidGradlePlugin = "8.2.2" androidx-activity-compose = "1.9.0-alpha03" androidx-appcompat = "1.6.1" -androidx-compose-bom = "2024.04.00" +androidx-compose-bom = "2024.04.01" androidx-compose-ui-test = "1.7.0-alpha03" androidx-constraintlayout = "2.1.4" androidx-constraintlayout-compose = "1.0.1" From 57ebb935d5a198550a51cab34595d5fe97db7824 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" <1033551+IanGClifton@users.noreply.github.com> Date: Wed, 1 May 2024 15:51:47 -0700 Subject: [PATCH 02/23] Updated ListDetailPaneScaffold use to alpha12 (#255) This eliminates storing state outside and directly uses the navigator as the source of truth. This also gets rid of the Modifier for AnimatedPane as it's no longer a required argument. --- build.gradle.kts | 1 + compose/snippets/build.gradle.kts | 1 + .../SampleListDetailPaneScaffold.kt | 65 ++++++------------- gradle/libs.versions.toml | 3 +- 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 13468624..547023ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kapt) apply false alias(libs.plugins.hilt) apply false + alias(libs.plugins.kotlin.parcelize) apply false } apply("${project.rootDir}/buildscripts/toml-updater-config.gradle") diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index acfa8ddc..48016ae8 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -19,6 +19,7 @@ plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kapt) alias(libs.plugins.hilt) + alias(libs.plugins.kotlin.parcelize) } android { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleListDetailPaneScaffold.kt b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleListDetailPaneScaffold.kt index 154ee4b3..68c14dfd 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleListDetailPaneScaffold.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleListDetailPaneScaffold.kt @@ -16,6 +16,7 @@ package com.example.compose.snippets.adaptivelayouts +import android.os.Parcelable import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -35,33 +36,25 @@ import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import kotlinx.parcelize.Parcelize @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun SampleListDetailPaneScaffoldParts() { // [START android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_part02] - val navigator = rememberListDetailPaneScaffoldNavigator() + val navigator = rememberListDetailPaneScaffoldNavigator() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } // [END android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_part02] - // [START android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_part01] - var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) { - mutableStateOf(null) - } - // [END android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_part01] - // [START android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_part03] ListDetailPaneScaffold( directive = navigator.scaffoldDirective, @@ -78,13 +71,11 @@ fun SampleListDetailPaneScaffoldParts() { directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { - AnimatedPane(Modifier) { + AnimatedPane { MyList( - onItemClick = { id -> - // Set current item - selectedItem = id - // Switch focus to detail pane - navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + onItemClick = { item -> + // Navigate to the detail pane with the passed item + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, item) } ) } @@ -104,9 +95,9 @@ fun SampleListDetailPaneScaffoldParts() { {}, // [END_EXCLUDE] detailPane = { - AnimatedPane(Modifier) { - selectedItem?.let { item -> - MyDetails(item) + AnimatedPane { + navigator.currentDestination?.content?.let { + MyDetails(it) } } }, @@ -119,13 +110,7 @@ fun SampleListDetailPaneScaffoldParts() { @Composable fun SampleListDetailPaneScaffoldFull() { // [START android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_full] - // Currently selected item - var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) { - mutableStateOf(null) - } - - // Create the ListDetailPaneScaffoldState - val navigator = rememberListDetailPaneScaffoldNavigator() + val navigator = rememberListDetailPaneScaffoldNavigator() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() @@ -135,22 +120,20 @@ fun SampleListDetailPaneScaffoldFull() { directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { - AnimatedPane(Modifier) { + AnimatedPane { MyList( - onItemClick = { id -> - // Set current item - selectedItem = id - // Display the detail pane - navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + onItemClick = { item -> + // Navigate to the detail pane with the passed item + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, item) }, ) } }, detailPane = { - AnimatedPane(Modifier) { + AnimatedPane { // Show the detail pane content if selected item is available - selectedItem?.let { item -> - MyDetails(item) + navigator.currentDestination?.content?.let { + MyDetails(it) } } }, @@ -206,19 +189,11 @@ fun MyDetails(item: MyItem) { } // [START android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_myitem] -class MyItem(val id: Int) { - companion object { - val Saver: Saver = Saver( - { it?.id }, - ::MyItem, - ) - } -} +@Parcelize +class MyItem(val id: Int) : Parcelable // [END android_compose_adaptivelayouts_sample_list_detail_pane_scaffold_myitem] val shortStrings = listOf( - "Android", - "Petit four", "Cupcake", "Donut", "Eclair", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 356661c8..f1d252e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ kotlin = "1.9.20" ksp = "1.8.0-1.0.9" maps-compose = "4.3.2" material = "1.11.0" -material3-adaptive = "1.0.0-alpha08" +material3-adaptive = "1.0.0-alpha12" material3-adaptive-navigation-suite = "1.0.0-alpha05" media3 = "1.2.1" # @keep @@ -118,3 +118,4 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } From edb4c219d30f69af2d4a40f2d33dfca00d516b0a Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" <1033551+IanGClifton@users.noreply.github.com> Date: Wed, 1 May 2024 17:21:07 -0700 Subject: [PATCH 03/23] Added NavigationSuiteScaffold snippets (#261) * Added NavigationSuiteScaffold snippets * Apply Spotless --- compose/snippets/build.gradle.kts | 1 + .../SampleNavigationSuiteScaffold.kt | 191 ++++++++++++++++++ .../src/main/res/values-es/strings.xml | 4 + .../snippets/src/main/res/values/strings.xml | 4 + gradle/libs.versions.toml | 2 +- 5 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index 48016ae8..17b3ea12 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -96,6 +96,7 @@ dependencies { implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive.layout) implementation(libs.androidx.compose.material3.adaptive.navigation) + implementation(libs.androidx.compose.material3.adaptive.navigation.suite) implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.runtime) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt new file mode 100644 index 00000000..f5c461f6 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleNavigationSuiteScaffold.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2024 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. + */ + +@file:OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class) + +package com.example.compose.snippets.adaptivelayouts + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountBox +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.window.core.layout.WindowWidthSizeClass +import com.example.compose.snippets.R + +// [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_destinations] +enum class AppDestinations( + @StringRes val label: Int, + val icon: ImageVector, + @StringRes val contentDescription: Int +) { + HOME(R.string.home, Icons.Default.Home, R.string.home), + FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites), + SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping), + PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile), +} +// [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_destinations] + +@Composable +fun SampleNavigationSuiteScaffoldParts() { + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_remember] + var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_remember] + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_items] + NavigationSuiteScaffold( + navigationSuiteItems = { + AppDestinations.entries.forEach { + item( + icon = { + Icon( + it.icon, + contentDescription = stringResource(it.contentDescription) + ) + }, + label = { Text(stringResource(it.label)) }, + selected = it == currentDestination, + onClick = { currentDestination = it } + ) + } + } + ) { + // TODO: Destination content. + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_items] + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_content] + NavigationSuiteScaffold( + navigationSuiteItems = { /*...*/ } + ) { + // Destination content. + when (currentDestination) { + AppDestinations.HOME -> HomeDestination() + AppDestinations.FAVORITES -> FavoritesDestination() + AppDestinations.SHOPPING -> ShoppingDestination() + AppDestinations.PROFILE -> ProfileDestination() + } + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_content] +} + +@Composable +fun SampleNavigationSuiteScaffoldColors() { + var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) } + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_container_color] + NavigationSuiteScaffold( + navigationSuiteItems = { /* ... */ }, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ) { + // Content... + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_container_color] + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_suite_colors] + NavigationSuiteScaffold( + navigationSuiteItems = { /* ... */ }, + navigationSuiteColors = NavigationSuiteDefaults.colors( + navigationBarContainerColor = Color.Transparent, + ) + ) { + // Content... + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_suite_colors] + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_item_colors] + val myNavigationSuiteItemColors = NavigationSuiteDefaults.itemColors( + navigationBarItemColors = NavigationBarItemDefaults.colors( + indicatorColor = MaterialTheme.colorScheme.primaryContainer, + selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer + ), + ) + + NavigationSuiteScaffold( + navigationSuiteItems = { + AppDestinations.entries.forEach { + item( + icon = { + Icon( + it.icon, + contentDescription = stringResource(it.contentDescription) + ) + }, + label = { Text(stringResource(it.label)) }, + selected = it == currentDestination, + onClick = { currentDestination = it }, + colors = myNavigationSuiteItemColors, + ) + } + }, + ) { + // Content... + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_item_colors] +} + +@Composable +fun SampleNavigationSuiteScaffoldCustomType() { + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_layout_type] + val adaptiveInfo = currentWindowAdaptiveInfo() + val customNavSuiteType = with(adaptiveInfo) { + if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) { + NavigationSuiteType.NavigationDrawer + } else { + NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) + } + } + + NavigationSuiteScaffold( + navigationSuiteItems = { /* ... */ }, + layoutType = customNavSuiteType, + ) { + // Content... + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_layout_type] +} + +@Composable +fun HomeDestination() {} + +@Composable +fun FavoritesDestination() {} + +@Composable +fun ShoppingDestination() {} + +@Composable +fun ProfileDestination() {} diff --git a/compose/snippets/src/main/res/values-es/strings.xml b/compose/snippets/src/main/res/values-es/strings.xml index 7507771a..cd1e2c96 100644 --- a/compose/snippets/src/main/res/values-es/strings.xml +++ b/compose/snippets/src/main/res/values-es/strings.xml @@ -48,4 +48,8 @@ Add View entry Related Articles + Inicio + Favoritos + Compras + Perfil \ No newline at end of file diff --git a/compose/snippets/src/main/res/values/strings.xml b/compose/snippets/src/main/res/values/strings.xml index 1baab18f..9c693caf 100644 --- a/compose/snippets/src/main/res/values/strings.xml +++ b/compose/snippets/src/main/res/values/strings.xml @@ -48,4 +48,8 @@ Add View entry Related Articles + Home + Favorites + Shopping + Profile \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f1d252e8..01c5c439 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ ksp = "1.8.0-1.0.9" maps-compose = "4.3.2" material = "1.11.0" material3-adaptive = "1.0.0-alpha12" -material3-adaptive-navigation-suite = "1.0.0-alpha05" +material3-adaptive-navigation-suite = "1.0.0-alpha07" media3 = "1.2.1" # @keep minSdk = "21" From cf682eb855fef5e0117954ae8c80f1f8b622719b Mon Sep 17 00:00:00 2001 From: compose-devrel-github-bot <118755852+compose-devrel-github-bot@users.noreply.github.com> Date: Fri, 3 May 2024 09:52:00 +0100 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=A4=96=20Update=20Dependencies=20(#?= =?UTF-8?q?262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01c5c439..2078c212 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ androidGradlePlugin = "8.2.2" androidx-activity-compose = "1.9.0-alpha03" androidx-appcompat = "1.6.1" androidx-compose-bom = "2024.04.01" -androidx-compose-ui-test = "1.7.0-alpha03" +androidx-compose-ui-test = "1.7.0-alpha08" androidx-constraintlayout = "2.1.4" androidx-constraintlayout-compose = "1.0.1" androidx-coordinator-layout = "1.2.0" @@ -12,8 +12,8 @@ androidx-corektx = "1.9.0" androidx-emoji2-views = "1.4.0" androidx-fragment-ktx = "1.6.2" androidx-glance-appwidget = "1.0.0" -androidx-lifecycle-compose = "2.7.0" -androidx-lifecycle-runtime-compose = "2.7.0" +androidx-lifecycle-compose = "2.8.0-rc01" +androidx-lifecycle-runtime-compose = "2.8.0-rc01" androidx-navigation = "2.7.7" androidx-paging = "3.2.1" androidx-test = "1.5.0" @@ -117,5 +117,5 @@ gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradle- hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" } From ef29bb6fda5c60c3f1b6d9de4d1c500b9f49c7a0 Mon Sep 17 00:00:00 2001 From: "Ian G. Clifton" <1033551+IanGClifton@users.noreply.github.com> Date: Mon, 6 May 2024 11:30:43 -0700 Subject: [PATCH 05/23] Added snippets for SupportingPaneScaffold (#260) * Added snippets for SupportingPaneScaffold * Apply Spotless * Update compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleSupportingPaneScaffold.kt --- .../SampleSupportingPaneScaffold.kt | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleSupportingPaneScaffold.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleSupportingPaneScaffold.kt b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleSupportingPaneScaffold.kt new file mode 100644 index 00000000..ad2e0d30 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/adaptivelayouts/SampleSupportingPaneScaffold.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2024 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. + */ + +@file:OptIn(ExperimentalMaterial3AdaptiveApi::class) + +package com.example.compose.snippets.adaptivelayouts + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane +import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold +import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope +import androidx.compose.material3.adaptive.navigation.rememberSupportingPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun SampleSupportingPaneScaffoldParts() { + // [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_nav_and_back] + val navigator = rememberSupportingPaneScaffoldNavigator() + + BackHandler(navigator.canNavigateBack()) { + navigator.navigateBack() + } + // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_nav_and_back] + + // [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_params] + SupportingPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + mainPane = { /*...*/ }, + supportingPane = { /*...*/ }, + ) + // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_params] +} + +@Composable +fun SampleSupportingPaneScaffoldFull() { + // [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_full] + val navigator = rememberSupportingPaneScaffoldNavigator() + + BackHandler(navigator.canNavigateBack()) { + navigator.navigateBack() + } + + SupportingPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + mainPane = { + AnimatedPane(modifier = Modifier.safeContentPadding()) { + // Main pane content + if (navigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) { + Button( + modifier = Modifier.wrapContentSize(), + onClick = { + navigator.navigateTo(SupportingPaneScaffoldRole.Supporting) + } + ) { + Text("Show supporting pane") + } + } else { + Text("Supporting pane is shown") + } + } + }, + supportingPane = { + AnimatedPane(modifier = Modifier.safeContentPadding()) { + // Supporting pane content + Text("Supporting pane") + } + }, + ) + // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_full] +} + +// [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_extracted_panes] +@Composable +fun ThreePaneScaffoldScope.MainPane( + shouldShowSupportingPaneButton: Boolean, + onNavigateToSupportingPane: () -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedPane(modifier = modifier.safeContentPadding()) { + // Main pane content + if (shouldShowSupportingPaneButton) { + Button(onClick = onNavigateToSupportingPane) { + Text("Show supporting pane") + } + } else { + Text("Supporting pane is shown") + } + } +} + +@Composable +fun ThreePaneScaffoldScope.SupportingPane( + modifier: Modifier = Modifier, +) { + AnimatedPane(modifier = modifier.safeContentPadding()) { + // Supporting pane content + Text("This is the supporting pane") + } +} +// [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_extracted_panes] + +@Composable +fun SampleSupportingPaneScaffoldSimplified() { +// [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_simplified] + val navigator = rememberSupportingPaneScaffoldNavigator() + + BackHandler(navigator.canNavigateBack()) { + navigator.navigateBack() + } + + SupportingPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + mainPane = { + MainPane( + shouldShowSupportingPaneButton = navigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden, + onNavigateToSupportingPane = { navigator.navigateTo(ThreePaneScaffoldRole.Secondary) } + ) + }, + supportingPane = { SupportingPane() }, + ) +// [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_simplified] +} From 30f5ef7366ed59a7484f3fda6c76efcc3bcd6a1f Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Mon, 6 May 2024 15:51:35 -0700 Subject: [PATCH 06/23] Migrate to material3-adaptive WindowSizeClass method (#264) * Migrate to material3-adaptive WindowSizeClass method * Update comments to be single line --- compose/snippets/build.gradle.kts | 3 +- .../compose/snippets/glance/GlanceSnippets.kt | 8 ++-- .../layouts/AdaptiveLayoutSnippets.kt | 42 ++++++++----------- .../snippets/state/StateOverviewSnippets.kt | 2 +- gradle/libs.versions.toml | 3 +- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index 17b3ea12..3930f561 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -101,7 +101,6 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.runtime.livedata) - implementation(libs.androidx.compose.materialWindow) implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material.ripple) implementation(libs.androidx.constraintlayout.compose) @@ -117,6 +116,8 @@ dependencies { implementation(libs.androidx.glance.appwidget) implementation(libs.androidx.glance.material3) + implementation(libs.androidx.window.core) + implementation(libs.accompanist.theme.adapter.appcompat) implementation(libs.accompanist.theme.adapter.material3) implementation(libs.accompanist.theme.adapter.material) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceSnippets.kt index 2566a514..aaea6a62 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceSnippets.kt @@ -85,8 +85,8 @@ import androidx.glance.material3.ColorProviders import androidx.glance.text.Text import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.example.compose.snippets.MyActivity import com.example.compose.snippets.R -import com.example.compose.snippets.layouts.MainActivity import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -167,11 +167,11 @@ private object CreateUI { Row(horizontalAlignment = Alignment.CenterHorizontally) { Button( text = "Home", - onClick = actionStartActivity() + onClick = actionStartActivity() ) Button( text = "Work", - onClick = actionStartActivity() + onClick = actionStartActivity() ) } } @@ -188,7 +188,7 @@ private object ActionLaunchActivity { // .. Button( text = "Go Home", - onClick = actionStartActivity() + onClick = actionStartActivity() ) } // [END android_compose_glance_launchactivity] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/AdaptiveLayoutSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/AdaptiveLayoutSnippets.kt index 0db5be6b..c66253cd 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/AdaptiveLayoutSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/AdaptiveLayoutSnippets.kt @@ -18,22 +18,19 @@ package com.example.compose.snippets.layouts -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowSizeClass /* * Copyright 2023 The Android Open Source Project @@ -51,25 +48,15 @@ import androidx.compose.ui.unit.dp * limitations under the License. */ // [START android_compose_adaptive_layouts_basic] -class MainActivity : ComponentActivity() { - @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - val windowSizeClass = calculateWindowSizeClass(this) - MyApp(windowSizeClass) - } - } -} +@OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable -fun MyApp(windowSizeClass: WindowSizeClass) { - // Perform logic on the size class to decide whether to show - // the top app bar. - val showTopAppBar = windowSizeClass.heightSizeClass != WindowHeightSizeClass.Compact +fun MyApp( + windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass +) { + // Perform logic on the size class to decide whether to show the top app bar. + val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT - // MyScreen knows nothing about window sizes, and performs logic - // based on a Boolean flag. + // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag. MyScreen( showTopAppBar = showTopAppBar, /* ... */ @@ -95,6 +82,13 @@ fun AdaptivePane( } // [END android_compose_layouts_adaptive_pane] +@Composable +private fun WindowSizeClassSnippet() { + // [START android_compose_windowsizeclass] + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + // [END android_compose_windowsizeclass] +} + @Composable fun OnePane() { // your content here diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateOverviewSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateOverviewSnippets.kt index 2ac1e62f..503732ad 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateOverviewSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateOverviewSnippets.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text -import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -46,6 +45,7 @@ import androidx.compose.ui.res.imageResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowSizeClass // [START android_compose_state_overview] @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2078c212..62a11563 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ androidx-navigation = "2.7.7" androidx-paging = "3.2.1" androidx-test = "1.5.0" androidx-test-espresso = "3.5.1" +androidx-window = "1.3.0-beta02" androidxHiltNavigationCompose = "1.2.0" coil = "2.5.0" # @keep @@ -63,7 +64,6 @@ androidx-compose-material3-adaptive = { module = "androidx.compose.material3.ada androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3-adaptive" } androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3-adaptive" } androidx-compose-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3-adaptive-navigation-suite" } -androidx-compose-materialWindow = { module = "androidx.compose.material3:material3-window-size-class" } androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" } androidx-compose-ui = { module = "androidx.compose.ui:ui" } @@ -98,6 +98,7 @@ androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" } androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } androidx-test-runner = "androidx.test:runner:1.5.2" +androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window" } androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.9.0" coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } google-android-material = { module = "com.google.android.material:material", version.ref = "material" } From 0ce6be1907362ffb63ec509af25fb98d8af4123a Mon Sep 17 00:00:00 2001 From: MagicalMeghan <46006059+MagicalMeghan@users.noreply.github.com> Date: Wed, 8 May 2024 17:15:34 -0400 Subject: [PATCH 07/23] Update PiP snippets with new aspect ratio best practice (#257) * Update PictureInPictureSnippets.kt * Update PictureInPictureSnippets.kt * Apply Spotless * Update PictureInPictureSnippets.kt * Update PictureInPictureSnippets.kt * Update PictureInPictureSnippets.kt --------- Co-authored-by: MagicalMeghan --- .../pictureinpicture/PictureInPictureSnippets.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/pictureinpicture/PictureInPictureSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/pictureinpicture/PictureInPictureSnippets.kt index 953c5723..39cf2d22 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/pictureinpicture/PictureInPictureSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/pictureinpicture/PictureInPictureSnippets.kt @@ -47,6 +47,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.toRect import androidx.core.util.Consumer import androidx.media3.common.Player +import androidx.media3.common.VideoSize import androidx.media3.exoplayer.ExoPlayer var shouldEnterPipMode by mutableStateOf(false) @@ -237,6 +238,7 @@ fun PiPBuilderSetSourceRect( @Composable fun PiPBuilderSetAspectRatio( + player: Player?, shouldEnterPipMode: Boolean, modifier: Modifier = Modifier, ) { @@ -246,12 +248,11 @@ fun PiPBuilderSetAspectRatio( val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() - - if (shouldEnterPipMode) { + if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( - Rational(sourceRect.width(), sourceRect.height()) + Rational(player.videoSize.width, player.videoSize.height) ) } @@ -312,6 +313,7 @@ fun listOfRemoteActions(): List { @Composable fun PiPBuilderAddRemoteActions( + player: Player?, shouldEnterPipMode: Boolean, modifier: Modifier = Modifier, ) { @@ -325,11 +327,11 @@ fun PiPBuilderAddRemoteActions( listOfRemoteActions() ) - if (shouldEnterPipMode) { + if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( - Rational(sourceRect.width(), sourceRect.height()) + Rational(player.videoSize.width, player.videoSize.height) ) } From 6a553a62a41ed3cbe5d6e61691b0c028a1bf9369 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 9 May 2024 10:39:45 +0200 Subject: [PATCH 08/23] Add snippets for Drag & Drop --- .../draganddrop/DragAndDropSnippets.kt | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt new file mode 100644 index 00000000..6c7e7792 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt @@ -0,0 +1,95 @@ +package com.example.compose.snippets.draganddrop + +import android.content.ClipData +import android.content.ClipDescription +import android.os.Build +import android.view.View +import androidx.annotation.RequiresApi +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.draganddrop.dragAndDropSource +import androidx.compose.foundation.draganddrop.dragAndDropTarget +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draganddrop.DragAndDropEvent +import androidx.compose.ui.draganddrop.DragAndDropTarget +import androidx.compose.ui.draganddrop.DragAndDropTransferData +import androidx.compose.ui.draganddrop.mimeTypes + +@RequiresApi(Build.VERSION_CODES.N) +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun DragAndDropSnippet() { + + val url = "" + + // [START android_compose_drag_and_drop_1] + Modifier.dragAndDropSource { + detectTapGestures(onLongPress = { + startTransfer( + DragAndDropTransferData( + ClipData.newPlainText( + "image Url", url + ) + ) + ) + }) + } + // [END android_compose_drag_and_drop_1] + + // [START android_compose_drag_and_drop_2] + Modifier.dragAndDropSource { + detectTapGestures(onLongPress = { + startTransfer( + DragAndDropTransferData( + ClipData.newPlainText( + "image Url", url + ), flags = View.DRAG_FLAG_GLOBAL + ) + ) + }) + } + // [END android_compose_drag_and_drop_2] + + // [START android_compose_drag_and_drop_3] + val callback = remember { + object : DragAndDropTarget { + override fun onDrop(event: DragAndDropEvent): Boolean { + // Parse received data + return true + } + } + } + // [END android_compose_drag_and_drop_3] + + // [START android_compose_drag_and_drop_4] + Modifier.dragAndDropTarget( + shouldStartDragAndDrop = { event -> + event.mimeTypes().contains(ClipDescription.MIMETYPE_TEXT_PLAIN) + }, target = callback + ) + // [END android_compose_drag_and_drop_4] + + // [START android_compose_drag_and_drop_5] + object : DragAndDropTarget { + override fun onStarted(event: DragAndDropEvent) { + //When the drag event starts + } + + override fun onEntered(event: DragAndDropEvent) { + //When the dragged object enters the target surface + } + + override fun onEnded(event: DragAndDropEvent) { + //When the drag event stops + } + + override fun onExited(event: DragAndDropEvent) { + //When the dragged object exits the target surface + } + + override fun onDrop(event: DragAndDropEvent): Boolean = true + } + // [END android_compose_drag_and_drop_5] +} \ No newline at end of file From bd223e389437e994e9edbf0d79f57c3cff0acb55 Mon Sep 17 00:00:00 2001 From: tiwiz Date: Thu, 9 May 2024 08:42:17 +0000 Subject: [PATCH 09/23] Apply Spotless --- .../draganddrop/DragAndDropSnippets.kt | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt index 6c7e7792..4291b8b0 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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.compose.snippets.draganddrop import android.content.ClipData @@ -45,7 +61,8 @@ fun DragAndDropSnippet() { DragAndDropTransferData( ClipData.newPlainText( "image Url", url - ), flags = View.DRAG_FLAG_GLOBAL + ), + flags = View.DRAG_FLAG_GLOBAL ) ) }) @@ -74,22 +91,22 @@ fun DragAndDropSnippet() { // [START android_compose_drag_and_drop_5] object : DragAndDropTarget { override fun onStarted(event: DragAndDropEvent) { - //When the drag event starts + // When the drag event starts } override fun onEntered(event: DragAndDropEvent) { - //When the dragged object enters the target surface + // When the dragged object enters the target surface } override fun onEnded(event: DragAndDropEvent) { - //When the drag event stops + // When the drag event stops } override fun onExited(event: DragAndDropEvent) { - //When the dragged object exits the target surface + // When the dragged object exits the target surface } override fun onDrop(event: DragAndDropEvent): Boolean = true } // [END android_compose_drag_and_drop_5] -} \ No newline at end of file +} From 1a34e2910dc27008518e7921a41a4c74d581bd13 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 9 May 2024 14:57:29 +0200 Subject: [PATCH 10/23] Add snippet that was previously forgotten --- .../draganddrop/DragAndDropSnippets.kt | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt index 4291b8b0..7d1a4826 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt @@ -41,6 +41,14 @@ fun DragAndDropSnippet() { val url = "" // [START android_compose_drag_and_drop_1] + Modifier.dragAndDropSource { + detectTapGestures(onLongPress = { + // Transfer data here. + }) + } + // [END android_compose_drag_and_drop_1] + + // [START android_compose_drag_and_drop_2] Modifier.dragAndDropSource { detectTapGestures(onLongPress = { startTransfer( @@ -52,9 +60,9 @@ fun DragAndDropSnippet() { ) }) } - // [END android_compose_drag_and_drop_1] + // [END android_compose_drag_and_drop_2] - // [START android_compose_drag_and_drop_2] + // [START android_compose_drag_and_drop_3] Modifier.dragAndDropSource { detectTapGestures(onLongPress = { startTransfer( @@ -67,9 +75,9 @@ fun DragAndDropSnippet() { ) }) } - // [END android_compose_drag_and_drop_2] + // [END android_compose_drag_and_drop_3] - // [START android_compose_drag_and_drop_3] + // [START android_compose_drag_and_drop_4] val callback = remember { object : DragAndDropTarget { override fun onDrop(event: DragAndDropEvent): Boolean { @@ -78,17 +86,17 @@ fun DragAndDropSnippet() { } } } - // [END android_compose_drag_and_drop_3] + // [END android_compose_drag_and_drop_4] - // [START android_compose_drag_and_drop_4] + // [START android_compose_drag_and_drop_5] Modifier.dragAndDropTarget( shouldStartDragAndDrop = { event -> event.mimeTypes().contains(ClipDescription.MIMETYPE_TEXT_PLAIN) }, target = callback ) - // [END android_compose_drag_and_drop_4] + // [END android_compose_drag_and_drop_5] - // [START android_compose_drag_and_drop_5] + // [START android_compose_drag_and_drop_6] object : DragAndDropTarget { override fun onStarted(event: DragAndDropEvent) { // When the drag event starts @@ -108,5 +116,5 @@ fun DragAndDropSnippet() { override fun onDrop(event: DragAndDropEvent): Boolean = true } - // [END android_compose_drag_and_drop_5] + // [END android_compose_drag_and_drop_6] } From fc536b7c76b9fa1998ce4d797a4525c5a29964af Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 9 May 2024 14:57:29 +0200 Subject: [PATCH 11/23] Make the composable private --- .../example/compose/snippets/draganddrop/DragAndDropSnippets.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt index 7d1a4826..46e245a3 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/draganddrop/DragAndDropSnippets.kt @@ -36,7 +36,7 @@ import androidx.compose.ui.draganddrop.mimeTypes @RequiresApi(Build.VERSION_CODES.N) @OptIn(ExperimentalFoundationApi::class) @Composable -fun DragAndDropSnippet() { +private fun DragAndDropSnippet() { val url = "" From 7dca9cfe9f4ebc7f1f972ec89c076ab99cb26941 Mon Sep 17 00:00:00 2001 From: Jake Roseman <122034773+jakeroseman@users.noreply.github.com> Date: Fri, 10 May 2024 14:59:29 +0100 Subject: [PATCH 12/23] Additonal Compose components (#269) * first commit - checkbox * Apply Spotless * Fixing region tags * updating parent example * Apply Spotless --------- Co-authored-by: jakeroseman --- .../compose/snippets/SnippetsActivity.kt | 2 + .../compose/snippets/components/Checkbox.kt | 137 ++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + 3 files changed, 140 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/components/Checkbox.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 31037647..37dab73f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -30,6 +30,7 @@ import androidx.navigation.compose.rememberNavController import com.example.compose.snippets.animations.AnimationExamplesScreen import com.example.compose.snippets.components.AppBarExamples import com.example.compose.snippets.components.ButtonExamples +import com.example.compose.snippets.components.CheckboxExamples import com.example.compose.snippets.components.ChipExamples import com.example.compose.snippets.components.ComponentsScreen import com.example.compose.snippets.components.DialogExamples @@ -93,6 +94,7 @@ class SnippetsActivity : ComponentActivity() { TopComponentsDestination.ProgressIndicatorExamples -> ProgressIndicatorExamples() TopComponentsDestination.ScaffoldExample -> ScaffoldExample() TopComponentsDestination.AppBarExamples -> AppBarExamples() + TopComponentsDestination.CheckboxExamples -> CheckboxExamples() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Checkbox.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Checkbox.kt new file mode 100644 index 00000000..a203914c --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Checkbox.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2023 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.compose.snippets.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Text +import androidx.compose.material3.TriStateCheckbox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.state.ToggleableState +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun CheckboxExamples() { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column { + Text("Minimal checkbox example") + CheckboxMinimalExample() + } + Column { + Text("Parent checkbox example") + CheckboxParentExample() + } + } +} + +@Preview +// [START android_compose_components_checkbox_minimal] +@Composable +fun CheckboxMinimalExample() { + var checked by remember { mutableStateOf(true) } + + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + "Minimal checkbox" + ) + Checkbox( + checked = checked, + onCheckedChange = { checked = it } + ) + } + + Text( + if (checked) "Checkbox is checked" else "Checkbox is unchecked" + ) +} +// [END android_compose_components_checkbox_minimal] + +@Preview +// [START android_compose_components_checkbox_parent] +@Composable +fun CheckboxParentExample() { + // Initialize states for the child checkboxes + val childCheckedStates = remember { mutableStateListOf(false, false, false) } + + // Compute the parent state based on children's states + val parentState = when { + childCheckedStates.all { it } -> ToggleableState.On + childCheckedStates.none { it } -> ToggleableState.Off + else -> ToggleableState.Indeterminate + } + + Column { + // Parent TriStateCheckbox + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text("Select all") + TriStateCheckbox( + state = parentState, + onClick = { + // Determine new state based on current state + val newState = parentState != ToggleableState.On + childCheckedStates.forEachIndexed { index, _ -> + childCheckedStates[index] = newState + } + } + ) + } + + // Child Checkboxes + childCheckedStates.forEachIndexed { index, checked -> + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text("Option ${index + 1}") + Checkbox( + checked = checked, + onCheckedChange = { isChecked -> + // Update the individual child state + childCheckedStates[index] = isChecked + } + ) + } + } + } + + if (childCheckedStates.all { it }) { + Text("All options selected") + } +} +// [END android_compose_components_checkbox_parent] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index 60c07901..b7ab7c08 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -37,4 +37,5 @@ enum class TopComponentsDestination(val route: String, val title: String) { ProgressIndicatorExamples("progressIndicatorExamples", "Progress Indicators"), ScaffoldExample("scaffoldExample", "Scaffold"), AppBarExamples("appBarExamples", "App bars"), + CheckboxExamples("checkboxExamples", "Checkbox"), } From da1a92982f12e7eb1e9f5574b2dc304f8d0cf9c2 Mon Sep 17 00:00:00 2001 From: Jake Roseman <122034773+jakeroseman@users.noreply.github.com> Date: Thu, 16 May 2024 15:33:18 +0100 Subject: [PATCH 13/23] Add navigation example to app bars examples (#271) * First commit for app bars navigation example * Apply Spotless * Implementing a top app bar whose navigation icon actually pops from the back stack when clicked * Implementing a top app bar whose navigation icon actually pops from the back stack when clicked * Removing unnecessary code --------- Co-authored-by: jakeroseman --- .../compose/snippets/SnippetsActivity.kt | 8 +-- .../compose/snippets/components/AppBar.kt | 52 ++++++++++++++++--- .../snippets/components/ComponentsScreen.kt | 6 ++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 37dab73f..c3595ac6 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -65,7 +65,7 @@ class SnippetsActivity : ComponentActivity() { composable("LandingScreen") { LandingScreen { navController.navigate(it.route) } } - Destination.values().forEach { destination -> + Destination.entries.forEach { destination -> composable(destination.route) { when (destination) { Destination.BrushExamples -> BrushExamplesScreen() @@ -81,7 +81,7 @@ class SnippetsActivity : ComponentActivity() { } } } - TopComponentsDestination.values().forEach { destination -> + TopComponentsDestination.entries.forEach { destination -> composable(destination.route) { when (destination) { TopComponentsDestination.CardExamples -> CardExamples() @@ -93,7 +93,9 @@ class SnippetsActivity : ComponentActivity() { TopComponentsDestination.ButtonExamples -> ButtonExamples() TopComponentsDestination.ProgressIndicatorExamples -> ProgressIndicatorExamples() TopComponentsDestination.ScaffoldExample -> ScaffoldExample() - TopComponentsDestination.AppBarExamples -> AppBarExamples() + TopComponentsDestination.AppBarExamples -> AppBarExamples { + navController.popBackStack() + } TopComponentsDestination.CheckboxExamples -> CheckboxExamples() } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt index 25e7a005..3df1e91f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt @@ -23,8 +23,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Image @@ -59,9 +59,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -@Preview @Composable -fun AppBarExamples() { +fun AppBarExamples( + navigateBack: () -> Unit +) { var selection by remember { mutableStateOf("none") } Surface( @@ -74,12 +75,14 @@ fun AppBarExamples() { "topBarCenter" -> CenterAlignedTopAppBarExample() "topBarMedium" -> MediumTopAppBarExample() "topBarLarge" -> LargeTopAppBarExample() + "topBarNavigation" -> TopBarNavigationExample { navigateBack() } else -> AppBarOptions( toBottom = { selection = "bottomBar" }, toTopBarSmall = { selection = "topBar" }, toTopBarCenter = { selection = "topBarCenter" }, toTopBarMedium = { selection = "topBarMedium" }, toTopBarLarge = { selection = "topBarLarge" }, + toTopBarNavigation = { selection = "topBarNavigation" }, ) } } @@ -92,6 +95,7 @@ fun AppBarOptions( toTopBarCenter: () -> Unit, toTopBarMedium: () -> Unit, toTopBarLarge: () -> Unit, + toTopBarNavigation: () -> Unit, ) { Column() { Button({ toBottom() }) { @@ -109,6 +113,9 @@ fun AppBarOptions( Button({ toTopBarLarge() }) { Text("Large top bar") } + Button({ toTopBarNavigation() }) { + Text("Top bar navigation example") + } } } @@ -211,7 +218,7 @@ fun CenterAlignedTopAppBarExample() { navigationIcon = { IconButton(onClick = { /* do something */ }) { Icon( - imageVector = Icons.Filled.ArrowBack, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Localized description" ) } @@ -258,7 +265,7 @@ fun MediumTopAppBarExample() { navigationIcon = { IconButton(onClick = { /* do something */ }) { Icon( - imageVector = Icons.Filled.ArrowBack, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Localized description" ) } @@ -305,7 +312,7 @@ fun LargeTopAppBarExample() { navigationIcon = { IconButton(onClick = { /* do something */ }) { Icon( - imageVector = Icons.Filled.ArrowBack, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Localized description" ) } @@ -327,6 +334,39 @@ fun LargeTopAppBarExample() { } // [END android_compose_components_largetopappbar] +@OptIn(ExperimentalMaterial3Api::class) +// [START android_compose_components_navigation] +@Composable +fun TopBarNavigationExample( + navigateBack: () -> Unit, +) { + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + "Navigation example", + ) + }, + navigationIcon = { + IconButton(onClick = navigateBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Localized description" + ) + } + }, + ) + }, + ) { innerPadding -> + Text( + "Click the back button to pop from the back stack.", + modifier = Modifier.padding(innerPadding), + ) + } +} +// [END android_compose_components_navigation] + @Composable fun ScrollContent(innerPadding: PaddingValues) { val range = 1..100 diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/ComponentsScreen.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/ComponentsScreen.kt index 90d7a496..ce1bbae4 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/ComponentsScreen.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/ComponentsScreen.kt @@ -30,7 +30,9 @@ import androidx.compose.ui.unit.dp import com.example.compose.snippets.navigation.TopComponentsDestination @Composable -fun ComponentsScreen(navigate: (TopComponentsDestination) -> Unit) { +fun ComponentsScreen( + navigate: (TopComponentsDestination) -> Unit +) { LazyColumn( modifier = Modifier .padding(16.dp) @@ -38,7 +40,7 @@ fun ComponentsScreen(navigate: (TopComponentsDestination) -> Unit) { verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - items(TopComponentsDestination.values().toList()) { destination -> + items(TopComponentsDestination.entries) { destination -> NavigationItem(destination) { navigate( destination From ff89f155aca0cc71580b40ff353528bea7f304c8 Mon Sep 17 00:00:00 2001 From: Simona <35065668+simona-anomis@users.noreply.github.com> Date: Tue, 21 May 2024 16:44:37 +0100 Subject: [PATCH 14/23] Add nested scroll snippets (#274) * Add nested scroll snippets * Apply Spotless --------- Co-authored-by: simona-anomis --- .../touchinput/gestures/GesturesSnippets.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt index d480d1b8..dcddc073 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt @@ -62,6 +62,8 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.ComposeView @@ -351,3 +353,43 @@ private fun TransformableSample() { ) } // [END android_compose_touchinput_gestures_transformable] + +@Composable +fun NestedScrollSample() { + + // [START android_compose_touchinput_gestures_nestedscrollconnection] + val nestedScrollConnection = object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + println("Received onPreScroll callback.") + return Offset.Zero + } + + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset { + println("Received onPostScroll callback.") + return Offset.Zero + } + } + // [END android_compose_touchinput_gestures_nestedscrollconnection] + + // [START android_compose_touchinput_gestures_nestedscrolldisabled] + val disabledNestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset { + return if (source == NestedScrollSource.SideEffect) { + available + } else { + Offset.Zero + } + } + } + } + // [END android_compose_touchinput_gestures_nestedscrolldisabled] +} From e34c891bd17cad1e64ed71e8e4e01e3bacb1f555 Mon Sep 17 00:00:00 2001 From: Jake Roseman <122034773+jakeroseman@users.noreply.github.com> Date: Fri, 24 May 2024 13:41:05 +0100 Subject: [PATCH 15/23] Adding simple divider examples (#275) * Adding simple divider examples * Adding region tags * Apply Spotless --------- Co-authored-by: jakeroseman --- .../compose/snippets/SnippetsActivity.kt | 2 + .../compose/snippets/components/Divider.kt | 84 +++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + 3 files changed, 87 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/components/Divider.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index c3595ac6..ed28be7f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -34,6 +34,7 @@ import com.example.compose.snippets.components.CheckboxExamples import com.example.compose.snippets.components.ChipExamples import com.example.compose.snippets.components.ComponentsScreen import com.example.compose.snippets.components.DialogExamples +import com.example.compose.snippets.components.DividerExamples import com.example.compose.snippets.components.FloatingActionButtonExamples import com.example.compose.snippets.components.ProgressIndicatorExamples import com.example.compose.snippets.components.ScaffoldExample @@ -97,6 +98,7 @@ class SnippetsActivity : ComponentActivity() { navController.popBackStack() } TopComponentsDestination.CheckboxExamples -> CheckboxExamples() + TopComponentsDestination.DividerExamples -> DividerExamples() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Divider.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Divider.kt new file mode 100644 index 00000000..e9add3f4 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Divider.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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.compose.snippets.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun DividerExamples() { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text("Column with divider", fontWeight = FontWeight.Bold) + HorizontalDividerExample() + Text("Row with divider", fontWeight = FontWeight.Bold) + VerticalDividerExample() + } +} + +@Preview +// [START android_compose_components_horizontaldivider] +@Composable +fun HorizontalDividerExample() { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text("First item in list") + HorizontalDivider(thickness = 2.dp) + Text("Second item in list") + } +} +// [END android_compose_components_horizontaldivider] + +@Preview +// [START android_compose_components_verticaldivider] +@Composable +fun VerticalDividerExample() { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + Text("First item in row") + VerticalDivider(color = MaterialTheme.colorScheme.secondary) + Text("Second item in row") + } +} +// [END android_compose_components_verticaldivider] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index b7ab7c08..a920ad6b 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -38,4 +38,5 @@ enum class TopComponentsDestination(val route: String, val title: String) { ScaffoldExample("scaffoldExample", "Scaffold"), AppBarExamples("appBarExamples", "App bars"), CheckboxExamples("checkboxExamples", "Checkbox"), + DividerExamples("dividerExamples", "Dividers"), } From b883fb9bca33ef91cbd8f6a5d69d4cfba80f8d4e Mon Sep 17 00:00:00 2001 From: Jake Roseman <122034773+jakeroseman@users.noreply.github.com> Date: Tue, 28 May 2024 10:35:25 +0100 Subject: [PATCH 16/23] Adding badge examples (#277) * Adding badge examples * Apply Spotless * Tweaks * Moving conditional so there's always a BadgedBox * Apply Spotless --------- Co-authored-by: jakeroseman --- .../compose/snippets/SnippetsActivity.kt | 2 + .../compose/snippets/components/Badges.kt | 108 ++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + 3 files changed, 111 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/components/Badges.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index ed28be7f..ba27fb52 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -29,6 +29,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.compose.snippets.animations.AnimationExamplesScreen import com.example.compose.snippets.components.AppBarExamples +import com.example.compose.snippets.components.BadgeExamples import com.example.compose.snippets.components.ButtonExamples import com.example.compose.snippets.components.CheckboxExamples import com.example.compose.snippets.components.ChipExamples @@ -99,6 +100,7 @@ class SnippetsActivity : ComponentActivity() { } TopComponentsDestination.CheckboxExamples -> CheckboxExamples() TopComponentsDestination.DividerExamples -> DividerExamples() + TopComponentsDestination.BadgeExamples -> BadgeExamples() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Badges.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Badges.kt new file mode 100644 index 00000000..f2f83b01 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Badges.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2023 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.compose.snippets.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Mail +import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun BadgeExamples() { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text("Minimal badge example", fontWeight = FontWeight.Bold) + BadgeExample() + Text("Badge number example", fontWeight = FontWeight.Bold) + BadgeInteractiveExample() + } +} + +@Preview +// [START android_compose_components_badge] +@Composable +fun BadgeExample() { + BadgedBox( + badge = { + Badge() + } + ) { + Icon( + imageVector = Icons.Filled.Mail, + contentDescription = "Email" + ) + } +} +// [END android_compose_components_badge] + +@Preview +// [START android_compose_components_badgeinteractive] +@Composable +fun BadgeInteractiveExample() { + var itemCount by remember { mutableStateOf(0) } + + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + BadgedBox( + badge = { + if (itemCount > 0) { + Badge( + containerColor = Color.Red, + contentColor = Color.White + ) { + Text("$itemCount") + } + } + } + ) { + Icon( + imageVector = Icons.Filled.ShoppingCart, + contentDescription = "Shopping cart", + ) + } + Button(onClick = { itemCount++ }) { + Text("Add item") + } + } +} +// [END android_compose_components_badgeinteractive] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index a920ad6b..820109b4 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -39,4 +39,5 @@ enum class TopComponentsDestination(val route: String, val title: String) { AppBarExamples("appBarExamples", "App bars"), CheckboxExamples("checkboxExamples", "Checkbox"), DividerExamples("dividerExamples", "Dividers"), + BadgeExamples("badgeExamples", "Badges"), } From 07b51c299a4a7c443060fa924b284b31de48307a Mon Sep 17 00:00:00 2001 From: Jake Roseman <122034773+jakeroseman@users.noreply.github.com> Date: Fri, 7 Jun 2024 08:59:44 +0100 Subject: [PATCH 17/23] Partial bottom sheet example (#279) * Example of a bottom sheet that only partially displays * Minor tweaks, adding regions tags * Apply Spotless * Adding padding --------- Co-authored-by: jakeroseman --- .../compose/snippets/SnippetsActivity.kt | 2 + .../snippets/components/BottomSheet.kt | 72 +++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + 3 files changed, 75 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/components/BottomSheet.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index ba27fb52..02f3fbe2 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -37,6 +37,7 @@ import com.example.compose.snippets.components.ComponentsScreen import com.example.compose.snippets.components.DialogExamples import com.example.compose.snippets.components.DividerExamples import com.example.compose.snippets.components.FloatingActionButtonExamples +import com.example.compose.snippets.components.PartialBottomSheet import com.example.compose.snippets.components.ProgressIndicatorExamples import com.example.compose.snippets.components.ScaffoldExample import com.example.compose.snippets.components.SliderExamples @@ -101,6 +102,7 @@ class SnippetsActivity : ComponentActivity() { TopComponentsDestination.CheckboxExamples -> CheckboxExamples() TopComponentsDestination.DividerExamples -> DividerExamples() TopComponentsDestination.BadgeExamples -> BadgeExamples() + TopComponentsDestination.PartialBottomSheet -> PartialBottomSheet() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/BottomSheet.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/BottomSheet.kt new file mode 100644 index 00000000..c7e37431 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/BottomSheet.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2024 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.compose.snippets.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +// [START android_compose_components_partialbottomsheet] +@Composable +fun PartialBottomSheet() { + var showBottomSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = false, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Button( + onClick = { showBottomSheet = true } + ) { + Text("Display partial bottom sheet") + } + + if (showBottomSheet) { + ModalBottomSheet( + modifier = Modifier.fillMaxHeight(), + sheetState = sheetState, + onDismissRequest = { showBottomSheet = false } + ) { + Text( + "Swipe up to open sheet. Swipe down to dismiss.", + modifier = Modifier.padding(16.dp) + ) + } + } + } +} +// [END android_compose_components_partialbottomsheet] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index 820109b4..9e64792f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -40,4 +40,5 @@ enum class TopComponentsDestination(val route: String, val title: String) { CheckboxExamples("checkboxExamples", "Checkbox"), DividerExamples("dividerExamples", "Dividers"), BadgeExamples("badgeExamples", "Badges"), + PartialBottomSheet("partialBottomSheets", "Partial Bottom Sheet"), } From 2f6441ceefecf3f562f491fa7d7f341f5d4db66c Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Mon, 10 Jun 2024 14:10:51 +0100 Subject: [PATCH 18/23] Adds Wear voice input --- .github/workflows/apply_spotless.yml | 3 + .github/workflows/build.yml | 2 + gradle/libs.versions.toml | 11 ++ settings.gradle.kts | 1 + wear/.gitignore | 1 + wear/build.gradle.kts | 78 +++++++++++ wear/lint.xml | 8 ++ wear/proguard-rules.pro | 21 +++ wear/src/main/AndroidManifest.xml | 39 ++++++ .../com/example/wear/snippets/MainActivity.kt | 43 ++++++ .../snippets/voiceinput/VoiceInputScreen.kt | 122 ++++++++++++++++++ .../res/drawable/ic_launcher_background.xml | 74 +++++++++++ .../res/drawable/ic_launcher_foreground.xml | 31 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 4292 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2762 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 6126 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 8874 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 12130 bytes wear/src/main/res/values/strings.xml | 5 + 21 files changed, 449 insertions(+) create mode 100644 wear/.gitignore create mode 100644 wear/build.gradle.kts create mode 100644 wear/lint.xml create mode 100644 wear/proguard-rules.pro create mode 100644 wear/src/main/AndroidManifest.xml create mode 100644 wear/src/main/java/com/example/wear/snippets/MainActivity.kt create mode 100644 wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt create mode 100644 wear/src/main/res/drawable/ic_launcher_background.xml create mode 100644 wear/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 wear/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 wear/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 wear/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 wear/src/main/res/values/strings.xml diff --git a/.github/workflows/apply_spotless.yml b/.github/workflows/apply_spotless.yml index 79c68e23..0ab86301 100644 --- a/.github/workflows/apply_spotless.yml +++ b/.github/workflows/apply_spotless.yml @@ -47,3 +47,6 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Apply Spotless + + - name: Run spotlessApply for Wear + run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51f9b6cb..8337b423 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,3 +43,5 @@ jobs: run: ./gradlew :compose:recomposehighlighter:build - name: Build kotlin snippets run: ./gradlew :kotlin:build + - name: Build Wear snippets + run: ./gradlew :wear:build diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 62a11563..82937258 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,11 @@ recyclerview = "1.3.2" # @keep targetSdk = "34" version-catalog-update = "0.8.3" +playServicesWearable = "18.1.0" +wearComposeMaterial = "1.2.1" +wearComposeFoundation = "1.2.1" +coreSplashscreen = "1.0.1" +horologist = "0.5.24" [libraries] accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" } @@ -110,6 +115,12 @@ junit = { module = "junit:junit", version.ref = "junit" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } +play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" } +compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearComposeMaterial" } +compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearComposeFoundation" } +androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } +horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" } +horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1edd26a8..f9ba4f22 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,4 +26,5 @@ include( ":compose:recomposehighlighter", ":kotlin", ":compose:snippets", + ":wear", ) diff --git a/wear/.gitignore b/wear/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/wear/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts new file mode 100644 index 00000000..f31e8ba8 --- /dev/null +++ b/wear/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.example.wear" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.wear" + minSdk = 26 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + vectorDrawables { + useSupportLibrary = true + } + + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlin { + jvmToolchain(17) + } + + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) + + implementation(libs.play.services.wearable) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.compose.material) + implementation(libs.compose.foundation) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.core.splashscreen) + implementation(libs.horologist.compose.layout) + implementation(libs.horologist.compose.material) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) + testImplementation(libs.junit) + + androidTestImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.core) + androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation(libs.androidx.test.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test) + debugImplementation(libs.androidx.compose.ui.tooling) +} diff --git a/wear/lint.xml b/wear/lint.xml new file mode 100644 index 00000000..44fac75b --- /dev/null +++ b/wear/lint.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/wear/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f0d2328e --- /dev/null +++ b/wear/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wear/src/main/java/com/example/wear/snippets/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt new file mode 100644 index 00000000..0921cb04 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt @@ -0,0 +1,43 @@ +/* + * 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 + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import com.example.wear.snippets.voiceinput.VoiceInputScreen +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + + setTheme(android.R.style.Theme_DeviceDefault) + + setContent { + WearApp() + } + } +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun WearApp() { + VoiceInputScreen() +} diff --git a/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt new file mode 100644 index 00000000..36e3e19b --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2024 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.voiceinput + +/* + * 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. + */ + +import android.content.Intent +import android.speech.RecognizerIntent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.example.wear.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults +import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType +import com.google.android.horologist.compose.layout.ScreenScaffold +import com.google.android.horologist.compose.material.Chip +import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll + +/** + * Shows voice input option + */ +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun VoiceInputScreen() { + // [START android_wear_voice_input] + var textForVoiceInput by remember { mutableStateOf("") } + + val voiceLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { activityResult -> + // This is where you process the intent and extract the speech text from the intent. + activityResult.data?.let { data -> + val results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) + textForVoiceInput = results?.get(0) ?: "None" + } + } + + // [START_EXCLUDE android_wear_voice_input] + val scrollState = rememberScrollState() + + ScreenScaffold(scrollState = scrollState) { + val padding = ScalingLazyColumnDefaults.padding( + first = ItemType.Text, + last = ItemType.Chip + )() + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .rotaryWithScroll(scrollState) + .padding(padding), + verticalArrangement = Arrangement.Center + ) { + // [END_EXCLUDE android_wear_voice_input] + // Create an intent that can start the Speech Recognizer activity + val voiceIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra( + RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM + ) + + putExtra( + RecognizerIntent.EXTRA_PROMPT, + stringResource(R.string.voice_text_entry_label) + ) + } + // Invoke the process from a chip + Chip( + onClick = { + voiceLauncher.launch(voiceIntent) + }, + label = stringResource(R.string.voice_input_label), + secondaryLabel = textForVoiceInput + ) + // [START_EXCLUDE android_wear_voice_input] + } + } + // [END_EXCLUDE android_wear_voice_input] + // [END android_wear_voice_input] +} diff --git a/wear/src/main/res/drawable/ic_launcher_background.xml b/wear/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..ca3826a4 --- /dev/null +++ b/wear/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wear/src/main/res/drawable/ic_launcher_foreground.xml b/wear/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..da1dcd94 --- /dev/null +++ b/wear/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..bbd3e021 --- /dev/null +++ b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..bbd3e021 --- /dev/null +++ b/wear/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a4383dac1d0ba729b308b25a2963f657e9af5846 GIT binary patch literal 4292 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 AZ2$lO literal 0 HcmV?d00001 diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/wear/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..59beb611809efca5e1b95822b143c4a78cfa82a4 GIT binary patch literal 8874 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 + Wear Snippets + Voice Input + Voice Text Entry + \ No newline at end of file From a5736c15ed1639437a8a6e180af39f45a000e11c Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Fri, 14 Jun 2024 17:00:49 +0100 Subject: [PATCH 19/23] add the structure of the screen --- .../wear/snippets/voiceinput/VoiceInputScreen.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt index 36e3e19b..4109b95e 100644 --- a/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt +++ b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt @@ -77,15 +77,19 @@ fun VoiceInputScreen() { } } - // [START_EXCLUDE android_wear_voice_input] val scrollState = rememberScrollState() ScreenScaffold(scrollState = scrollState) { + //rest of implementation here + // [START_EXCLUDE] val padding = ScalingLazyColumnDefaults.padding( first = ItemType.Text, last = ItemType.Chip )() + // [END_EXCLUDE] Column( + //rest of implementation here + // [START_EXCLUDE] modifier = Modifier .fillMaxSize() .verticalScroll(scrollState) @@ -93,7 +97,8 @@ fun VoiceInputScreen() { .padding(padding), verticalArrangement = Arrangement.Center ) { - // [END_EXCLUDE android_wear_voice_input] + // [END_EXCLUDE] + // Create an intent that can start the Speech Recognizer activity val voiceIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra( @@ -114,9 +119,7 @@ fun VoiceInputScreen() { label = stringResource(R.string.voice_input_label), secondaryLabel = textForVoiceInput ) - // [START_EXCLUDE android_wear_voice_input] } } - // [END_EXCLUDE android_wear_voice_input] // [END android_wear_voice_input] } From 37f5da8fbe143712db219a3812a3bf06e6cc94e6 Mon Sep 17 00:00:00 2001 From: compose-devrel-github-bot <118755852+compose-devrel-github-bot@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:43:02 +0100 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=A4=96=20Update=20Dependencies=20(#?= =?UTF-8?q?282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82937258..44ac7fee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,30 +24,30 @@ coil = "2.5.0" # @keep compileSdk = "34" compose-compiler = "1.5.4" +coreSplashscreen = "1.0.1" coroutines = "1.7.3" google-maps = "18.2.0" gradle-versions = "0.51.0" hilt = "2.50" +horologist = "0.5.24" junit = "4.13.2" # @pin Update in conjuction with Compose Compiler kotlin = "1.9.20" ksp = "1.8.0-1.0.9" maps-compose = "4.3.2" -material = "1.11.0" +material = "1.4.0-beta01" material3-adaptive = "1.0.0-alpha12" material3-adaptive-navigation-suite = "1.0.0-alpha07" media3 = "1.2.1" # @keep minSdk = "21" +playServicesWearable = "18.1.0" recyclerview = "1.3.2" # @keep targetSdk = "34" version-catalog-update = "0.8.3" -playServicesWearable = "18.1.0" -wearComposeMaterial = "1.2.1" -wearComposeFoundation = "1.2.1" -coreSplashscreen = "1.0.1" -horologist = "0.5.24" +wearComposeFoundation = "1.3.0" +wearComposeMaterial = "1.3.0" [libraries] accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" } @@ -85,6 +85,7 @@ androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayo androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "androidx-constraintlayout-compose" } androidx-coordinator-layout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinator-layout" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-corektx" } +androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } androidx-emoji2-views = { module = "androidx.emoji2:emoji2-views", version.ref = "androidx-emoji2-views" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment-ktx" } androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "androidx-glance-appwidget" } @@ -106,21 +107,20 @@ androidx-test-runner = "androidx.test:runner:1.5.2" androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window" } androidx-work-runtime-ktx = "androidx.work:work-runtime-ktx:2.9.0" coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } +compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" } +compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" } google-android-material = { module = "com.google.android.material:material", version.ref = "material" } googlemaps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "maps-compose" } googlemaps-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "google-maps" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" } +horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" } junit = { module = "junit:junit", version.ref = "junit" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } -play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" } -compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearComposeMaterial" } -compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearComposeFoundation" } -androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } -horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" } -horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" } +play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "playServicesWearable" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 6da9f024bd0d394e0fbc8e93cddcf5af048b81e0 Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Tue, 18 Jun 2024 17:40:35 +0100 Subject: [PATCH 21/23] Adds snippet for Navigation --- gradle/libs.versions.toml | 2 + wear/build.gradle.kts | 1 + .../com/example/wear/snippets/MainActivity.kt | 6 +- .../wear/snippets/navigation/Navigation.kt | 136 ++++++++++++++++++ wear/src/main/res/values/strings.xml | 1 + 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 44ac7fee..d054cc13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,6 +48,7 @@ targetSdk = "34" version-catalog-update = "0.8.3" wearComposeFoundation = "1.3.0" wearComposeMaterial = "1.3.0" +composeUiTooling = "1.3.1" [libraries] accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" } @@ -121,6 +122,7 @@ kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.re kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "playServicesWearable" } +compose-ui-tooling = { group = "androidx.wear.compose", name = "compose-ui-tooling", version.ref = "composeUiTooling" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index f31e8ba8..d6d44736 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(composeBom) androidTestImplementation(composeBom) + implementation(libs.compose.ui.tooling) implementation(libs.play.services.wearable) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.ui) diff --git a/wear/src/main/java/com/example/wear/snippets/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt index 0921cb04..ea2e27cd 100644 --- a/wear/src/main/java/com/example/wear/snippets/MainActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt @@ -20,8 +20,10 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable +import com.example.wear.snippets.navigation.navigation import com.example.wear.snippets.voiceinput.VoiceInputScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.AppScaffold class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -39,5 +41,7 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalHorologistApi::class) @Composable fun WearApp() { - VoiceInputScreen() + AppScaffold { + navigation() + } } diff --git a/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt new file mode 100644 index 00000000..53e4d4aa --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2022 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.navigation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.wear.compose.material.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 com.example.wear.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumn +import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults +import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType +import com.google.android.horologist.compose.layout.ScreenScaffold +import com.google.android.horologist.compose.layout.rememberResponsiveColumnState +import com.google.android.horologist.compose.material.Chip +import com.google.android.horologist.compose.material.ListHeaderDefaults.firstItemPadding +import com.google.android.horologist.compose.material.ResponsiveListHeader +import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll + + @Composable + fun navigation() { + // [START android_wear_navigation] + val navController = rememberSwipeDismissableNavController() + SwipeDismissableNavHost( + navController = navController, + startDestination = "message_list" + ) { + composable("message_list") { + MessageList(onMessageClick = { id -> + navController.navigate("message_detail/$id") + }) + } + composable("message_detail/{id}") { + MessageDetail(id = it.arguments?.getString("id")!!) + } + } + // [END android_wear_navigation] + } + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun MessageDetail(id: String){ + val scrollState = rememberScrollState() + + ScreenScaffold(scrollState = scrollState) { + val padding = ScalingLazyColumnDefaults.padding( + first = ItemType.Text, + last = ItemType.Text + )() + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .rotaryWithScroll(scrollState) + .padding(padding), + verticalArrangement = Arrangement.Center + ) { + Text(text= id, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxSize()) + } + } +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun MessageList(onMessageClick: (String) -> Unit){ + val columnState = rememberResponsiveColumnState( + contentPadding = ScalingLazyColumnDefaults.padding( + first = ItemType.Text, + last = ItemType.Chip + ) + ) + + ScreenScaffold(scrollState = columnState) { + ScalingLazyColumn( + columnState = columnState, + modifier = Modifier + .fillMaxSize() + ) { + item { + ResponsiveListHeader(contentPadding = firstItemPadding()) { + Text(text = stringResource(R.string.message_list)) + } + } + item { + Chip(label = "Message 1", onClick = { onMessageClick("message1")}) + } + item { + Chip(label = "Message 2", onClick = { onMessageClick("message2")}) + } + } + } +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun MessageDetailPreview() { + MessageDetail("test") +} + +@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 9fecdf81..31b64b4d 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ Wear Snippets Voice Input Voice Text Entry + Message List \ No newline at end of file From 1ef854252f92e5ab17afb2fb4fa1483695c7e47c Mon Sep 17 00:00:00 2001 From: Ben Trengrove Date: Tue, 25 Jun 2024 12:09:05 +1200 Subject: [PATCH 22/23] Update the LaunchedEffect snipper (#285) --- .../sideeffects/SideEffectsSnippets.kt | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/sideeffects/SideEffectsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/sideeffects/SideEffectsSnippets.kt index 4241e4af..c68791a0 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/sideeffects/SideEffectsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/sideeffects/SideEffectsSnippets.kt @@ -18,12 +18,13 @@ package com.example.compose.snippets.sideeffects import android.media.Image import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Animatable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Button +import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -50,53 +51,32 @@ import com.example.compose.snippets.interop.FirebaseAnalytics import com.example.compose.snippets.interop.User import com.example.compose.snippets.kotlin.Message import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -// [START android_compose_side_effects_launchedeffect] @Composable -fun MyScreen( - state: UiState>, - snackbarHostState: SnackbarHostState -) { - - // If the UI state contains an error, show snackbar - if (state.hasError) { - - // `LaunchedEffect` will cancel and re-launch if - // `scaffoldState.snackbarHostState` changes - LaunchedEffect(snackbarHostState) { - // Show snackbar using a coroutine, when the coroutine is cancelled the - // snackbar will automatically dismiss. This coroutine will cancel whenever - // `state.hasError` is false, and only start when `state.hasError` is true - // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes. - snackbarHostState.showSnackbar( - message = "Error message", - actionLabel = "Retry message" - ) - } - } - - Scaffold( - snackbarHost = { - SnackbarHost(hostState = snackbarHostState) +fun MyScreen() { +// [START android_compose_side_effects_launchedeffect] + // Allow the pulse rate to be configured, so it can be sped up if the user is running + // out of time + var pulseRateMs by remember { mutableStateOf(3000L) } + val alpha = remember { Animatable(1f) } + LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes + while (isActive) { + delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user + alpha.animateTo(0f) + alpha.animateTo(1f) } - ) { contentPadding -> - // [START_EXCLUDE] - Box(Modifier.padding(contentPadding)) - // [END_EXCLUDE] } +// [END android_compose_side_effects_launchedeffect] } + // [START_EXCLUDE silent] class Movie { val url = "" val id = "" } -class UiState { - val hasError = true -} // [END_EXCLUDE] -// [END android_compose_side_effects_launchedeffect] - // [START android_compose_side_effects_remembercoroutinescope] @Composable fun MoviesScreen(snackbarHostState: SnackbarHostState) { From 184d4348288bea29053e0873d728d11a221dd06e Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Fri, 21 Jun 2024 16:36:59 +0100 Subject: [PATCH 23/23] Adds sample for rotary and timepicker --- .../com/example/wear/snippets/MainActivity.kt | 9 +- .../wear/snippets/navigation/Navigation.kt | 22 +- .../example/wear/snippets/rotary/Rotary.kt | 219 ++++++++++++++++++ .../snippets/voiceinput/VoiceInputScreen.kt | 106 +++++---- 4 files changed, 294 insertions(+), 62 deletions(-) create mode 100644 wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt diff --git a/wear/src/main/java/com/example/wear/snippets/MainActivity.kt b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt index ea2e27cd..1e51a3fe 100644 --- a/wear/src/main/java/com/example/wear/snippets/MainActivity.kt +++ b/wear/src/main/java/com/example/wear/snippets/MainActivity.kt @@ -20,10 +20,8 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable -import com.example.wear.snippets.navigation.navigation -import com.example.wear.snippets.voiceinput.VoiceInputScreen +import com.example.wear.snippets.rotary.TimePicker import com.google.android.horologist.annotations.ExperimentalHorologistApi -import com.google.android.horologist.compose.layout.AppScaffold class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -41,7 +39,6 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalHorologistApi::class) @Composable fun WearApp() { - AppScaffold { - navigation() - } + // insert here the snippet you want to test + TimePicker() } diff --git a/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt index 53e4d4aa..5c7fbe06 100644 --- a/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt +++ b/wear/src/main/java/com/example/wear/snippets/navigation/Navigation.kt @@ -34,6 +34,7 @@ import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales import com.example.wear.R import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.AppScaffold import com.google.android.horologist.compose.layout.ScalingLazyColumn import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType @@ -44,8 +45,9 @@ import com.google.android.horologist.compose.material.ListHeaderDefaults.firstIt import com.google.android.horologist.compose.material.ResponsiveListHeader import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll - @Composable - fun navigation() { +@Composable +fun navigation() { + AppScaffold { // [START android_wear_navigation] val navController = rememberSwipeDismissableNavController() SwipeDismissableNavHost( @@ -63,10 +65,11 @@ import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll } // [END android_wear_navigation] } +} @OptIn(ExperimentalHorologistApi::class) @Composable -fun MessageDetail(id: String){ +fun MessageDetail(id: String) { val scrollState = rememberScrollState() ScreenScaffold(scrollState = scrollState) { @@ -82,16 +85,18 @@ fun MessageDetail(id: String){ .padding(padding), verticalArrangement = Arrangement.Center ) { - Text(text= id, + Text( + text = id, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxSize()) + modifier = Modifier.fillMaxSize() + ) } } } @OptIn(ExperimentalHorologistApi::class) @Composable -fun MessageList(onMessageClick: (String) -> Unit){ +fun MessageList(onMessageClick: (String) -> Unit) { val columnState = rememberResponsiveColumnState( contentPadding = ScalingLazyColumnDefaults.padding( first = ItemType.Text, @@ -111,10 +116,10 @@ fun MessageList(onMessageClick: (String) -> Unit){ } } item { - Chip(label = "Message 1", onClick = { onMessageClick("message1")}) + Chip(label = "Message 1", onClick = { onMessageClick("message1") }) } item { - Chip(label = "Message 2", onClick = { onMessageClick("message2")}) + Chip(label = "Message 2", onClick = { onMessageClick("message2") }) } } } @@ -133,4 +138,3 @@ fun MessageDetailPreview() { fun MessageListPreview() { MessageList(onMessageClick = {}) } - diff --git a/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt b/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt new file mode 100644 index 00000000..92f84673 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/rotary/Rotary.kt @@ -0,0 +1,219 @@ +/* + * Copyright 2024 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.rotary + +import android.view.MotionEvent +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.input.rotary.onRotaryScrollEvent +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.ExperimentalWearFoundationApi +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import androidx.wear.compose.foundation.rememberActiveFocusRequester +import androidx.wear.compose.material.Chip +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Picker +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.rememberPickerState +import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices +import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales +import kotlinx.coroutines.launch + +@OptIn(ExperimentalWearFoundationApi::class) +@Composable +fun ScrollableScreen() { + // This sample doesn't add a Time Text at the top of the screen. + // If using Time Text, add padding to ensure content does not overlap with Time Text. + // [START android_wear_rotary_input] + val listState = rememberScalingLazyListState() + Scaffold( + positionIndicator = { + PositionIndicator(scalingLazyListState = listState) + } + ) { + + val focusRequester = rememberActiveFocusRequester() + val coroutineScope = rememberCoroutineScope() + + ScalingLazyColumn( + modifier = Modifier + .onRotaryScrollEvent { + coroutineScope.launch { + listState.scrollBy(it.verticalScrollPixels) + listState.animateScrollBy(0f) + } + true + } + .focusRequester(focusRequester) + .focusable() + .fillMaxSize(), + state = listState + ) { + // Content goes here + // [START_EXCLUDE] + items(count = 5) { + Chip(onClick = { }, label = { Text("Item #$it") }) + } + // [END_EXCLUDE] + } + } + // [END android_wear_rotary_input] +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun TimePicker() { + val textStyle = MaterialTheme.typography.display1 + + // [START android_wear_rotary_input_picker] + var selectedColumn by remember { mutableIntStateOf(0) } + + val hoursFocusRequester = remember { FocusRequester() } + val minutesRequester = remember { FocusRequester() } + // [START_EXCLUDE] + val coroutineScope = rememberCoroutineScope() + + @Composable + fun Option(column: Int, text: String) = Box(modifier = Modifier.fillMaxSize()) { + Text( + text = text, style = textStyle, + color = if (selectedColumn == column) MaterialTheme.colors.secondary + else MaterialTheme.colors.onBackground, + modifier = Modifier + .pointerInteropFilter { + if (it.action == MotionEvent.ACTION_DOWN) selectedColumn = column + true + } + ) + } + // [END_EXCLUDE] + Scaffold(modifier = Modifier.fillMaxSize()) { + Row( + // [START_EXCLUDE] + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + // [END_EXCLUDE] + // ... + ) { + // [START_EXCLUDE] + val hourState = rememberPickerState( + initialNumberOfOptions = 12, + initiallySelectedOption = 5 + ) + val hourContentDescription by remember { + derivedStateOf { "${hourState.selectedOption + 1 } hours" } + } + // [END_EXCLUDE] + Picker( + readOnly = selectedColumn != 0, + modifier = Modifier.size(64.dp, 100.dp) + .onRotaryScrollEvent { + coroutineScope.launch { + hourState.scrollBy(it.verticalScrollPixels) + } + true + } + .focusRequester(hoursFocusRequester) + .focusable(), + onSelected = { selectedColumn = 0 }, + // ... + // [START_EXCLUDE] + state = hourState, + contentDescription = hourContentDescription, + option = { hour: Int -> Option(0, "%2d".format(hour + 1)) } + // [END_EXCLUDE] + ) + // [START_EXCLUDE] + Spacer(Modifier.width(8.dp)) + Text(text = ":", style = textStyle, color = MaterialTheme.colors.onBackground) + Spacer(Modifier.width(8.dp)) + val minuteState = + rememberPickerState(initialNumberOfOptions = 60, initiallySelectedOption = 0) + val minuteContentDescription by remember { + derivedStateOf { "${minuteState.selectedOption} minutes" } + } + // [END_EXCLUDE] + Picker( + readOnly = selectedColumn != 1, + modifier = Modifier.size(64.dp, 100.dp) + .onRotaryScrollEvent { + coroutineScope.launch { + minuteState.scrollBy(it.verticalScrollPixels) + } + true + } + .focusRequester(minutesRequester) + .focusable(), + onSelected = { selectedColumn = 1 }, + // ... + // [START_EXCLUDE] + state = minuteState, + contentDescription = minuteContentDescription, + option = { minute: Int -> Option(1, "%02d".format(minute)) } + // [END_EXCLUDE] + ) + LaunchedEffect(selectedColumn) { + listOf( + hoursFocusRequester, + minutesRequester + )[selectedColumn] + .requestFocus() + } + } + } + // [END android_wear_rotary_input_picker] +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun ScrollableScreenPreview() { + ScrollableScreen() +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun TimePickerPreview() { + TimePicker() +} diff --git a/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt index 4109b95e..fa80ab80 100644 --- a/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt +++ b/wear/src/main/java/com/example/wear/snippets/voiceinput/VoiceInputScreen.kt @@ -49,8 +49,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices +import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales import com.example.wear.R import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.AppScaffold import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType import com.google.android.horologist.compose.layout.ScreenScaffold @@ -63,63 +66,72 @@ import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll @OptIn(ExperimentalHorologistApi::class) @Composable fun VoiceInputScreen() { - // [START android_wear_voice_input] - var textForVoiceInput by remember { mutableStateOf("") } + AppScaffold { + // [START android_wear_voice_input] + var textForVoiceInput by remember { mutableStateOf("") } - val voiceLauncher = - rememberLauncherForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { activityResult -> - // This is where you process the intent and extract the speech text from the intent. - activityResult.data?.let { data -> - val results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) - textForVoiceInput = results?.get(0) ?: "None" + val voiceLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { activityResult -> + // This is where you process the intent and extract the speech text from the intent. + activityResult.data?.let { data -> + val results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) + textForVoiceInput = results?.get(0) ?: "None" + } } - } - val scrollState = rememberScrollState() + val scrollState = rememberScrollState() - ScreenScaffold(scrollState = scrollState) { - //rest of implementation here - // [START_EXCLUDE] - val padding = ScalingLazyColumnDefaults.padding( - first = ItemType.Text, - last = ItemType.Chip - )() - // [END_EXCLUDE] - Column( - //rest of implementation here + ScreenScaffold(scrollState = scrollState) { + // rest of implementation here // [START_EXCLUDE] - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .rotaryWithScroll(scrollState) - .padding(padding), - verticalArrangement = Arrangement.Center - ) { + val padding = ScalingLazyColumnDefaults.padding( + first = ItemType.Text, + last = ItemType.Chip + )() // [END_EXCLUDE] + Column( + // rest of implementation here + // [START_EXCLUDE] + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .rotaryWithScroll(scrollState) + .padding(padding), + verticalArrangement = Arrangement.Center + ) { + // [END_EXCLUDE] - // Create an intent that can start the Speech Recognizer activity - val voiceIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { - putExtra( - RecognizerIntent.EXTRA_LANGUAGE_MODEL, - RecognizerIntent.LANGUAGE_MODEL_FREE_FORM - ) + // Create an intent that can start the Speech Recognizer activity + val voiceIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra( + RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM + ) - putExtra( - RecognizerIntent.EXTRA_PROMPT, - stringResource(R.string.voice_text_entry_label) + putExtra( + RecognizerIntent.EXTRA_PROMPT, + stringResource(R.string.voice_text_entry_label) + ) + } + // Invoke the process from a chip + Chip( + onClick = { + voiceLauncher.launch(voiceIntent) + }, + label = stringResource(R.string.voice_input_label), + secondaryLabel = textForVoiceInput ) } - // Invoke the process from a chip - Chip( - onClick = { - voiceLauncher.launch(voiceIntent) - }, - label = stringResource(R.string.voice_input_label), - secondaryLabel = textForVoiceInput - ) } + // [END android_wear_voice_input] } - // [END android_wear_voice_input] +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun VoiceInputScreenPreview() { + VoiceInputScreen() }