diff --git a/.github/workflows/apply_spotless.yml b/.github/workflows/apply_spotless.yml index 5ba1f6fe5..3727da63a 100644 --- a/.github/workflows/apply_spotless.yml +++ b/.github/workflows/apply_spotless.yml @@ -42,13 +42,7 @@ jobs: java-version: '17' - name: Run spotlessApply - run: ./gradlew :compose:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace - - - name: Run spotlessApply for Wear - run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace - - - name: Run spotlessApply for Misc - run: ./gradlew :misc:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace + run: ./gradlew spotlessApply --stacktrace - name: Auto-commit if spotlessApply has changes uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml new file mode 100644 index 000000000..6e6d9a06a --- /dev/null +++ b/.github/workflows/build-ios.yml @@ -0,0 +1,51 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. +name: Build snippets + +on: + push: + branches: [ '*' ] + paths: + - 'kmp/**' + - '.github/workflows/build-ios.yml' + pull_request: + branches: [ '*' ] + paths: + - 'kmp/**' + - '.github/workflows/build-ios.yml' + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-build-ios + cancel-in-progress: true +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build_ios: + name: Build iOS app + runs-on: macos-latest + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Checkout + uses: actions/checkout@v5 + + - name: Build iOS app + uses: mxcl/xcodebuild@v3 + with: + xcode: ^16 + scheme: iosApp + platform: iOS + action: build + working-directory: kmp/iosApp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97b36f468..1d3c201ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,18 +32,12 @@ jobs: - uses: actions/checkout@v4 with: token: ${{ secrets.PAT || github.token }} - - name: set up Java 17 + - name: set up Java 25 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: '17' - - name: Build Compose - run: ./gradlew :compose:snippets:build - - name: Build recompose snippets - run: ./gradlew :compose:recomposehighlighter:build - - name: Build kotlin snippets - run: ./gradlew :kotlin:build - - name: Build Wear snippets - run: ./gradlew :wear:build - - name: Build misc snippets - run: ./gradlew :misc:build + java-version: '25' + - name: Build All + run: ./gradlew build --stacktrace + - name: Build Watch Face Push validation snippets + run: ./gradlew :watchfacepush:validator:run --stacktrace diff --git a/.gitignore b/.gitignore index 30e5f7bdc..7b190dc83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,22 @@ *.iml .gradle /local.properties -/.idea/caches/build_file_checksums.ser -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml +.idea/ .DS_Store -/build +build /captures .externalNativeBuild -.idea/* -/.idea/* +.kotlin + +### Xcode ### +## User settings +xcuserdata/ + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index ae78c113f..000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/bluetoothle/src/main/AndroidManifest.xml b/bluetoothle/src/main/AndroidManifest.xml index 1661e746b..b42865576 100644 --- a/bluetoothle/src/main/AndroidManifest.xml +++ b/bluetoothle/src/main/AndroidManifest.xml @@ -1,6 +1,20 @@ - + + @@ -23,6 +37,6 @@ + - - \ No newline at end of file + diff --git a/bluetoothle/src/main/java/com/sample/android/bluetoothle/java/MainActivity.java b/bluetoothle/src/main/java/com/sample/android/bluetoothle/java/MainActivity.java index b296ce70b..5e25d58e6 100644 --- a/bluetoothle/src/main/java/com/sample/android/bluetoothle/java/MainActivity.java +++ b/bluetoothle/src/main/java/com/sample/android/bluetoothle/java/MainActivity.java @@ -1,13 +1,15 @@ package com.sample.android.bluetoothle.java; -import androidx.appcompat.app.AppCompatActivity; - +import android.Manifest; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import androidx.annotation.RequiresPermission; +import androidx.appcompat.app.AppCompatActivity; + import com.sample.android.bluetoothle.R; public class MainActivity extends AppCompatActivity { @@ -19,6 +21,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); } + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) private void setUpBLE() { // [START get_bluetooth_adapter] // Initializes Bluetooth adapter. diff --git a/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/DeviceScanActivity.kt b/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/DeviceScanActivity.kt index 8ed32d5fa..585a9e3f5 100644 --- a/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/DeviceScanActivity.kt +++ b/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/DeviceScanActivity.kt @@ -16,11 +16,13 @@ package com.sample.android.bluetoothle.kotlin +import android.Manifest import android.app.ListActivity import android.bluetooth.BluetoothAdapter import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanResult import android.os.Handler +import androidx.annotation.RequiresPermission import com.sample.android.bluetoothle.java.LeDeviceListAdapter /** @@ -47,6 +49,7 @@ class DeviceScanActivity : ListActivity() { // Stops scanning after 10 seconds. private val SCAN_PERIOD: Long = 10000 + @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN) private fun scanLeDevice() { if (!mScanning) { // Stops scanning after a pre-defined scan period. handler.postDelayed({ diff --git a/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/MainActivity.kt b/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/MainActivity.kt index a8857b4aa..95fe7df91 100644 --- a/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/MainActivity.kt +++ b/bluetoothle/src/main/java/com/sample/android/bluetoothle/kotlin/MainActivity.kt @@ -16,11 +16,13 @@ package com.sample.android.bluetoothle.kotlin +import android.Manifest import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.annotation.RequiresPermission import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { @@ -31,6 +33,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) } + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) private fun setUpBLE() { // [START get_bluetooth_adapter] // Initializes Bluetooth adapter. diff --git a/bluetoothle/src/main/res/layout/activity_main.xml b/bluetoothle/src/main/res/layout/activity_main.xml index b692bf4a3..b46563beb 100644 --- a/bluetoothle/src/main/res/layout/activity_main.xml +++ b/bluetoothle/src/main/res/layout/activity_main.xml @@ -1,4 +1,19 @@ + - \ No newline at end of file + diff --git a/bluetoothle/src/main/res/values/strings.xml b/bluetoothle/src/main/res/values/strings.xml index 7abc06d3b..fa7796f20 100644 --- a/bluetoothle/src/main/res/values/strings.xml +++ b/bluetoothle/src/main/res/values/strings.xml @@ -1 +1,17 @@ + + diff --git a/build.gradle.kts b/build.gradle.kts index 6b4ec9ad0..d4ea18313 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,96 @@ plugins { alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.android.kotlin.multiplatform.library) apply false + alias(libs.plugins.android.lint) apply false + alias(libs.plugins.spotless) apply false +} + +allprojects { + apply(plugin = "com.diffplug.spotless") + extensions.configure { + kotlin { + target("**/*.kt") + targetExclude("**/build/**/*.kt", "spotless/**/*.kt") + + val disabledRules = arrayOf( + // These rules were introduced in ktlint 0.46.0 and should not be + // enabled without further discussion. They are disabled for now. + // See: https://github.com/pinterest/ktlint/releases/tag/0.46.0 + "filename", + "annotation", + "annotation-spacing", + "argument-list-wrapping", + "double-colon-spacing", + "enum-entry-name-case", + "multiline-if-else", + "no-empty-first-line-in-method-block", + "package-name", + "trailing-comma", + "spacing-around-angle-brackets", + "spacing-between-declarations-with-annotations", + "spacing-between-declarations-with-comments", + "unary-op-spacing", + "no-trailing-spaces", + "max-line-length", + // Disabled rules that were introduced or changed between 0.46.0 ~ 1.50.0 + "class-signature", + "trailing-comma-on-call-site", + "trailing-comma-on-declaration-site", + "comment-wrapping", + "function-literal", + "function-signature", + "function-expression-body", + "function-start-of-body-spacing", + "multiline-expression-wrapping", + ) + + ktlint(libs.versions.ktlint.get()).editorConfigOverride( + mapOf( + "android" to "true", + "ktlint_code_style" to "android_studio", + "ij_kotlin_allow_trailing_comma" to "true", + ) + disabledRules.map { Pair("ktlint_standard_$it", "disabled") } + ) + + // ktlint 7.0.0 introduces lints, which existing snippets do not satisfy + val kotlinSuppressLints = arrayOf( + "standard:function-naming", + "standard:property-naming", + "standard:class-naming", + "standard:max-line-length", + "standard:comment-wrapping", + "standard:import-ordering", + "standard:filename", + "standard:backing-property-naming", + ) + for (lint in kotlinSuppressLints) { + suppressLintsFor { + step = "ktlint" + shortCode = lint + } + } + + licenseHeaderFile(rootProject.file("spotless/copyright.kt")) + } + kotlinGradle { + target("**/*.kts") + targetExclude("**/build/**/*.kts", "spotless/**/*.kts") + // Look for the first line that doesn't have a block comment (assumed to be the license) + licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)") + } + format("xml") { + target("**/*.xml") + targetExclude( + "**/build/**/*.xml", + "spotless/**/*.xml", + ".idea/**", + ) + // Look for the root tag or a tag that is a snippet + licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[a-zA-Z])|( + Copyright 2018 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. +--> RecomposeHighlighter diff --git a/compose/recomposehighlighter/src/main/res/values/themes.xml b/compose/recomposehighlighter/src/main/res/values/themes.xml index 918929c1b..f94279433 100644 --- a/compose/recomposehighlighter/src/main/res/values/themes.xml +++ b/compose/recomposehighlighter/src/main/res/values/themes.xml @@ -1,5 +1,21 @@ + + - \ No newline at end of file + diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index 8b644b694..7a5062f65 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -25,7 +25,9 @@ plugins { } android { - compileSdk = libs.versions.compileSdk.get().toInt() + compileSdk { + version = release(libs.versions.compileSdk.get().toInt()) + {minorApiLevel = 1}} // Android 16 QPR 2 namespace = "com.example.compose.snippets" defaultConfig { @@ -160,6 +162,9 @@ dependencies { debugImplementation(libs.androidx.compose.ui.tooling) androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.compose.ui.test.junit4.accessibility) debugImplementation(libs.androidx.compose.ui.test.manifest) + androidTestImplementation(libs.androidx.glance.testing) + androidTestImplementation(libs.androidx.glance.appwidget.testing) } diff --git a/compose/snippets/lint.xml b/compose/snippets/lint.xml index 778dd98f3..9ec5c3429 100644 --- a/compose/snippets/lint.xml +++ b/compose/snippets/lint.xml @@ -1,6 +1,21 @@ - + + - \ No newline at end of file + diff --git a/compose/snippets/src/androidTest/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt b/compose/snippets/src/androidTest/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt index 724876a87..ee6b51325 100644 --- a/compose/snippets/src/androidTest/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt +++ b/compose/snippets/src/androidTest/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt @@ -16,25 +16,109 @@ package com.example.compose.snippets.accessibility +import androidx.activity.ComponentActivity +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.SemanticsActions +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.getOrNull +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assert +import androidx.compose.ui.test.junit4.accessibility.enableAccessibilityChecks import androidx.compose.ui.test.junit4.createAndroidComposeRule -import com.example.compose.snippets.MyActivity +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.tryPerformAccessibilityChecks +import androidx.compose.ui.unit.dp +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult +import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator import org.junit.Ignore import org.junit.Rule import org.junit.Test -class AccessibilitySnippetsTest { +class AccessibilityTest { + +// [START android_compose_accessibility_testing_label] @Rule @JvmField - val composeTestRule = createAndroidComposeRule() + val composeTestRule = createAndroidComposeRule() + + @Test + fun noAccessibilityLabel() { + composeTestRule.setContent { + Box( + modifier = Modifier + .size(50.dp, 50.dp) + .background(color = Color.Gray) + .clickable { } + .semantics { + contentDescription = "" + } + ) + } - private val nodeMatcher = SemanticsMatcher("DUMMY") { it.isRoot } + composeTestRule.enableAccessibilityChecks() - @Ignore("Dummy test") + // Any action (such as performClick) will perform accessibility checks too: + composeTestRule.onRoot().tryPerformAccessibilityChecks() + } +// [END android_compose_accessibility_testing_label] +// [START android_compose_accessibility_testing_click] + @Test + fun smallClickTarget() { + composeTestRule.setContent { + Box( + modifier = Modifier + .size(20.dp, 20.dp) + .background(color = Color(0xFFFAFBFC)) + .clickable { } + ) + } + + composeTestRule.enableAccessibilityChecks() + + // Any action (such as performClick) will perform accessibility checks too: + composeTestRule.onRoot().tryPerformAccessibilityChecks() + } +// [END android_compose_accessibility_testing_click] + +// [START android_compose_accessibility_testing_validator] + @Test + fun lowContrastScreen() { + composeTestRule.setContent { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color(0xFFFAFBFC)), + contentAlignment = Alignment.Center + ) { + Text(text = "Hello", color = Color(0xFFB0B1B2)) + } + } + + // Optionally, set AccessibilityValidator manually + val accessibilityValidator = AccessibilityValidator() + .setThrowExceptionFor( + AccessibilityCheckResult.AccessibilityCheckResultType.WARNING + ) + + composeTestRule.enableAccessibilityChecks(accessibilityValidator) + + composeTestRule.onRoot().tryPerformAccessibilityChecks() + } +// [END android_compose_accessibility_testing_validator] + + private val nodeMatcher = SemanticsMatcher(description = "DUMMY") { it.isRoot } + + @Ignore("Dummy test") // [START android_compose_accessibility_testing] @Test fun test() { diff --git a/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt b/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt new file mode 100644 index 000000000..05698d1a3 --- /dev/null +++ b/compose/snippets/src/androidTest/java/com/example/compose/snippets/glance/GlanceUnitTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.glance + +import androidx.compose.runtime.Composable +import androidx.glance.GlanceModifier +import androidx.glance.appwidget.testing.unit.runGlanceAppWidgetUnitTest +import androidx.glance.layout.Row +import androidx.glance.semantics.semantics +import androidx.glance.semantics.testTag +import androidx.glance.testing.unit.assertHasText +import androidx.glance.testing.unit.hasTestTag +import androidx.glance.text.Text +import org.junit.Test + +// [START android_compose_glance_unit_test] +private const val FAKE_HEADLINE = "EXTRA! EXTRA! READ ALL ABOUT IT!" + +class MyGlanceComposableTest { + @Test + fun myNewsItemComposable_largeSize_hasHeadline() = runGlanceAppWidgetUnitTest { + // Set the composable to test + provideComposable { + MyNewsItemComposable(FAKE_HEADLINE) + } + + // Perform assertions + onNode(hasTestTag("headline")) + .assertHasText(FAKE_HEADLINE) + } + + + @Composable + fun MyNewsItemComposable(headline: String) { + Row { + Text( + text = headline, + modifier = GlanceModifier.semantics { testTag = "headline" }, + ) + } + } +} +// [END android_compose_glance_unit_test] \ No newline at end of file diff --git a/compose/snippets/src/main/AndroidManifest.xml b/compose/snippets/src/main/AndroidManifest.xml index 4ec4d9b70..5c0f57b2e 100644 --- a/compose/snippets/src/main/AndroidManifest.xml +++ b/compose/snippets/src/main/AndroidManifest.xml @@ -1,20 +1,19 @@ + 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. +--> @@ -70,4 +69,4 @@ - \ No newline at end of file + diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt index 3c6be8afc..a83111f71 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/accessibility/AccessibilitySnippets.kt @@ -19,9 +19,11 @@ package com.example.compose.snippets.accessibility import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -34,6 +36,7 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Share import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Checkbox @@ -44,10 +47,13 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SwipeToDismissBox import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -58,16 +64,29 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.CollectionInfo +import androidx.compose.ui.semantics.CollectionItemInfo import androidx.compose.ui.semantics.CustomAccessibilityAction +import androidx.compose.ui.semantics.LiveRegionMode +import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.collectionInfo +import androidx.compose.ui.semantics.collectionItemInfo import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.isTraversalGroup +import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.paneTitle +import androidx.compose.ui.semantics.progressBarRangeInfo +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.semantics.traversalIndex +import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.compose.snippets.R @@ -155,7 +174,7 @@ private fun LargeBox() { // [START android_compose_accessibility_click_label] @Composable -private fun ArticleListItem(openArticle: () -> Unit) { +private fun ArticleListItem(openArticle: () -> Unit = {}) { Row( Modifier.clickable( // R.string.action_read_article = "read article" @@ -418,6 +437,376 @@ fun FloatingBox() { } // [END android_compose_accessibility_traversal_fab] +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun InteractiveElements( + openArticle: () -> Unit = {}, + addToBookmarks: () -> Unit = {}, +) { +// [START android_compose_accessibility_interactive_clickable] + Row( + // Uses `mergeDescendants = true` under the hood + modifier = Modifier.clickable { openArticle() } + ) { + Icon( + painter = painterResource(R.drawable.ic_logo), + contentDescription = "Open", + ) + Text("Accessibility in Compose") + } +// [END android_compose_accessibility_interactive_clickable] + +// [START android_compose_accessibility_interactive_click_label] + Row( + modifier = Modifier + .clickable(onClickLabel = "Open this article") { + openArticle() + } + ) { + Icon( + painter = painterResource(R.drawable.ic_logo), + contentDescription = "Open" + ) + Text("Accessibility in Compose") + } +// [END android_compose_accessibility_interactive_click_label] + +// [START android_compose_accessibility_interactive_long_click] + Row( + modifier = Modifier + .combinedClickable( + onLongClickLabel = "Bookmark this article", + onLongClick = { addToBookmarks() }, + onClickLabel = "Open this article", + onClick = { openArticle() }, + ) + ) {} +// [END android_compose_accessibility_interactive_long_click] +} + +// [START android_compose_accessibility_interactive_nested_click] +@Composable +private fun ArticleList(openArticle: () -> Unit) { + NestedArticleListItem( + // Clickable is set separately, in a nested layer: + onClickAction = openArticle, + // Semantics are set here: + modifier = Modifier.semantics { + onClick( + label = "Open this article", + action = { + // Not needed here: openArticle() + true + } + ) + } + ) +} +// [END android_compose_accessibility_interactive_nested_click] + +@Composable +private fun NestedArticleListItem( + onClickAction: () -> Unit, + modifier: Modifier = Modifier, +) { +} + +@Composable +private fun Semantics( + removeArticle: () -> Unit, + openArticle: () -> Unit, + addToBookmarks: () -> Unit, +) { + +// [START android_compose_accessibility_semantics_alert_polite] + PopupAlert( + message = "You have a new message", + modifier = Modifier.semantics { + liveRegion = LiveRegionMode.Polite + } + ) +// [END android_compose_accessibility_semantics_alert_polite] + +// [START android_compose_accessibility_semantics_alert_assertive] + PopupAlert( + message = "Emergency alert incoming", + modifier = Modifier.semantics { + liveRegion = LiveRegionMode.Assertive + } + ) +// [END android_compose_accessibility_semantics_alert_assertive] + + Box() { +// [START android_compose_accessibility_semantics_window] + ShareSheet( + message = "Choose how to share this photo", + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopCenter) + .semantics { paneTitle = "New bottom sheet" } + ) +// [END android_compose_accessibility_semantics_window] + } + +// [START android_compose_accessibility_semantics_error] + Error( + errorText = "Fields cannot be empty", + modifier = Modifier + .semantics { + error("Please add both email and password") + } + ) +// [END android_compose_accessibility_semantics_error] + + val progress by remember { mutableFloatStateOf(0F) } +// [START android_compose_accessibility_semantics_progress] + ProgressInfoBar( + modifier = Modifier + .semantics { + progressBarRangeInfo = + ProgressBarRangeInfo( + current = progress, + range = 0F..1F + ) + } + ) +// [END android_compose_accessibility_semantics_progress] + + val milkyWay = List(10) { it.toString() } +// [START android_compose_accessibility_semantics_long_list] + MilkyWayList( + modifier = Modifier + .semantics { + collectionInfo = CollectionInfo( + rowCount = milkyWay.count(), + columnCount = 1 + ) + } + ) { + milkyWay.forEachIndexed { index, text -> + Text( + text = text, + modifier = Modifier.semantics { + collectionItemInfo = + CollectionItemInfo(index, 0, 0, 0) + } + ) + } + } +// [END android_compose_accessibility_semantics_long_list] + +// [START android_compose_accessibility_semantics_custom_action_swipe] + SwipeToDismissBox( + modifier = Modifier.semantics { + // Represents the swipe to dismiss for accessibility + customActions = listOf( + CustomAccessibilityAction( + label = "Remove article from list", + action = { + removeArticle() + true + } + ) + ) + }, + state = rememberSwipeToDismissBoxState(), + backgroundContent = {} + ) { + ArticleListItem() + } +// [END android_compose_accessibility_semantics_custom_action_swipe] + +// [START android_compose_accessibility_semantics_custom_action_long_list] + ArticleListItemRow( + modifier = Modifier + .semantics { + customActions = listOf( + CustomAccessibilityAction( + label = "Open article", + action = { + openArticle() + true + } + ), + CustomAccessibilityAction( + label = "Add to bookmarks", + action = { + addToBookmarks() + true + } + ), + ) + } + ) { + Article( + modifier = Modifier.clearAndSetSemantics { }, + onClick = openArticle, + ) + BookmarkButton( + modifier = Modifier.clearAndSetSemantics { }, + onClick = addToBookmarks, + ) + } +// [END android_compose_accessibility_semantics_custom_action_long_list] +} + +@Composable +private fun PopupAlert( + message: String, + modifier: Modifier = Modifier, +) { +} + +@Composable +fun ShareSheet( + message: String, + modifier: Modifier = Modifier, +) { +} + +@Composable +private fun Error( + errorText: String, + modifier: Modifier = Modifier, +) { +} + +@Composable +private fun ProgressInfoBar( + modifier: Modifier = Modifier, +) { +} + +@Composable +private fun MilkyWayList( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + content() +} + +@Composable +private fun ArticleListItemRow( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + content() +} + +@Composable +fun Article( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { +} + +@Composable +fun BookmarkButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { +} + +// [START android_compose_accessibility_merging] +@Composable +private fun ArticleListItem( + openArticle: () -> Unit, + addToBookmarks: () -> Unit, +) { + + Row(modifier = Modifier.clickable { openArticle() }) { + // Merges with parent clickable: + Icon( + painter = painterResource(R.drawable.ic_logo), + contentDescription = "Article thumbnail" + ) + ArticleDetails() + + // Defies the merge due to its own clickable: + BookmarkButton(onClick = addToBookmarks) + } +} +// [END android_compose_accessibility_merging] + +@Composable +fun ArticleDetails( + modifier: Modifier = Modifier, +) { +} + +// [START android_compose_accessibility_clearing] +// Developer might intend this to be a toggleable. +// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied, +// a custom description is set, and a Role is applied. + +@Composable +fun FavoriteToggle() { + val checked = remember { mutableStateOf(true) } + Row( + modifier = Modifier + .toggleable( + value = checked.value, + onValueChange = { checked.value = it } + ) + .clearAndSetSemantics { + stateDescription = if (checked.value) "Favorited" else "Not favorited" + toggleableState = ToggleableState(checked.value) + role = Role.Switch + }, + ) { + Icon( + imageVector = Icons.Default.Favorite, + contentDescription = null // not needed here + + ) + Text("Favorite?") + } +} +// [END android_compose_accessibility_clearing] + +// [START android_compose_accessibility_hiding] +@Composable +fun WatermarkExample( + watermarkText: String, + content: @Composable () -> Unit, +) { + Box { + WatermarkedContent() + // Mark the watermark as hidden to accessibility services. + WatermarkText( + text = watermarkText, + color = Color.Gray.copy(alpha = 0.5f), + modifier = Modifier + .align(Alignment.BottomEnd) + .semantics { hideFromAccessibility() } + ) + } +} + +@Composable +fun DecorativeExample() { + Text( + modifier = + Modifier.semantics { + hideFromAccessibility() + }, + text = "A dot character that is used to decoratively separate information, like •" + ) +} +// [END android_compose_accessibility_hiding] + +@Composable +private fun WatermarkedContent() { +} + +@Composable +private fun WatermarkText( + text: String, + color: Color, + modifier: Modifier = Modifier, +) { +} + private object ColumnWithFab { // [START android_compose_accessibility_traversal_fab_scaffold] @OptIn(ExperimentalMaterial3Api::class) 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 index 76006e684..6221902cf 100644 --- 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 @@ -17,6 +17,7 @@ package com.example.compose.snippets.adaptivelayouts import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBox import androidx.compose.material.icons.filled.Favorite @@ -28,6 +29,7 @@ import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItem import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType @@ -39,7 +41,7 @@ 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 androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND import com.example.compose.snippets.R // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_destinations] @@ -154,12 +156,40 @@ fun SampleNavigationSuiteScaffoldColors() { // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_item_colors] } +@Composable +fun SampleNavigationSuiteScaffoldIconsAlignment() { + var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) } + + // [START android_compose_adaptivelayouts_sample_navigation_suite_scaffold_icons_alignment] + NavigationSuiteScaffold( + navigationItems = { + AppDestinations.entries.forEach { + NavigationSuiteItem( + icon = { + Icon( + it.icon, + contentDescription = stringResource(it.contentDescription) + ) + }, + label = { Text(stringResource(it.label)) }, + selected = it == currentDestination, + onClick = { currentDestination = it }, + ) + } + }, + navigationItemVerticalArrangement = Arrangement.Center + ) { + // TODO: Destination content. + } + // [END android_compose_adaptivelayouts_sample_navigation_suite_scaffold_icons_alignment] +} + @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) { + if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND)) { NavigationSuiteType.NavigationDrawer } else { NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) 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 index 7132c5e7f..87b6c62b7 100644 --- 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 @@ -17,9 +17,15 @@ package com.example.compose.snippets.adaptivelayouts import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.layout.AnimatedPane @@ -30,13 +36,17 @@ import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldPaneScope import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior import androidx.compose.material3.adaptive.navigation.NavigableSupportingPaneScaffold +import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldPredictiveBackHandler import androidx.compose.material3.adaptive.navigation.rememberSupportingPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Devices.TABLET import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3AdaptiveApi::class) @@ -57,6 +67,12 @@ fun SampleNavigableSupportingPaneScaffoldParts() { // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_params] } +@Composable +@Preview(device = TABLET) +fun SampleNavigationSupportingPaneScaffoldFullTabletPreview() { + SampleNavigableSupportingPaneScaffoldFull() +} + @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable @Preview @@ -64,6 +80,7 @@ fun SampleNavigableSupportingPaneScaffoldFull() { // [START android_compose_adaptivelayouts_sample_supporting_pane_scaffold_full] val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator() val scope = rememberCoroutineScope() + val backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange NavigableSupportingPaneScaffold( navigator = scaffoldNavigator, @@ -92,7 +109,26 @@ fun SampleNavigableSupportingPaneScaffoldFull() { }, supportingPane = { AnimatedPane(modifier = Modifier.safeContentPadding()) { - Text("Supporting pane") + Column { + // Allow users to dismiss the supporting pane. Use back navigation to + // hide an expanded supporting pane. + if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) { + // Material design principles promote the usage of a right-aligned + // close (X) button. + IconButton( + modifier = Modifier.align(Alignment.End).padding(16.dp), + onClick = { + scope.launch { + scaffoldNavigator.navigateBack(backNavigationBehavior) + } + } + ) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + Text("Supporting pane") + } + } } ) @@ -124,11 +160,32 @@ fun ThreePaneScaffoldPaneScope.MainPane( @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun ThreePaneScaffoldPaneScope.SupportingPane( + scaffoldNavigator: ThreePaneScaffoldNavigator, modifier: Modifier = Modifier, + backNavigationBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange, ) { - AnimatedPane(modifier = modifier.safeContentPadding()) { - // Supporting pane content - Text("This is the supporting pane") + val scope = rememberCoroutineScope() + AnimatedPane(modifier = Modifier.safeContentPadding()) { + Column { + // Allow users to dismiss the supporting pane. Use back navigation to + // hide an expanded supporting pane. + if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) { + // Material design principles promote the usage of a right-aligned + // close (X) button. + IconButton( + modifier = modifier.align(Alignment.End).padding(16.dp), + onClick = { + scope.launch { + scaffoldNavigator.navigateBack(backNavigationBehavior) + } + } + ) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + Text("Supporting pane") + } + } } // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_extracted_panes] @@ -152,7 +209,7 @@ fun SampleNavigableSupportingPaneScaffoldSimplified() { } ) }, - supportingPane = { SupportingPane() }, + supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) }, ) // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_simplified] } @@ -182,7 +239,7 @@ fun SampleSupportingPaneScaffoldSimplifiedWithPredictiveBackHandler() { } ) }, - supportingPane = { SupportingPane() }, + supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) }, ) // [END android_compose_adaptivelayouts_sample_supporting_pane_scaffold_simplified_with_predictive_back_handler] } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/AnimationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/AnimationSnippets.kt index 2fe06cf06..bdb82790d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/AnimationSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/AnimationSnippets.kt @@ -280,14 +280,15 @@ private fun AnimateAsStateSimple() { // [START android_compose_animations_animate_as_state] var enabled by remember { mutableStateOf(true) } - val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") + val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() - .graphicsLayer(alpha = alpha) + .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) ) // [END android_compose_animations_animate_as_state] + { Button(onClick = { enabled = !enabled }) { Text("Animate me!") } } } @Preview diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementBlurSnippet.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementBlurSnippet.kt index 92bee9098..e07cbec6f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementBlurSnippet.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementBlurSnippet.kt @@ -157,7 +157,7 @@ fun SharedTransitionScope.SnackItem( SnackContents( snack = snack, modifier = Modifier.sharedElement( - state = rememberSharedContentState(key = snack.name), + sharedContentState = rememberSharedContentState(key = snack.name), animatedVisibilityScope = this@AnimatedVisibility, boundsTransform = boundsTransition, ), diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementSnippets.kt index 955dbe460..5a51b8d56 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementSnippets.kt @@ -111,7 +111,7 @@ private fun AnimatedVisibilitySharedElementShortenedExample() { SnackContents( snack = snack, modifier = Modifier.sharedElement( - state = rememberSharedContentState(key = snack.name), + sharedContentState = rememberSharedContentState(key = snack.name), animatedVisibilityScope = this@AnimatedVisibility ), onClick = { @@ -175,7 +175,7 @@ fun SharedTransitionScope.SnackEditDetails( SnackContents( snack = targetSnack, modifier = Modifier.sharedElement( - state = rememberSharedContentState(key = targetSnack.name), + sharedContentState = rememberSharedContentState(key = targetSnack.name), animatedVisibilityScope = this@AnimatedContent, ), onClick = { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt index d599e1586..079c68347 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt @@ -659,7 +659,7 @@ fun CustomPredictiveBackHandle() { // For each backEvent that comes in, we manually seekTo the reported back progress try { seekableTransitionState.seekTo(backEvent.progress, targetState = Screen.Home) - } catch (e: CancellationException) { + } catch (_: CancellationException) { // seekTo may be cancelled as expected, if animateTo or subsequent seekTo calls // before the current seekTo finishes, in this case, we ignore the cancellation. } @@ -671,6 +671,7 @@ fun CustomPredictiveBackHandle() { // When the predictive back gesture is cancelled, we snap to the end state to ensure // it completes its seeking animation back to the currentState seekableTransitionState.snapTo(seekableTransitionState.currentState) + throw e } } val coroutineScope = rememberCoroutineScope() 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 354efe635..72b4586a0 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 @@ -218,7 +218,7 @@ fun CenterAlignedTopAppBarExample() { topBar = { CenterAlignedTopAppBar( - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, titleContentColor = MaterialTheme.colorScheme.primary, ), @@ -431,6 +431,7 @@ fun AppBarSelectionActions( } } }, + modifier = modifier ) } // [END android_compose_components_appbarselectionactions] @@ -454,6 +455,7 @@ private fun AppBarMultiSelectionExample( var selectedItems by rememberSaveable { mutableStateOf(setOf()) } Scaffold( + modifier = modifier, topBar = { AppBarSelectionActions(selectedItems) } ) { innerPadding -> LazyColumn(contentPadding = innerPadding) { @@ -517,7 +519,8 @@ fun LazyListMultiSelection( ) { var selectedItems by rememberSaveable { mutableStateOf(setOf()) } - LazyColumn(contentPadding = contentPadding) { + LazyColumn(modifier = modifier, + contentPadding = contentPadding) { itemsIndexed(listItems) { _, index -> val selected = selectedItems.contains(index) ListItemSelectable( 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 index f2f83b01c..895c52def 100644 --- 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 @@ -30,7 +30,7 @@ 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.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -78,7 +78,7 @@ fun BadgeExample() { // [START android_compose_components_badgeinteractive] @Composable fun BadgeInteractiveExample() { - var itemCount by remember { mutableStateOf(0) } + var itemCount by remember { mutableIntStateOf(0) } Column( verticalArrangement = Arrangement.spacedBy(16.dp) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt index 692199123..4d59ea4ef 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt @@ -57,7 +57,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup -import com.example.compose.snippets.touchinput.userinteractions.MyAppTheme +import com.example.compose.snippets.ui.theme.SnippetsTheme import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -65,7 +65,7 @@ import java.util.Locale @Preview @Composable private fun DatePickerPreview() { - MyAppTheme { + SnippetsTheme { DatePickerExamples() } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt new file mode 100644 index 000000000..59727c71b --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.components + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.example.compose.snippets.R +import kotlinx.coroutines.delay + +// [START android_compose_components_togglebuttonexample] +@Preview +@Composable +fun ToggleIconButtonExample() { + // isToggled initial value should be read from a view model or persistent storage. + var isToggled by rememberSaveable { mutableStateOf(false) } + + IconButton( + onClick = { isToggled = !isToggled } + ) { + Icon( + painter = if (isToggled) painterResource(R.drawable.favorite_filled) else painterResource(R.drawable.favorite), + contentDescription = if (isToggled) "Selected icon button" else "Unselected icon button." + ) + } +} +// [END android_compose_components_togglebuttonexample] + +// [START android_compose_components_iconbutton] +@Composable +fun MomentaryIconButton( + unselectedImage: Int, + selectedImage: Int, + contentDescription: String, + modifier: Modifier = Modifier, + stepDelay: Long = 100L, // Minimum value is 1L milliseconds. + onClick: () -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val pressedListener by rememberUpdatedState(onClick) + + LaunchedEffect(isPressed) { + while (isPressed) { + delay(stepDelay.coerceIn(1L, Long.MAX_VALUE)) + pressedListener() + } + } + + IconButton( + modifier = modifier, + onClick = onClick, + interactionSource = interactionSource + ) { + Icon( + painter = if (isPressed) painterResource(id = selectedImage) else painterResource(id = unselectedImage), + contentDescription = contentDescription, + ) + } +} +// [END android_compose_components_iconbutton] + +// [START android_compose_components_momentaryiconbuttons] +@Preview() +@Composable +fun MomentaryIconButtonExample() { + var pressedCount by remember { mutableIntStateOf(0) } + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + MomentaryIconButton( + unselectedImage = R.drawable.fast_rewind, + selectedImage = R.drawable.fast_rewind_filled, + stepDelay = 100L, + onClick = { pressedCount -= 1 }, + contentDescription = "Decrease count button" + ) + Spacer(modifier = Modifier) + Text("advanced by $pressedCount frames") + Spacer(modifier = Modifier) + MomentaryIconButton( + unselectedImage = R.drawable.fast_forward, + selectedImage = R.drawable.fast_forward_filled, + contentDescription = "Increase count button", + stepDelay = 100L, + onClick = { pressedCount += 1 } + ) + } +} +// [END android_compose_components_momentaryiconbuttons] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt new file mode 100644 index 000000000..9cad68934 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt @@ -0,0 +1,214 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Album +import androidx.compose.material.icons.filled.MusicNote +import androidx.compose.material.icons.filled.PlaylistAddCircle +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationRail +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.PrimaryTabRow +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController + +@Composable +fun SongsScreen(modifier: Modifier = Modifier) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("Songs Screen") + } +} + +@Composable +fun AlbumScreen(modifier: Modifier = Modifier) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("Album Screen") + } +} + +@Composable +fun PlaylistScreen(modifier: Modifier = Modifier) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("Playlist Screen") + } +} + +enum class Destination( + val route: String, + val label: String, + val icon: ImageVector, + val contentDescription: String +) { + SONGS("songs", "Songs", Icons.Default.MusicNote, "Songs"), + ALBUM("album", "Album", Icons.Default.Album, "Album"), + PLAYLISTS("playlist", "Playlist", Icons.Default.PlaylistAddCircle, "Playlist") +} + +@Composable +fun AppNavHost( + navController: NavHostController, + startDestination: Destination, + modifier: Modifier = Modifier +) { + NavHost( + navController, + startDestination = startDestination.route + ) { + Destination.entries.forEach { destination -> + composable(destination.route) { + when (destination) { + Destination.SONGS -> SongsScreen() + Destination.ALBUM -> AlbumScreen() + Destination.PLAYLISTS -> PlaylistScreen() + } + } + } + } +} + +@Preview() +// [START android_compose_components_navigationbarexample] +@Composable +fun NavigationBarExample(modifier: Modifier = Modifier) { + val navController = rememberNavController() + val startDestination = Destination.SONGS + var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) } + + Scaffold( + modifier = modifier, + bottomBar = { + NavigationBar(windowInsets = NavigationBarDefaults.windowInsets) { + Destination.entries.forEachIndexed { index, destination -> + NavigationBarItem( + selected = selectedDestination == index, + onClick = { + navController.navigate(route = destination.route) + selectedDestination = index + }, + icon = { + Icon( + destination.icon, + contentDescription = destination.contentDescription + ) + }, + label = { Text(destination.label) } + ) + } + } + } + ) { contentPadding -> + AppNavHost(navController, startDestination, modifier = Modifier.padding(contentPadding)) + } +} +// [END android_compose_components_navigationbarexample] + +@Preview() +// [START android_compose_components_navigationrailexample] +@Composable +fun NavigationRailExample(modifier: Modifier = Modifier) { + val navController = rememberNavController() + val startDestination = Destination.SONGS + var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) } + + Scaffold(modifier = modifier) { contentPadding -> + NavigationRail(modifier = Modifier.padding(contentPadding)) { + Destination.entries.forEachIndexed { index, destination -> + NavigationRailItem( + selected = selectedDestination == index, + onClick = { + navController.navigate(route = destination.route) + selectedDestination = index + }, + icon = { + Icon( + destination.icon, + contentDescription = destination.contentDescription + ) + }, + label = { Text(destination.label) } + ) + } + } + AppNavHost(navController, startDestination) + } +} +// [END android_compose_components_navigationrailexample] + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +// [START android_compose_components_navigationtabexample] +@Composable +fun NavigationTabExample(modifier: Modifier = Modifier) { + val navController = rememberNavController() + val startDestination = Destination.SONGS + var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) } + + Scaffold(modifier = modifier) { contentPadding -> + PrimaryTabRow(selectedTabIndex = selectedDestination, modifier = Modifier.padding(contentPadding)) { + Destination.entries.forEachIndexed { index, destination -> + Tab( + selected = selectedDestination == index, + onClick = { + navController.navigate(route = destination.route) + selectedDestination = index + }, + text = { + Text( + text = destination.label, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + ) + } + } + AppNavHost(navController, startDestination) + } +} +// [END android_compose_components_navigationtabexample] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt index 4d5055457..be18829e2 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt @@ -29,6 +29,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -65,7 +66,7 @@ fun ProgressIndicatorExamples() { // [START android_compose_components_determinateindicator] @Composable fun LinearDeterminateIndicator() { - var currentProgress by remember { mutableStateOf(0f) } + var currentProgress by remember { mutableFloatStateOf(0f) } var loading by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() // Create a coroutine scope @@ -107,7 +108,7 @@ suspend fun loadProgress(updateProgress: (Float) -> Unit) { @Preview @Composable fun CircularDeterminateIndicator() { - var currentProgress by remember { mutableStateOf(0f) } + var currentProgress by remember { mutableFloatStateOf(0f) } var loading by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() // Create a coroutine scope diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt index 81280151a..5dbcb9221 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/PullToRefreshBox.kt @@ -32,11 +32,10 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.PositionalThreshold import androidx.compose.material3.pulltorefresh.PullToRefreshState -import androidx.compose.material3.pulltorefresh.pullToRefreshIndicator +import androidx.compose.material3.pulltorefresh.pullToRefresh import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -187,11 +186,13 @@ fun MyCustomIndicator( modifier: Modifier = Modifier, ) { Box( - modifier = modifier.pullToRefreshIndicator( + modifier = modifier.pullToRefresh( state = state, isRefreshing = isRefreshing, - containerColor = PullToRefreshDefaults.containerColor, - threshold = PositionalThreshold + threshold = PositionalThreshold, + onRefresh = { + + } ), contentAlignment = Alignment.Center ) { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt index f3243e299..d97f78524 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SearchBar.kt @@ -178,13 +178,13 @@ fun CustomizableSearchBar( onSearch: (String) -> Unit, searchResults: List, onResultClick: (String) -> Unit, + modifier: Modifier = Modifier, // Customization options placeholder: @Composable () -> Unit = { Text("Search") }, leadingIcon: @Composable (() -> Unit)? = { Icon(Icons.Default.Search, contentDescription = "Search") }, trailingIcon: @Composable (() -> Unit)? = null, supportingContent: (@Composable (String) -> Unit)? = null, leadingContent: (@Composable () -> Unit)? = null, - modifier: Modifier = Modifier ) { // Track expanded state of search bar var expanded by rememberSaveable { mutableStateOf(false) } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt index 183c19e16..22dc99303 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt @@ -16,14 +16,12 @@ package com.example.compose.snippets.components -import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons @@ -34,15 +32,17 @@ import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.OutlinedCard import androidx.compose.material3.SwipeToDismissBox -import androidx.compose.material3.SwipeToDismissBoxValue +import androidx.compose.material3.SwipeToDismissBoxValue.EndToStart +import androidx.compose.material3.SwipeToDismissBoxValue.Settled +import androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd import androidx.compose.material3.Text import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.lerp @@ -63,42 +63,31 @@ fun SwipeToDismissBoxExamples() { Text("Swipe to dismiss with change of background", fontWeight = FontWeight.Bold) SwipeItemExample() Text("Swipe to dismiss with a cross-fade animation", fontWeight = FontWeight.Bold) - SwipeCardItemExample() + SwipeItemWithAnimationExample() } } // [START android_compose_components_todoitem] data class TodoItem( - var isItemDone: Boolean, - var itemDescription: String + val itemDescription: String, + var isItemDone: Boolean = false ) // [END android_compose_components_todoitem] // [START android_compose_components_swipeitem] @Composable -fun SwipeItem( +fun TodoListItem( todoItem: TodoItem, - startToEndAction: (TodoItem) -> Unit, - endToStartAction: (TodoItem) -> Unit, + onToggleDone: (TodoItem) -> Unit, + onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, - content: @Composable (TodoItem) -> Unit ) { val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { - when (it) { - SwipeToDismissBoxValue.StartToEnd -> { - startToEndAction(todoItem) - // Do not dismiss this item. - false - } - SwipeToDismissBoxValue.EndToStart -> { - endToStartAction(todoItem) - true - } - SwipeToDismissBoxValue.Settled -> { - false - } - } + if (it == StartToEnd) onToggleDone(todoItem) + else if (it == EndToStart) onRemove(todoItem) + // Reset item when toggling done status + it != StartToEnd } ) @@ -106,59 +95,39 @@ fun SwipeItem( state = swipeToDismissBoxState, modifier = modifier.fillMaxSize(), backgroundContent = { - Row( - modifier = Modifier - .background( - when (swipeToDismissBoxState.dismissDirection) { - SwipeToDismissBoxValue.StartToEnd -> { - Color.Blue - } - SwipeToDismissBoxValue.EndToStart -> { - Color.Red - } - SwipeToDismissBoxValue.Settled -> { - Color.LightGray - } - } + when (swipeToDismissBoxState.dismissDirection) { + StartToEnd -> { + Icon( + if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank, + contentDescription = if (todoItem.isItemDone) "Done" else "Not done", + modifier = Modifier + .fillMaxSize() + .background(Color.Blue) + .wrapContentSize(Alignment.CenterStart) + .padding(12.dp), + tint = Color.White ) - .fillMaxSize(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - when (swipeToDismissBoxState.dismissDirection) { - SwipeToDismissBoxValue.StartToEnd -> { - val icon = if (todoItem.isItemDone) { - Icons.Default.CheckBox - } else { - Icons.Default.CheckBoxOutlineBlank - } - - val contentDescription = if (todoItem.isItemDone) "Done" else "Not done" - - Icon( - icon, - contentDescription, - Modifier.padding(12.dp), - tint = Color.White - ) - } - - SwipeToDismissBoxValue.EndToStart -> { - Spacer(modifier = Modifier) - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Remove item", - tint = Color.White, - modifier = Modifier.padding(12.dp) - ) - } - - SwipeToDismissBoxValue.Settled -> {} } + EndToStart -> { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove item", + modifier = Modifier + .fillMaxSize() + .background(Color.Red) + .wrapContentSize(Alignment.CenterEnd) + .padding(12.dp), + tint = Color.White + ) + } + Settled -> {} } } ) { - content(todoItem) + ListItem( + headlineContent = { Text(todoItem.itemDescription) }, + supportingContent = { Text("swipe me to update or remove.") } + ) } } // [END android_compose_components_swipeitem] @@ -169,10 +138,8 @@ fun SwipeItem( private fun SwipeItemExample() { val todoItems = remember { mutableStateListOf( - TodoItem(isItemDone = false, itemDescription = "Pay bills"), - TodoItem(isItemDone = false, itemDescription = "Buy groceries"), - TodoItem(isItemDone = false, itemDescription = "Go to gym"), - TodoItem(isItemDone = false, itemDescription = "Get dinner") + TodoItem("Pay bills"), TodoItem("Buy groceries"), + TodoItem("Go to gym"), TodoItem("Get dinner") ) } @@ -181,20 +148,16 @@ private fun SwipeItemExample() { items = todoItems, key = { it.itemDescription } ) { todoItem -> - SwipeItem( + TodoListItem( todoItem = todoItem, - startToEndAction = { + onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, - endToStartAction = { + onRemove = { todoItem -> todoItems -= todoItem - } - ) { - ListItem( - headlineContent = { Text(text = todoItem.itemDescription) }, - supportingContent = { Text(text = "swipe me to update or remove.") } - ) - } + }, + modifier = Modifier.animateItem() + ) } } } @@ -202,103 +165,74 @@ private fun SwipeItemExample() { // [START android_compose_components_swipecarditem] @Composable -fun SwipeCardItem( +fun TodoListItemWithAnimation( todoItem: TodoItem, - startToEndAction: (TodoItem) -> Unit, - endToStartAction: (TodoItem) -> Unit, + onToggleDone: (TodoItem) -> Unit, + onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, - content: @Composable (TodoItem) -> Unit ) { - // [START_EXCLUDE] - val swipeToDismissState = rememberSwipeToDismissBoxState( - positionalThreshold = { totalDistance -> totalDistance * 0.25f }, + val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { - when (it) { - SwipeToDismissBoxValue.StartToEnd -> { - startToEndAction(todoItem) - // Do not dismiss this item. - false - } - SwipeToDismissBoxValue.EndToStart -> { - endToStartAction(todoItem) - true - } - SwipeToDismissBoxValue.Settled -> { - false - } - } + if (it == StartToEnd) onToggleDone(todoItem) + else if (it == EndToStart) onRemove(todoItem) + // Reset item when toggling done status + it != StartToEnd } ) - // [END_EXCLUDE] SwipeToDismissBox( - modifier = Modifier, - state = swipeToDismissState, + state = swipeToDismissBoxState, + modifier = modifier.fillMaxSize(), backgroundContent = { - // Cross-fade the background color as the drag gesture progresses. - val color by animateColorAsState( - when (swipeToDismissState.targetValue) { - SwipeToDismissBoxValue.Settled -> Color.LightGray - SwipeToDismissBoxValue.StartToEnd -> - lerp(Color.LightGray, Color.Blue, swipeToDismissState.progress) - - SwipeToDismissBoxValue.EndToStart -> - lerp(Color.LightGray, Color.Red, swipeToDismissState.progress) - }, - label = "swipeable card item background color" - ) - // [START_EXCLUDE] - Row( - modifier = Modifier - .background(color) - .fillMaxSize(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - when (swipeToDismissState.dismissDirection) { - SwipeToDismissBoxValue.StartToEnd -> { - val icon = if (todoItem.isItemDone) { - Icons.Default.CheckBox - } else { - Icons.Default.CheckBoxOutlineBlank - } - - val contentDescription = if (todoItem.isItemDone) "Done" else "Not done" - - Icon(icon, contentDescription, Modifier.padding(12.dp), tint = Color.White) - } - - SwipeToDismissBoxValue.EndToStart -> { - Spacer(modifier = Modifier) - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Remove item", - tint = Color.White, - modifier = Modifier.padding(12.dp) - ) - } - - SwipeToDismissBoxValue.Settled -> {} + when (swipeToDismissBoxState.dismissDirection) { + StartToEnd -> { + Icon( + if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank, + contentDescription = if (todoItem.isItemDone) "Done" else "Not done", + modifier = Modifier + .fillMaxSize() + .drawBehind { + drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress)) + } + .wrapContentSize(Alignment.CenterStart) + .padding(12.dp), + tint = Color.White + ) } + EndToStart -> { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove item", + modifier = Modifier + .fillMaxSize() + .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress)) + .wrapContentSize(Alignment.CenterEnd) + .padding(12.dp), + tint = Color.White + ) + } + Settled -> {} } } ) { - content(todoItem) + OutlinedCard(shape = RectangleShape) { + ListItem( + headlineContent = { Text(todoItem.itemDescription) }, + supportingContent = { Text("swipe me to update or remove.") } + ) + } } - // [END_EXCLUDE] } // [END android_compose_components_swipecarditem] -// [START android_compose_components_swipecarditemexample] @Preview +// [START android_compose_components_swipecarditemexample] @Composable -private fun SwipeCardItemExample() { +private fun SwipeItemWithAnimationExample() { val todoItems = remember { mutableStateListOf( - TodoItem(isItemDone = false, itemDescription = "Pay bills"), - TodoItem(isItemDone = false, itemDescription = "Buy groceries"), - TodoItem(isItemDone = false, itemDescription = "Go to gym"), - TodoItem(isItemDone = false, itemDescription = "Get dinner") + TodoItem("Pay bills"), TodoItem("Buy groceries"), + TodoItem("Go to gym"), TodoItem("Get dinner") ) } @@ -307,22 +241,16 @@ private fun SwipeCardItemExample() { items = todoItems, key = { it.itemDescription } ) { todoItem -> - SwipeCardItem( + TodoListItemWithAnimation( todoItem = todoItem, - startToEndAction = { + onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, - endToStartAction = { + onRemove = { todoItem -> todoItems -= todoItem - } - ) { - OutlinedCard(shape = RectangleShape) { - ListItem( - headlineContent = { Text(todoItem.itemDescription) }, - supportingContent = { Text("swipe me to update or remove.") } - ) - } - } + }, + modifier = Modifier.animateItem() + ) } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt index 1066d0cfb..9a034e261 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Tooltips.kt @@ -166,7 +166,6 @@ fun AdvancedRichTooltipExample( } } }, - caretSize = DpSize(32.dp, 16.dp) ) { Text(richTooltipText) } 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 46e245a3f..12542079e 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 @@ -20,11 +20,11 @@ import android.content.ClipData import android.content.ClipDescription import android.os.Build import android.view.View +import androidx.activity.compose.LocalActivity 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 @@ -32,6 +32,7 @@ import androidx.compose.ui.draganddrop.DragAndDropEvent import androidx.compose.ui.draganddrop.DragAndDropTarget import androidx.compose.ui.draganddrop.DragAndDropTransferData import androidx.compose.ui.draganddrop.mimeTypes +import androidx.compose.ui.draganddrop.toAndroidDragEvent @RequiresApi(Build.VERSION_CODES.N) @OptIn(ExperimentalFoundationApi::class) @@ -40,40 +41,24 @@ private 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( - DragAndDropTransferData( - ClipData.newPlainText( - "image Url", url - ) - ) + Modifier.dragAndDropSource { _ -> + DragAndDropTransferData( + ClipData.newPlainText( + "image Url", url ) - }) + ) } // [END android_compose_drag_and_drop_2] // [START android_compose_drag_and_drop_3] - Modifier.dragAndDropSource { - detectTapGestures(onLongPress = { - startTransfer( - DragAndDropTransferData( - ClipData.newPlainText( - "image Url", url - ), - flags = View.DRAG_FLAG_GLOBAL - ) - ) - }) + Modifier.dragAndDropSource { _ -> + DragAndDropTransferData( + ClipData.newPlainText( + "image Url", url + ), + flags = View.DRAG_FLAG_GLOBAL + ) } // [END android_compose_drag_and_drop_3] @@ -88,11 +73,27 @@ private fun DragAndDropSnippet() { } // [END android_compose_drag_and_drop_4] + LocalActivity.current?.let { activity -> + // [START android_compose_drag_and_drop_7] + val externalAppCallback = remember { + object : DragAndDropTarget { + override fun onDrop(event: DragAndDropEvent): Boolean { + val permission = + activity.requestDragAndDropPermissions(event.toAndroidDragEvent()) + // Parse received data + permission?.release() + return true + } + } + } + // [END android_compose_drag_and_drop_7] + } + // [START android_compose_drag_and_drop_5] Modifier.dragAndDropTarget( shouldStartDragAndDrop = { event -> event.mimeTypes().contains(ClipDescription.MIMETYPE_TEXT_PLAIN) - }, target = callback + }, target = callback // or externalAppCallback ) // [END android_compose_drag_and_drop_5] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt new file mode 100644 index 000000000..3541cceb4 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlanceMetrics.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.glance + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi + +private const val TAG = "WidgetMetrics" + +class GlanceMetrics { + + // [START android_compose_glance_metrics] + @RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1) + fun getWidgetEngagementMetrics(context: Context) { + val manager = AppWidgetManager.getInstance(context) + + val endTime = System.currentTimeMillis() + val startTime = endTime - (24 * 60 * 60 * 1000) // a day ago + + val events = manager.queryAppWidgetEvents(startTime, endTime) + + if (events.isEmpty()) { + Log.d(TAG, "No events found for the given time range.") + } + + val metrics = hashMapOf( + "clicks" to 0L, + "scrolls" to 0L, + "totalImpressionLength" to 0L + ) + + for (event in events) { + + Log.d(TAG, "Event Start: ${event.start}") + Log.d(TAG, "Event End: ${event.end}") + + val widgetId = event.appWidgetId + + // Tap actions + val clickedIds = event.clickedIds + if (clickedIds?.isNotEmpty() == true) { + metrics["clicks"] = metrics.getValue("clicks") + clickedIds.size + // Log or analyze which components were clicked. + for (id in clickedIds) { + Log.d(TAG, "Widget $widgetId: Tap event on component with ID $id") + } + } + + // Scroll events + val scrolledIds = event.scrolledIds + if (scrolledIds?.isNotEmpty() == true) { + metrics["scrolls"] = metrics.getValue("scrolls") + scrolledIds.size + // Log or analyze which lists were scrolled. + for (id in scrolledIds) { + Log.d(TAG, "Widget $widgetId: Scroll event in list with ID/tag $id") + } + } + + // Impressions + metrics["totalImpressionLength"] = metrics.getValue("totalImpressionLength") + event.visibleDuration.toMillis() + Log.d( + TAG, + "Widget $widgetId: Impression event with duration " + event.visibleDuration.toMillis() + "ms" + ) + + // Position + val position = event.position + if (position != null) { + Log.d( + TAG, + "Widget $widgetId: left=${position.left}, right=${position.right}, top=${position.top}, bottom=${position.bottom}" + ) + } + } + Log.d("WidgetMetrics", "Metrics: $metrics") + } + // [END android_compose_glance_metrics] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlancePinAppWidget.kt b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlancePinAppWidget.kt new file mode 100644 index 000000000..428212068 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/glance/GlancePinAppWidget.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.glance + +import android.content.Context +import androidx.compose.material3.Button +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.glance.GlanceId +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import kotlinx.coroutines.launch + +class MyWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = MyWidget() +} + +class MyWidget : GlanceAppWidget() { + override suspend fun provideGlance( + context: Context, + id: GlanceId + ) {} +} + +// [START android_compose_glance_in_app_pinning] +@Composable +fun AnInAppComposable() { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + Button( + onClick = { + coroutineScope.launch { + GlanceAppWidgetManager(context).requestPinGlanceAppWidget( + receiver = MyWidgetReceiver::class.java, + preview = MyWidget(), + previewState = DpSize(245.dp, 115.dp) + ) + } + } + ) {} +} +// [END android_compose_glance_in_app_pinning] 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 bb575a8b5..df5efbd79 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 @@ -34,6 +34,7 @@ import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -225,7 +226,7 @@ private object ActionLaunchSendBroadcastEvent { private object ActionLambda { @Composable - fun actionLambda() { + fun ActionLambda() { // [START android_compose_glance_lambda01] Text( text = "Submit", @@ -237,7 +238,7 @@ private object ActionLambda { } @Composable - fun actionLambda2() { + fun ActionLambda2() { // [START android_compose_glance_lambda02] Button( text = "Submit", @@ -436,7 +437,7 @@ object ManageAndUpdate { object BuildUIWithGlance { @Composable - fun example1() { + fun Example1() { // [START android_compose_glance_buildUI01] Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() @@ -448,7 +449,7 @@ object BuildUIWithGlance { } @Composable - fun example2() { + fun Example2() { // [START android_compose_glance_buildUI02] // Remember to import Glance Composables @@ -466,7 +467,7 @@ object BuildUIWithGlance { } @Composable - fun example3() { + fun Example3() { // [START android_compose_glance_buildUI03] LazyColumn { item { @@ -480,7 +481,7 @@ object BuildUIWithGlance { } @Composable - fun example4() { + fun Example4() { val peopleNameList = arrayListOf() val peopleList = arrayListOf() @@ -631,7 +632,7 @@ object SizeModeSnippets3 { object AccessResources { @Composable - fun example1() { + fun Example1() { // [START android_compose_glance_buildUI10] LocalContext.current.getString(R.string.glance_title) // [END android_compose_glance_buildUI10] @@ -651,11 +652,11 @@ object AccessResources { object CompoundButton { @Composable - fun example1() { + fun Example1() { // [START android_compose_glance_buildUI12] var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } - var isRadioChecked by remember { mutableStateOf(0) } + var isRadioChecked by remember { mutableIntStateOf(0) } CheckBox( checked = isApplesChecked, @@ -860,7 +861,7 @@ object GlanceTheming { } @Composable - fun shapeExample() { + fun ShapeExample() { // Note : android_compose_glance_glancetheming04 is found in button_outline.xml // [START android_compose_glance_glancetheming05] GlanceModifier.background( @@ -898,7 +899,7 @@ object GlanceInnerPadding { object GlanceInteroperability { @Composable - fun example01() { + fun Example01() { // [START android_compose_glance_glanceinteroperability01] val packageName = LocalContext.current.packageName Column(modifier = GlanceModifier.fillMaxSize()) { @@ -909,7 +910,7 @@ object GlanceInteroperability { } @Composable - fun example02() { + fun Example02() { val packageName = null // [START android_compose_glance_glanceinteroperability02] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/Shadows.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/Shadows.kt new file mode 100644 index 000000000..8e2b208b5 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/Shadows.kt @@ -0,0 +1,616 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.graphics + +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.EaseInOut +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.draw.clip +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.draw.innerShadow +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.compose.snippets.ui.theme.SnippetsTheme +import kotlinx.coroutines.delay + +@Preview(showBackground = true) +// [START android_compose_graphics_simple_shadow] +@Composable +fun ElevationBasedShadow() { + Box( + modifier = Modifier.aspectRatio(1f).fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + Modifier + .size(100.dp, 100.dp) + .shadow(10.dp, RectangleShape) + .background(Color.White) + ) + } +} +// [END android_compose_graphics_simple_shadow] + +@Preview( + showBackground = true, + backgroundColor = 0xFFFFFFFF +) +// [START android_compose_graphics_simple_drop_shadow] +@Composable +fun SimpleDropShadowUsage() { + Box(Modifier.fillMaxSize()) { + Box( + Modifier + .width(300.dp) + .height(300.dp) + .dropShadow( + shape = RoundedCornerShape(20.dp), + shadow = Shadow( + radius = 10.dp, + spread = 6.dp, + color = Color(0x40000000), + offset = DpOffset(x = 4.dp, 4.dp) + ) + ) + .align(Alignment.Center) + .background( + color = Color.White, + shape = RoundedCornerShape(20.dp) + ) + ) { + Text( + "Drop Shadow", + modifier = Modifier.align(Alignment.Center), + fontSize = 32.sp + ) + } + } +} +// [END android_compose_graphics_simple_drop_shadow] + +@Preview( + showBackground = true, + backgroundColor = 0xFFFFFFFF +) +// [START android_compose_graphics_simple_inner_shadow] +@Composable +fun SimpleInnerShadowUsage() { + Box(Modifier.fillMaxSize()) { + Box( + Modifier + .width(300.dp) + .height(200.dp) + .align(Alignment.Center) + // note that the background needs to be defined before defining the inner shadow + .background( + color = Color.White, + shape = RoundedCornerShape(20.dp) + ) + .innerShadow( + shape = RoundedCornerShape(20.dp), + shadow = Shadow( + radius = 10.dp, + spread = 2.dp, + color = Color(0x40000000), + offset = DpOffset(x = 6.dp, 7.dp) + ) + ) + + ) { + Text( + "Inner Shadow", + modifier = Modifier.align(Alignment.Center), + fontSize = 32.sp + ) + } + } +} +// [END android_compose_graphics_simple_inner_shadow] + +@Preview( + showBackground = true, + backgroundColor = 0xFF232323 +) +// [START android_compose_graphics_realistic_shadow] +@Composable +fun RealisticShadows() { + Box(Modifier.fillMaxSize()) { + val dropShadowColor1 = Color(0xB3000000) + val dropShadowColor2 = Color(0x66000000) + + val innerShadowColor1 = Color(0xCC000000) + val innerShadowColor2 = Color(0xFF050505) + val innerShadowColor3 = Color(0x40FFFFFF) + val innerShadowColor4 = Color(0x1A050505) + Box( + Modifier + .width(300.dp) + .height(200.dp) + .align(Alignment.Center) + .dropShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 40.dp, + spread = 0.dp, + color = dropShadowColor1, + offset = DpOffset(x = 2.dp, 8.dp) + ) + ) + .dropShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 4.dp, + spread = 0.dp, + color = dropShadowColor2, + offset = DpOffset(x = 0.dp, 4.dp) + ) + ) + // note that the background needs to be defined before defining the inner shadow + .background( + color = Color.Black, + shape = RoundedCornerShape(100.dp) + ) +// // + .innerShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 12.dp, + spread = 3.dp, + color = innerShadowColor1, + offset = DpOffset(x = 6.dp, 6.dp) + ) + ) + .innerShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 4.dp, + spread = 1.dp, + color = Color.White, + offset = DpOffset(x = 5.dp, 5.dp) + ) + ) + .innerShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 12.dp, + spread = 5.dp, + color = innerShadowColor2, + offset = DpOffset(x = (-3).dp, (-12).dp) + ) + ) + .innerShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 3.dp, + spread = 10.dp, + color = innerShadowColor3, + offset = DpOffset(x = 0.dp, 0.dp) + ) + ) + .innerShadow( + shape = RoundedCornerShape(100.dp), + shadow = Shadow( + radius = 3.dp, + spread = 9.dp, + color = innerShadowColor4, + offset = DpOffset(x = 1.dp, 1.dp) + ) + ) + + ) { + Text( + "Realistic Shadows", + modifier = Modifier.align(Alignment.Center), + fontSize = 24.sp, + color = Color.White + ) + } + } +} +// [END android_compose_graphics_realistic_shadow] + +// Define breathing states +enum class BreathingState { + Inhaling, + Exhaling +} + +@Preview( + showBackground = true, + backgroundColor = 0xFFFFFFFF +) +@Composable +fun GradientBasedShadowAnimation() { + SnippetsTheme { + val colors = listOf( + Color(0xFF4cc9f0), + Color(0xFFf72585), + Color(0xFFb5179e), + Color(0xFF7209b7), + Color(0xFF560bad), + Color(0xFF480ca8), + Color(0xFF3a0ca3), + Color(0xFF3f37c9), + Color(0xFF4361ee), + Color(0xFF4895ef), + Color(0xFF4cc9f0) + ) + + // .. + + // State for the breathing animation + var breathingState by remember { mutableStateOf(BreathingState.Inhaling) } + + // Create transition based on breathing state + val transition = updateTransition( + targetState = breathingState, + label = "breathing_transition" + ) + + // Animate spread based on breathing state + val animatedSpread by transition.animateFloat( + transitionSpec = { + tween( + durationMillis = 5000, + easing = FastOutSlowInEasing + ) + }, + label = "spread_animation" + ) { state -> + when (state) { + BreathingState.Inhaling -> 10f + BreathingState.Exhaling -> 2f + } + } + + // Animate alpha based on breathing state (optional) + val animatedAlpha by transition.animateFloat( + transitionSpec = { + tween( + durationMillis = 2000, + easing = FastOutSlowInEasing + ) + }, + label = "alpha_animation" + ) { state -> + when (state) { + BreathingState.Inhaling -> 1f + BreathingState.Exhaling -> 1f + } + } + + // Get text based on current state + val breathingText = when (breathingState) { + BreathingState.Inhaling -> "Inhale" + BreathingState.Exhaling -> "Exhale" + } + + // Switch states when animation completes + LaunchedEffect(breathingState) { + delay(5000) // Wait for animation to complete + breathingState = when (breathingState) { + BreathingState.Inhaling -> BreathingState.Exhaling + BreathingState.Exhaling -> BreathingState.Inhaling + } + } + + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + // [START android_compose_graphics_gradient_shadow] + Box( + modifier = Modifier + .width(240.dp) + .height(200.dp) + .dropShadow( + shape = RoundedCornerShape(70.dp), + shadow = Shadow( + radius = 10.dp, + spread = animatedSpread.dp, + brush = Brush.sweepGradient( + colors + ), + offset = DpOffset(x = 0.dp, y = 0.dp), + alpha = animatedAlpha + ) + ) + .clip(RoundedCornerShape(70.dp)) + .background(Color(0xEDFFFFFF)), + contentAlignment = Alignment.Center + ) { + Text( + text = breathingText, + color = Color.Black, + style = MaterialTheme.typography.bodyLarge + ) + } + // [END android_compose_graphics_gradient_shadow] + } + } +} + +@Preview +// [START android_compose_graphics_neumorphic_shadow] +@Composable +fun NeumorphicRaisedButton( + shape: RoundedCornerShape = RoundedCornerShape(30.dp) +) { + val bgColor = Color(0xFFe0e0e0) + val lightShadow = Color(0xFFFFFFFF) + val darkShadow = Color(0xFFb1b1b1) + val upperOffset = -10.dp + val lowerOffset = 10.dp + val radius = 15.dp + val spread = 0.dp + Box( + modifier = Modifier + .fillMaxSize() + .background(bgColor) + .wrapContentSize(Alignment.Center) + .size(240.dp) + .dropShadow( + shape, + shadow = Shadow( + radius = radius, + color = lightShadow, + spread = spread, + offset = DpOffset(upperOffset, upperOffset) + ), + ) + .dropShadow( + shape, + shadow = Shadow( + radius = radius, + color = darkShadow, + spread = spread, + offset = DpOffset(lowerOffset, lowerOffset) + ), + + ) + .background(bgColor, shape) + ) +} +// [END android_compose_graphics_neumorphic_shadow] + +@Preview( + showBackground = true, + backgroundColor = 0xFFFFFFFF +) +// [START android_compose_graphics_animated_shadow] +@Composable +fun AnimatedColoredShadows() { + SnippetsTheme { + Box(Modifier.fillMaxSize()) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + // Create transition with pressed state + val transition = updateTransition( + targetState = isPressed, + label = "button_press_transition" + ) + + fun buttonPressAnimation() = tween( + durationMillis = 400, + easing = EaseInOut + ) + + // Animate all properties using the transition + val shadowAlpha by transition.animateFloat( + label = "shadow_alpha", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) 0f else 1f + } + // [START_EXCLUDE] + val innerShadowAlpha by transition.animateFloat( + label = "shadow_alpha", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) 1f else 0f + } + + val blueDropShadowColor = Color(0x5C007AFF) + + val darkBlueDropShadowColor = Color(0x66007AFF) + + val greyInnerShadowColor1 = Color(0x1A007AFF) + + val greyInnerShadowColor2 = Color(0x1A007AFF) + // [END_EXCLUDE] + + val blueDropShadow by transition.animateColor( + label = "shadow_color", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) Color.Transparent else blueDropShadowColor + } + + // [START_EXCLUDE] + val darkBlueDropShadow by transition.animateColor( + label = "shadow_color", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) Color.Transparent else darkBlueDropShadowColor + } + + val innerShadowColor1 by transition.animateColor( + label = "shadow_color", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) greyInnerShadowColor1 + else greyInnerShadowColor2 + } + + val innerShadowColor2 by transition.animateColor( + label = "shadow_color", + transitionSpec = { buttonPressAnimation() } + ) { pressed -> + if (pressed) Color(0x4D007AFF) + else Color(0x1A007AFF) + } + // [END_EXCLUDE] + + Box( + Modifier + .clickable( + interactionSource, indication = null + ) { + // ** ...... **// + } + .width(300.dp) + .height(200.dp) + .align(Alignment.Center) + .dropShadow( + shape = RoundedCornerShape(70.dp), + shadow = Shadow( + radius = 10.dp, + spread = 0.dp, + color = blueDropShadow, + offset = DpOffset(x = 0.dp, -(2).dp), + alpha = shadowAlpha + ) + ) + .dropShadow( + shape = RoundedCornerShape(70.dp), + shadow = Shadow( + radius = 10.dp, + spread = 0.dp, + color = darkBlueDropShadow, + offset = DpOffset(x = 2.dp, 6.dp), + alpha = shadowAlpha + ) + ) + // note that the background needs to be defined before defining the inner shadow + .background( + color = Color(0xFFFFFFFF), + shape = RoundedCornerShape(70.dp) + ) + .innerShadow( + shape = RoundedCornerShape(70.dp), + shadow = Shadow( + radius = 8.dp, + spread = 4.dp, + color = innerShadowColor2, + offset = DpOffset(x = 4.dp, 0.dp) + ) + ) + .innerShadow( + shape = RoundedCornerShape(70.dp), + shadow = Shadow( + radius = 20.dp, + spread = 4.dp, + color = innerShadowColor1, + offset = DpOffset(x = 4.dp, 0.dp), + alpha = innerShadowAlpha + ) + ) + + ) { + Text( + "Animated Shadows", + // [START_EXCLUDE] + modifier = Modifier + .align(Alignment.Center), + style = MaterialTheme.typography.bodyLarge, + fontSize = 24.sp, + color = Color.Black + // [END_EXCLUDE] + ) + } + } + } +} +// [END android_compose_graphics_animated_shadow] + +@Preview( + showBackground = true, + backgroundColor = 0xFFFFCC00 +) +// [START android_compose_graphics_neobrutal_shadow] +@Composable +fun NeoBrutalShadows() { + SnippetsTheme { + val dropShadowColor = Color(0xFF007AFF) + val borderColor = Color(0xFFFF2D55) + Box(Modifier.fillMaxSize()) { + Box( + Modifier + .width(300.dp) + .height(200.dp) + .align(Alignment.Center) + .dropShadow( + shape = RoundedCornerShape(0.dp), + shadow = Shadow( + radius = 0.dp, + spread = 0.dp, + color = dropShadowColor, + offset = DpOffset(x = 8.dp, 8.dp) + ) + ) + .border( + 8.dp, borderColor + ) + .background( + color = Color.White, + shape = RoundedCornerShape(0.dp) + ) + ) { + Text( + "Neobrutal Shadows", + modifier = Modifier.align(Alignment.Center), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} +// [END android_compose_graphics_neobrutal_shadow] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/interop/InteroperabilityAPIsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/interop/InteroperabilityAPIsSnippets.kt index bfafc95df..efad40fd7 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/interop/InteroperabilityAPIsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/interop/InteroperabilityAPIsSnippets.kt @@ -32,13 +32,14 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue @@ -191,10 +192,32 @@ class ExampleFragmentMultipleComposeView : Fragment() { } // [END android_compose_interop_apis_compose_in_fragment_multiple] +// [START android_compose_interop_apis_android_view_reuse] +@Composable +fun AndroidViewInLazyList() { + LazyColumn { + items(100) { index -> + AndroidView( + modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree + factory = { context -> + MyView(context) + }, + update = { view -> + view.selectedItem = index + }, + onReset = { view -> + view.clear() + } + ) + } + } +} +// [END android_compose_interop_apis_android_view_reuse] + // [START android_compose_interop_apis_views_in_compose] @Composable fun CustomView() { - var selectedItem by remember { mutableStateOf(0) } + var selectedItem by remember { mutableIntStateOf(0) } // Adds view to Compose AndroidView( @@ -231,6 +254,8 @@ fun ContentExample() { // [START_EXCLUDE silent] class MyView(context: Context) : View(context) { var selectedItem: Int = 0 + + fun clear() { } } // [END_EXCLUDE silent] // [END android_compose_interop_apis_views_in_compose] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt index 59ac0129d..fec60d697 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/interop/MigrationCommonScenariosSnippets.kt @@ -58,7 +58,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/kotlin/KotlinSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/kotlin/KotlinSnippets.kt index 12c5618b4..447b37031 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/kotlin/KotlinSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/kotlin/KotlinSnippets.kt @@ -340,15 +340,17 @@ fun MoveBoxWhereTapped() { // coroutines inside a suspend function coroutineScope { while (true) { - // Wait for the user to tap on the screen - val offset = awaitPointerEventScope { - awaitFirstDown().position - } - // Launch a new coroutine to asynchronously animate to - // where the user tapped on the screen - launch { - // Animate to the pressed position - animatedOffset.animateTo(offset) + // Wait for the user to tap on the screen and animate + // in the same block + awaitPointerEventScope { + val offset = awaitFirstDown().position + + // Launch a new coroutine to asynchronously animate to + // where the user tapped on the screen + launch { + // Animate to the pressed position + animatedOffset.animateTo(offset) + } } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt index 164dd3ea4..8c682a601 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/FlowLayoutSnippets.kt @@ -21,9 +21,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ContextualFlowRow -import androidx.compose.foundation.layout.ContextualFlowRowOverflow -import androidx.compose.foundation.layout.ContextualFlowRowOverflowScope import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowColumn import androidx.compose.foundation.layout.FlowRow @@ -33,22 +30,13 @@ 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.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.FilterChip 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.draw.clip import androidx.compose.ui.graphics.Color @@ -465,48 +453,6 @@ fun FlowLayout_FractionalSizing() { // [END android_compose_flow_layout_fractional_sizing] } -@OptIn(ExperimentalLayoutApi::class) -@Preview -@Composable -fun ContextualFlowLayoutExample() { - // [START android_compose_layouts_contextual_flow] - val totalCount = 40 - var maxLines by remember { - mutableStateOf(2) - } - - val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope -> - val remainingItems = totalCount - scope.shownItemCount - ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = { - if (remainingItems == 0) { - maxLines = 2 - } else { - maxLines += 5 - } - }) - } - ContextualFlowRow( - modifier = Modifier - .safeDrawingPadding() - .fillMaxWidth(1f) - .padding(16.dp) - .wrapContentHeight(align = Alignment.Top) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(4.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - maxLines = maxLines, - overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator( - minRowsToShowCollapse = 4, - expandIndicator = moreOrCollapseIndicator, - collapseIndicator = moreOrCollapseIndicator - ), - itemCount = totalCount - ) { index -> - ChipItem("Item $index") - } - // [END android_compose_layouts_contextual_flow] -} - @OptIn(ExperimentalLayoutApi::class) @Preview @Composable diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt index 77568686d..e893e8099 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/InsetsSnippets.kt @@ -28,9 +28,14 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fitInside import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding @@ -44,10 +49,19 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.BottomCenter import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.RectRulers +import androidx.compose.ui.layout.WindowInsetsRulers +import androidx.compose.ui.layout.innermostOf +import androidx.compose.ui.layout.layout import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt class InsetSnippetActivity : ComponentActivity() { @@ -147,3 +161,105 @@ fun OverrideDefaultInsetsSnippet() { ) // [END android_compose_insets_override_defaults] } + +// [START android_compose_insets_rulers] +@Composable +fun WindowInsetsRulersDemo(modifier: Modifier) { + Box( + contentAlignment = BottomCenter, + modifier = modifier + .fillMaxSize() + // The mistake that causes issues downstream, as .padding doesn't consume insets. + // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars), + // assume it's difficult to identify this issue to see how WindowInsetsRulers can help. + .padding(WindowInsets.navigationBars.asPaddingValues()) + ) { + TextField( + value = "Demo IME Insets", + onValueChange = {}, + modifier = modifier + // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child + // Composable without having to fix the parent upstream. + .alignToSafeDrawing() + + // .imePadding() + // .fillMaxWidth() + ) + } +} + +fun Modifier.alignToSafeDrawing(): Modifier { + return layout { measurable, constraints -> + if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) { + val placeable = measurable.measure(constraints) + val width = placeable.width + val height = placeable.height + layout(width, height) { + val bottom = WindowInsetsRulers.SafeDrawing.current.bottom + .current(0f).roundToInt() - height + val right = WindowInsetsRulers.SafeDrawing.current.right + .current(0f).roundToInt() + val left = WindowInsetsRulers.SafeDrawing.current.left + .current(0f).roundToInt() + measurable.measure(Constraints.fixed(right - left, height)) + .place(left, bottom) + } + } else { + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + } + } +} +// [END android_compose_insets_rulers] + +// [START android_compose_insets_fit_inside] +@Composable +fun FitInsideDemo(modifier: Modifier) { + Box( + modifier = modifier + .fillMaxSize() + // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc... + .fitInside(WindowInsetsRulers.SafeDrawing.current) + ) +} +// [END android_compose_insets_fit_inside] + +// [START android_compose_insets_rulers_ime] +@Composable +fun FitInsideWithImeDemo(modifier: Modifier) { + Box( + modifier = modifier + .fillMaxSize() + .fitInside( + RectRulers.innermostOf( + WindowInsetsRulers.NavigationBars.current, + WindowInsetsRulers.Ime.current + ) + ) + ) { + TextField( + value = "Demo IME Insets", + onValueChange = {}, + modifier = modifier.align(Alignment.BottomStart).fillMaxWidth() + ) + } +} +// [END android_compose_insets_rulers_ime] + +// [START android_compose_insets_rulers_status_caption_bars] +@Composable +fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) { + Box( + modifier = modifier + .fillMaxSize() + .fitInside( + RectRulers.innermostOf( + WindowInsetsRulers.StatusBars.current, + WindowInsetsRulers.CaptionBar.current + ) + ) + ) +} +// [END android_compose_insets_rulers_status_caption_bars] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt index 899163367..e09fe456d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/layouts/PagerSnippets.kt @@ -50,8 +50,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -239,7 +239,7 @@ fun PagerWithTabsExample() { pages.size }) - TabRow( + PrimaryTabRow( // Our selected tab is our current page selectedTabIndex = pagerState.currentPage, ) { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/CustomModifierSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/CustomModifierSnippets.kt index 7b778ef25..665fea4b7 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/CustomModifierSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/CustomModifierSnippets.kt @@ -18,6 +18,7 @@ package com.example.compose.snippets.modifiers import android.annotation.SuppressLint import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.DecayAnimationSpec import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloatAsState @@ -141,6 +142,7 @@ private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { } // [END android_compose_custom_modifiers_7] +// TODO: Create a new snippet that overrides InspectorInfo.inspectableProperties. // [START android_compose_custom_modifiers_8] // ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement() { @@ -158,6 +160,7 @@ fun Modifier.circle(color: Color) = this then CircleElement(color) // [END android_compose_custom_modifiers_9] private object CustomModifierSnippets10 { + // TODO: Create a new snippet that overrides InspectorInfo.inspectableProperties. // [START android_compose_custom_modifiers_10] // Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color) @@ -180,6 +183,7 @@ private object CustomModifierSnippets10 { // [END android_compose_custom_modifiers_10] } +// TODO: Create a new snippet that overrides InspectorInfo.inspectableProperties. // [START android_compose_custom_modifiers_11] fun Modifier.fixedPadding() = this then FixedPaddingElement @@ -259,7 +263,7 @@ class ScrollableNode : object CustomModifierSnippets14 { // [START android_compose_custom_modifiers_14] class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { - private val alpha = Animatable(1f) + private lateinit var alpha: Animatable override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) @@ -267,6 +271,7 @@ object CustomModifierSnippets14 { } override fun onAttach() { + alpha = Animatable(1f) coroutineScope.launch { alpha.animateTo( 0f, diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/VisibilitySnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/VisibilitySnippets.kt new file mode 100644 index 000000000..dbad0cc1b --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/modifiers/VisibilitySnippets.kt @@ -0,0 +1,170 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.modifiers + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +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.foundation.lazy.LazyColumn +import androidx.compose.material3.Card +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.layout.onFirstVisible +import androidx.compose.ui.layout.onVisibilityChanged +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp + +private object OnVisibilityChangedSample { + + @Composable + fun OnVisibilityChangedExample(modifier: Modifier = Modifier) { + // [START android_compose_modifiers_onVisibilityChanged] + Text( + text = "Some text", + modifier = Modifier + .onVisibilityChanged { visible -> + if (visible) { + // Do something if visible + } else { + // Do something if not visible + } + } + .padding(vertical = 8.dp) + ) + // [END android_compose_modifiers_onVisibilityChanged] + } +} + +private object OnVisibilityChangedMinFractionVisible { + + @Composable + fun OnVisibilityChangedModifierMinFractionExample(modifier: Modifier = Modifier) { + // [START android_compose_modifiers_onVisibilityChangedMinFraction] + LazyColumn( + modifier = modifier.fillMaxSize() + ) { + item { + Box( + modifier = Modifier + // [START_EXCLUDE] + .fillParentMaxWidth() + .border(0.5.dp, Color.Gray) + // [END_EXCLUDE] + // Here the visible callback gets triggered when 20% of the composable is visible + .onVisibilityChanged( + minFractionVisible = 0.2f, + ) { visible -> + if (visible) { + // Call specific logic here + // viewModel.fetchDataFromNetwork() + } + } + .padding(vertical = 16.dp) + ) { + Text( + text = "Sample Text", + modifier = Modifier.padding(horizontal = 16.dp) + ) + } + } + } + // [END android_compose_modifiers_onVisibilityChangedMinFraction] + } +} + +private object onVisibilityChangedMinDuration { + + val MutedPlum = Color(0xFF7B4B6B) + val PalePink = Color(0xFFF3E9EB) + + @Composable + fun OnVisibilityChangedMinDurationExample( + @DrawableRes imageRes: Int, + modifier: Modifier = Modifier, + ) { + // [START android_compose_modifiers_onVisibilityChangedMinDuration] + var background by remember { mutableStateOf(PalePink) } + Card( + modifier = modifier + // [START_EXCLUDE] + .height(300.dp) + .fillMaxWidth() + // [END_EXCLUDE] + .onVisibilityChanged(minDurationMs = 3000) { + if (it) { + background = MutedPlum + } + } + ) { + + Box( + modifier = Modifier + // [START_EXCLUDE] + .fillMaxSize() + // [END_EXCLUDE] + .background(background), + contentAlignment = Alignment.Center, + ) { + // [START_EXCLUDE] + Image( + painter = painterResource(id = imageRes), + contentDescription = "Androidify Bot", + ) + // [END_EXCLUDE] + } + } + // [END android_compose_modifiers_onVisibilityChangedMinDuration] + } +} + + +private object OnFirstVisibleSample { + + @Composable + fun OnFirstVisibleExample(id: Int, modifier: Modifier = Modifier) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(8.dp) + ) { + // [START android_compose_modifiers_onFirstVisible] + Text( + modifier = Modifier + .fillMaxSize() + .onFirstVisible { + println("OnFirstVisible : ProductCard: $id is visible") + } + .padding(8.dp), + text = "Product $id" + ) + // [END android_compose_modifiers_onFirstVisible] + } + } +} \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/performance/PerformanceSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/performance/PerformanceSnippets.kt index dcb7786e5..fc9af5d63 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/performance/PerformanceSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/performance/PerformanceSnippets.kt @@ -36,6 +36,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -264,7 +265,7 @@ private object BackwardsWrite { // [START android_compose_performance_backwardswrite] @Composable fun BadComposable() { - var count by remember { mutableStateOf(0) } + var count by remember { mutableIntStateOf(0) } // Causes recomposition on click Button(onClick = { count++ }, Modifier.wrapContentSize()) { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/phases/PhasesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/phases/PhasesSnippets.kt index c785052c4..1ca55b9e0 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/phases/PhasesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/phases/PhasesSnippets.kt @@ -30,6 +30,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -178,7 +179,7 @@ private object Loop { fun Loop() { // [START android_compose_phases_loop] Box { - var imageHeightPx by remember { mutableStateOf(0) } + var imageHeightPx by remember { mutableIntStateOf(0) } Image( painter = painterResource(R.drawable.rectangle), 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 02d65d57c..295894832 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 @@ -356,7 +356,7 @@ fun PipListenerPreAPI12(shouldEnterPipMode: Boolean) { ) { val context = LocalContext.current DisposableEffect(context) { - val onUserLeaveBehavior: () -> Unit = { + val onUserLeaveBehavior = Runnable { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } @@ -384,7 +384,7 @@ fun EnterPiPPre12(shouldEnterPipMode: Boolean) { ) { val context = LocalContext.current DisposableEffect(context) { - val onUserLeaveBehavior: () -> Unit = { + val onUserLeaveBehavior = Runnable { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/PredictiveBackSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/PredictiveBackSnippets.kt index fb94e1186..f9999f67c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/PredictiveBackSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/PredictiveBackSnippets.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -110,7 +111,10 @@ private fun PredictiveBackHandlerBasicExample() { Box( modifier = Modifier - .fillMaxSize(boxScale) + .graphicsLayer { + scaleX = boxScale + scaleY = scaleX + } .background(Color.Blue) ) @@ -127,6 +131,7 @@ private fun PredictiveBackHandlerBasicExample() { } catch (e: CancellationException) { // code for cancellation boxScale = 1F + throw e } } // [END android_compose_predictivebackhandler_basic] @@ -180,8 +185,10 @@ private fun PredictiveBackHandlerManualProgress() { closeDrawer(velocityTracker.calculateVelocity().x) } catch (e: CancellationException) { openDrawer(velocityTracker.calculateVelocity().x) + throw e + } finally { + velocityTracker.resetTracking() } - velocityTracker.resetTracking() } // [END android_compose_predictivebackhandler_manualprogress] } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt index 0a2f34a56..f964e352d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/resources/ResourcesSnippets.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Menu import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -109,7 +110,7 @@ fun Colors() { // #FFBB86FC // In your Compose code - Divider(color = colorResource(R.color.purple_200)) + HorizontalDivider(color = colorResource(R.color.purple_200)) // [END android_compose_resources_colors] } 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 dca1a2b02..cba065a33 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 @@ -36,6 +36,7 @@ import androidx.compose.runtime.SideEffect import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -59,7 +60,7 @@ 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) } + var pulseRateMs by remember { mutableLongStateOf(3000L) } val alpha = remember { Animatable(1f) } LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes while (isActive) { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt index 2a4f0c949..2e607fde9 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/state/StateHoistingSnippets.kt @@ -18,6 +18,8 @@ package com.example.compose.snippets.state +import android.R.id.message +import androidx.compose.foundation.clickable import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items @@ -34,7 +36,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withLink import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope @@ -60,9 +68,11 @@ private object StateHoistingSnippets1 { ) { var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state - ClickableText( + Text( text = AnnotatedString(message.content), - onClick = { showDetails = !showDetails } // Apply simple UI logic + modifier = Modifier.clickable { + showDetails = !showDetails // Apply UI logic + } ) if (showDetails) { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/AutofillSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/AutofillSnippets.kt new file mode 100644 index 000000000..6e30ba93b --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/AutofillSnippets.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.text + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.LocalAutofillHighlightColor +import androidx.compose.foundation.text.input.rememberTextFieldState +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.TextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.autofill.ContentType +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalAutofillManager +import androidx.compose.ui.semantics.contentType +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import com.example.compose.snippets.touchinput.Button + +@Composable +fun AddAutofill() { + // [START android_compose_autofill_1] + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.Username } + ) + // [END android_compose_autofill_1] +} + +@Composable +fun AddMultipleTypesOfAutofill() { + // [START android_compose_autofill_2] + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { + contentType = ContentType.Username + ContentType.EmailAddress + } + ) + // [END android_compose_autofill_2] +} + +@Composable +fun AutofillManager() { + // [START android_compose_autofill_3] + val autofillManager = LocalAutofillManager.current + // [END android_compose_autofill_3] +} + +@Composable +fun SaveDataWithAutofill() { + var textFieldValue = remember { + mutableStateOf(TextFieldValue("")) + } + // [START android_compose_autofill_4] + val autofillManager = LocalAutofillManager.current + + Column { + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.NewUsername } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.NewPassword } + ) + } + // [END android_compose_autofill_4] +} + +@Composable +fun SaveDataWithAutofillOnClick() { + // [START android_compose_autofill_5] + val autofillManager = LocalAutofillManager.current + + Column { + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.NewUsername }, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.NewPassword }, + ) + + // Submit button + Button(onClick = { autofillManager?.commit() }) { Text("Reset credentials") } + } + // [END android_compose_autofill_5] +} + +@Composable +fun CustomAutofillHighlight(customHighlightColor: Color = Color.Red) { + // [START android_compose_autofill_6] + val customHighlightColor = Color.Red + + CompositionLocalProvider(LocalAutofillHighlightColor provides customHighlightColor) { + TextField( + state = rememberTextFieldState(), + modifier = Modifier.semantics { contentType = ContentType.Username } + ) + } + // [END android_compose_autofill_6] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/StateBasedText.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/StateBasedText.kt new file mode 100644 index 000000000..e4e901bd8 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/StateBasedText.kt @@ -0,0 +1,296 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldBuffer +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.insert +import androidx.compose.foundation.text.input.maxLength +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.selectAll +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +import androidx.compose.foundation.text.input.then +import androidx.compose.material.OutlinedTextField +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.TextField +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.text.isDigitsOnly +import androidx.lifecycle.ViewModel + +@Preview +@Composable +fun StateBasedTextSnippets() { + Column() { + // [START android_compose_state_text_0] + TextField( + state = rememberTextFieldState(initialText = "Hello"), + label = { Text("Label") } + ) + // [END android_compose_state_text_0] + + // [START android_compose_state_text_1] + OutlinedTextField( + state = rememberTextFieldState(), + label = { Text("Label") } + ) + // [END android_compose_state_text_1] + } +} + +@Preview +@Composable +fun StyleTextField() { + // [START android_compose_state_text_2] + TextField( + state = rememberTextFieldState("Hello\nWorld\nInvisible"), + lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2), + placeholder = { Text("") }, + textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold), + label = { Text("Enter text") }, + modifier = Modifier.padding(20.dp) + ) + // [END android_compose_state_text_2] +} + +@Composable +fun ConfigureLineLimits() { + // [START android_compose_state_text_3] + TextField( + state = rememberTextFieldState(), + lineLimits = TextFieldLineLimits.SingleLine + ) + // [END android_compose_state_text_3] +} + +@Preview +@Composable +fun Multiline() { + Spacer(modifier = Modifier.height(15.dp)) + // [START android_compose_state_text_4] + TextField( + state = rememberTextFieldState("Hello\nWorld\nHello\nWorld"), + lineLimits = TextFieldLineLimits.MultiLine(1, 4) + ) + // [END android_compose_state_text_4] +} + +@Composable +fun StyleWithBrush() { + // [START android_compose_state_text_5] + val brush = remember { + Brush.linearGradient( + colors = listOf(Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Magenta) + ) + } + TextField( + state = rememberTextFieldState(), textStyle = TextStyle(brush = brush) + ) + // [END android_compose_state_text_5] +} + +@Composable +fun StateHoisting() { + // [START android_compose_state_text_6] + val usernameState = rememberTextFieldState() + TextField( + state = usernameState, + lineLimits = TextFieldLineLimits.SingleLine, + placeholder = { Text("Enter Username") } + ) + // [END android_compose_state_text_6] +} + +@Composable +fun TextFieldInitialState() { + // [START android_compose_state_text_7] + TextField( + state = rememberTextFieldState(initialText = "Username"), + lineLimits = TextFieldLineLimits.SingleLine, + ) + // [END android_compose_state_text_7] +} + +@Preview(showBackground = true) +@Composable +fun TextFieldBuffer() { + // [START android_compose_state_text_8] + val phoneNumberState = rememberTextFieldState("1234567890") + + TextField( + state = phoneNumberState, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone + ), + inputTransformation = InputTransformation.maxLength(10).then { + if (!asCharSequence().isDigitsOnly()) { + revertAllChanges() + } + }, + outputTransformation = OutputTransformation { + if (length > 0) insert(0, "(") + if (length > 4) insert(4, ")") + if (length > 8) insert(8, "-") + } + ) + // [END android_compose_state_text_8] +} + +@Preview +@Composable +fun EditTextFieldState() { + val usernameState = rememberTextFieldState("I love Android") + editTFState(usernameState) +} + +fun editTFState(textFieldState: TextFieldState) { + // [START android_compose_state_text_9] + // Initial textFieldState text passed in is "I love Android" + // textFieldState.text : I love Android + // textFieldState.selection: TextRange(14, 14) + textFieldState.edit { insert(14, "!") } + // textFieldState.text : I love Android! + // textFieldState.selection: TextRange(15, 15) + textFieldState.edit { replace(7, 14, "Compose") } + // textFieldState.text : I love Compose! + // textFieldState.selection: TextRange(15, 15) + textFieldState.edit { append("!!!") } + // textFieldState.text : I love Compose!!!! + // textFieldState.selection: TextRange(18, 18) + textFieldState.edit { selectAll() } + // textFieldState.text : I love Compose!!!! + // textFieldState.selection: TextRange(0, 18) + // [END android_compose_state_text_9] + + // [START android_compose_state_text_10] + textFieldState.setTextAndPlaceCursorAtEnd("I really love Android") + // textFieldState.text : I really love Android + // textFieldState.selection : TextRange(21, 21) + // [END android_compose_state_text_10] + + // [START android_compose_state_text_11] + textFieldState.clearText() + // textFieldState.text : + // textFieldState.selection : TextRange(0, 0) + // [END android_compose_state_text_11] +} + +class TextFieldViewModel : ViewModel() { + val usernameState = TextFieldState() + fun validateUsername() { + } +} +val textFieldViewModel = TextFieldViewModel() + +@Composable +fun TextFieldKeyboardOptions() { + // [START android_compose_state_text_13] + TextField( + state = textFieldViewModel.usernameState, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + onKeyboardAction = { performDefaultAction -> + textFieldViewModel.validateUsername() + performDefaultAction() + } + ) + // [END android_compose_state_text_13] +} + +@Composable +fun TextFieldInputTransformation() { + // [START android_compose_state_text_14] + TextField( + state = rememberTextFieldState(), + lineLimits = TextFieldLineLimits.SingleLine, + inputTransformation = InputTransformation.maxLength(10) + ) + // [END android_compose_state_text_14] +} + +// [START android_compose_state_text_15] +class CustomInputTransformation : InputTransformation { + override fun TextFieldBuffer.transformInput() { + } +} +// [END android_compose_state_text_15] + +// [START android_compose_state_text_16] +class DigitOnlyInputTransformation : InputTransformation { + override fun TextFieldBuffer.transformInput() { + if (!asCharSequence().isDigitsOnly()) { + revertAllChanges() + } + } +} +// [END android_compose_state_text_16] + +@Composable +fun ChainInputTransformation() { + // [START android_compose_state_text_17] + TextField( + state = rememberTextFieldState(), + inputTransformation = InputTransformation.maxLength(6) + .then(CustomInputTransformation()), + ) + // [END android_compose_state_text_17] +} + +// [START android_compose_state_text_18] +class CustomOutputTransformation : OutputTransformation { + override fun TextFieldBuffer.transformOutput() { + } +} +// [END android_compose_state_text_18] + +// [START android_compose_state_text_19] +class PhoneNumberOutputTransformation : OutputTransformation { + override fun TextFieldBuffer.transformOutput() { + if (length > 0) insert(0, "(") + if (length > 4) insert(4, ")") + if (length > 8) insert(8, "-") + } +} +// [END android_compose_state_text_19] + +@Composable +fun TextFieldOutputTransformation() { + // [START android_compose_state_text_20] + TextField( + state = rememberTextFieldState(), + outputTransformation = PhoneNumberOutputTransformation() + ) + // [END android_compose_state_text_20] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt new file mode 100644 index 000000000..a9e970266 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextFieldMigrationSnippets.kt @@ -0,0 +1,345 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.text + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.delete +import androidx.compose.foundation.text.input.insert +import androidx.compose.foundation.text.input.maxLength +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.SecureTextField +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.Text +//noinspection UsingMaterialAndMaterial3Libraries +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.substring +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.example.compose.snippets.touchinput.Button +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.update + +// [START android_compose_text_textfield_migration_old_simple] +@Composable +fun OldSimpleTextField() { + var state by rememberSaveable { mutableStateOf("") } + TextField( + value = state, + onValueChange = { state = it }, + singleLine = true, + ) +} +// [END android_compose_text_textfield_migration_old_simple] + +// [START android_compose_text_textfield_migration_new_simple] +@Composable +fun NewSimpleTextField() { + TextField( + state = rememberTextFieldState(), + lineLimits = TextFieldLineLimits.SingleLine + ) +} +// [END android_compose_text_textfield_migration_new_simple] + +// [START android_compose_text_textfield_migration_old_filtering] +@Composable +fun OldNoLeadingZeroes() { + var input by rememberSaveable { mutableStateOf("") } + TextField( + value = input, + onValueChange = { newText -> + input = newText.trimStart { it == '0' } + } + ) +} +// [END android_compose_text_textfield_migration_old_filtering] + +// [START android_compose_text_textfield_migration_new_filtering] + +@Preview +@Composable +fun NewNoLeadingZeros() { + TextField( + state = rememberTextFieldState(), + inputTransformation = InputTransformation { + while (length > 0 && charAt(0) == '0') delete(0, 1) + } + ) +} +// [END android_compose_text_textfield_migration_new_filtering] + +// [START android_compose_text_textfield_migration_old_credit_card_formatter] +@Composable +fun OldTextFieldCreditCardFormatter() { + var state by remember { mutableStateOf("") } + TextField( + value = state, + onValueChange = { if (it.length <= 16) state = it }, + visualTransformation = VisualTransformation { text -> + // Making XXXX-XXXX-XXXX-XXXX string. + var out = "" + for (i in text.indices) { + out += text[i] + if (i % 4 == 3 && i != 15) out += "-" + } + + TransformedText( + text = AnnotatedString(out), + offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 3) return offset + if (offset <= 7) return offset + 1 + if (offset <= 11) return offset + 2 + if (offset <= 16) return offset + 3 + return 19 + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 4) return offset + if (offset <= 9) return offset - 1 + if (offset <= 14) return offset - 2 + if (offset <= 19) return offset - 3 + return 16 + } + } + ) + } + ) +} +// [END android_compose_text_textfield_migration_old_credit_card_formatter] + +// [START android_compose_text_textfield_migration_new_credit_card_formatter] +@Composable +fun NewTextFieldCreditCardFormatter() { + val state = rememberTextFieldState() + TextField( + state = state, + inputTransformation = InputTransformation.maxLength(16), + outputTransformation = OutputTransformation { + if (length > 4) insert(4, "-") + if (length > 9) insert(9, "-") + if (length > 14) insert(14, "-") + }, + ) +} +// [END android_compose_text_textfield_migration_new_credit_card_formatter] + +private object StateUpdateSimpleSnippet { + object UserRepository { + suspend fun fetchUsername(): String = TODO() + } + // [START android_compose_text_textfield_migration_old_update_state_simple] + @Composable + fun OldTextFieldStateUpdate(userRepository: UserRepository) { + var username by remember { mutableStateOf("") } + LaunchedEffect(Unit) { + username = userRepository.fetchUsername() + } + TextField( + value = username, + onValueChange = { username = it } + ) + } + // [END android_compose_text_textfield_migration_old_update_state_simple] + + // [START android_compose_text_textfield_migration_new_update_state_simple] + @Composable + fun NewTextFieldStateUpdate(userRepository: UserRepository) { + val usernameState = rememberTextFieldState() + LaunchedEffect(Unit) { + usernameState.setTextAndPlaceCursorAtEnd(userRepository.fetchUsername()) + } + TextField(state = usernameState) + } + // [END android_compose_text_textfield_migration_new_update_state_simple] +} + +// [START android_compose_text_textfield_migration_old_state_update_complex] +@Composable +fun OldTextFieldAddMarkdownEmphasis() { + var markdownState by remember { mutableStateOf(TextFieldValue()) } + Button(onClick = { + // add ** decorations around the current selection, also preserve the selection + markdownState = with(markdownState) { + copy( + text = buildString { + append(text.take(selection.min)) + append("**") + append(text.substring(selection)) + append("**") + append(text.drop(selection.max)) + }, + selection = TextRange(selection.min + 2, selection.max + 2) + ) + } + }) { + Text("Bold") + } + TextField( + value = markdownState, + onValueChange = { markdownState = it }, + maxLines = 10 + ) +} +// [END android_compose_text_textfield_migration_old_state_update_complex] + +// [START android_compose_text_textfield_migration_new_state_update_complex] +@Composable +fun NewTextFieldAddMarkdownEmphasis() { + val markdownState = rememberTextFieldState() + LaunchedEffect(Unit) { + // add ** decorations around the current selection + markdownState.edit { + insert(originalSelection.max, "**") + insert(originalSelection.min, "**") + selection = TextRange(originalSelection.min + 2, originalSelection.max + 2) + } + } + TextField( + state = markdownState, + lineLimits = TextFieldLineLimits.MultiLine(1, 10) + ) +} +// [END android_compose_text_textfield_migration_new_state_update_complex] + +private object ViewModelMigrationOldSnippet { + // [START android_compose_text_textfield_migration_old_viewmodel] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() + + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } + + data class UiState( + val username: String = "", + val password: String = "" + ) + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val uiState by loginViewModel.uiState.collectAsStateWithLifecycle() + Column(modifier) { + TextField( + value = uiState.username, + onValueChange = { loginViewModel.updateUsername(it) } + ) + TextField( + value = uiState.password, + onValueChange = { loginViewModel.updatePassword(it) }, + visualTransformation = PasswordVisualTransformation() + ) + } + } + // [END android_compose_text_textfield_migration_old_viewmodel] +} + +private object ViewModelMigrationNewSimpleSnippet { + // [START android_compose_text_textfield_migration_new_viewmodel_simple] + class LoginViewModel : ViewModel() { + val usernameState = TextFieldState() + val passwordState = TextFieldState() + } + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + Column(modifier) { + TextField(state = loginViewModel.usernameState,) + SecureTextField(state = loginViewModel.passwordState) + } + } + // [END android_compose_text_textfield_migration_new_viewmodel_simple] +} + +private object ViewModelMigrationNewConformingSnippet { + // [START android_compose_text_textfield_migration_new_viewmodel_conforming] + class LoginViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow + get() = _uiState.asStateFlow() + + fun updateUsername(username: String) = _uiState.update { it.copy(username = username) } + + fun updatePassword(password: String) = _uiState.update { it.copy(password = password) } + } + + data class UiState( + val username: String = "", + val password: String = "" + ) + + @Composable + fun LoginForm( + loginViewModel: LoginViewModel, + modifier: Modifier = Modifier + ) { + val initialUiState = remember(loginViewModel) { loginViewModel.uiState.value } + Column(modifier) { + val usernameState = rememberTextFieldState(initialUiState.username) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updateUsername(it) + } + } + TextField(usernameState) + + val passwordState = rememberTextFieldState(initialUiState.password) + LaunchedEffect(usernameState) { + snapshotFlow { usernameState.text.toString() }.collectLatest { + loginViewModel.updatePassword(it) + } + } + SecureTextField(passwordState) + } + } + // [END android_compose_text_textfield_migration_new_viewmodel_conforming] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/focus/FocusSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/focus/FocusSnippets.kt index 2fce7f0aa..d48412c3c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/focus/FocusSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/focus/FocusSnippets.kt @@ -297,7 +297,7 @@ private fun RequestFocus2() { private fun Capture() { var text by remember { mutableStateOf("") } // [START android_compose_touchinput_focus_capture] - val textField = FocusRequester() + val textField = remember { FocusRequester() } TextField( value = text, 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 d5f7fffed..9fcedffe2 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 @@ -52,6 +52,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -77,7 +79,7 @@ import kotlin.math.roundToInt // [START android_compose_touchinput_gestures_clickable] @Composable private fun ClickableSample() { - val count = remember { mutableStateOf(0) } + val count = remember { mutableIntStateOf(0) } // content that you want to make clickable Text( text = count.value.toString(), @@ -89,7 +91,7 @@ private fun ClickableSample() { @Preview @Composable private fun WithPointerInput() { - val count = remember { mutableStateOf(0) } + val count = remember { mutableIntStateOf(0) } // content that you want to make clickable Text( text = count.value.toString(), @@ -151,7 +153,7 @@ private fun ScrollBoxesSmooth() { @Composable private fun ScrollableSample() { // actual composable state - var offset by remember { mutableStateOf(0f) } + var offset by remember { mutableFloatStateOf(0f) } Box( Modifier .size(150.dp) @@ -249,7 +251,7 @@ private object NestedScrollInterop { // [START android_compose_touchinput_gestures_draggable] @Composable private fun DraggableText() { - var offsetX by remember { mutableStateOf(0f) } + var offsetX by remember { mutableFloatStateOf(0f) } Text( modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0) } @@ -268,8 +270,8 @@ private fun DraggableText() { @Composable private fun DraggableTextLowLevel() { Box(modifier = Modifier.fillMaxSize()) { - var offsetX by remember { mutableStateOf(0f) } - var offsetY by remember { mutableStateOf(0f) } + var offsetX by remember { mutableFloatStateOf(0f) } + var offsetY by remember { mutableFloatStateOf(0f) } Box( Modifier @@ -324,8 +326,8 @@ private fun SwipeableSample() { @Composable private fun TransformableSample() { // set up all transformation states - var scale by remember { mutableStateOf(1f) } - var rotation by remember { mutableStateOf(0f) } + var scale by remember { mutableFloatStateOf(1f) } + var rotation by remember { mutableFloatStateOf(0f) } var offset by remember { mutableStateOf(Offset.Zero) } val state = rememberTransformableState { zoomChange, offsetChange, rotationChange -> scale *= zoomChange diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt index 07248a6f9..6b9fa3fda 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/userinteractions/UserInteractions.kt @@ -30,13 +30,13 @@ import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.LocalRippleConfiguration -import androidx.compose.material.LocalUseFallbackRippleImplementation import androidx.compose.material.RippleConfiguration import androidx.compose.material.ripple import androidx.compose.material.ripple.LocalRippleTheme import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material.ripple.RippleTheme import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -239,28 +239,6 @@ private class ScaleIndicationNode( fun App() { } -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun LocalUseFallbackRippleImplementationExample() { -// [START android_compose_userinteractions_localusefallbackrippleimplementation] - CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { - MaterialTheme { - App() - } - } -// [END android_compose_userinteractions_localusefallbackrippleimplementation] -} - -// [START android_compose_userinteractions_localusefallbackrippleimplementation_app_theme] -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun MyAppTheme(content: @Composable () -> Unit) { - CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { - MaterialTheme(content = content) - } -} -// [END android_compose_userinteractions_localusefallbackrippleimplementation_app_theme] - @OptIn(ExperimentalMaterialApi::class) @Composable private fun MyComposableDisabledRippleConfig() { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt b/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt index acf007bfd..ab604e852 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/ui/theme/Type.kt @@ -46,5 +46,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ + */ ) diff --git a/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml b/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml index 2f9758487..0f6026a41 100644 --- a/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/compose/snippets/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,19 +1,19 @@ + + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml b/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml index 2de5d0a86..745eab18a 100644 --- a/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml +++ b/compose/snippets/src/main/res/drawable/baseline_directions_bus_24.xml @@ -1,19 +1,19 @@ + + 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. +--> diff --git a/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml b/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml index 817fb0539..15368afa6 100644 --- a/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml +++ b/compose/snippets/src/main/res/drawable/baseline_shopping_cart_24.xml @@ -1,19 +1,19 @@ + + 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. +--> diff --git a/compose/snippets/src/main/res/drawable/button_outline.xml b/compose/snippets/src/main/res/drawable/button_outline.xml index f4eb02291..790fd2eb2 100644 --- a/compose/snippets/src/main/res/drawable/button_outline.xml +++ b/compose/snippets/src/main/res/drawable/button_outline.xml @@ -1,22 +1,22 @@ + + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/drawable/fast_forward.xml b/compose/snippets/src/main/res/drawable/fast_forward.xml new file mode 100644 index 000000000..95cdea9c4 --- /dev/null +++ b/compose/snippets/src/main/res/drawable/fast_forward.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/fast_forward_filled.xml b/compose/snippets/src/main/res/drawable/fast_forward_filled.xml new file mode 100644 index 000000000..84a42ad80 --- /dev/null +++ b/compose/snippets/src/main/res/drawable/fast_forward_filled.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/fast_rewind.xml b/compose/snippets/src/main/res/drawable/fast_rewind.xml new file mode 100644 index 000000000..4564c5ba7 --- /dev/null +++ b/compose/snippets/src/main/res/drawable/fast_rewind.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/fast_rewind_filled.xml b/compose/snippets/src/main/res/drawable/fast_rewind_filled.xml new file mode 100644 index 000000000..f6f8cebb1 --- /dev/null +++ b/compose/snippets/src/main/res/drawable/fast_rewind_filled.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/favorite.xml b/compose/snippets/src/main/res/drawable/favorite.xml new file mode 100644 index 000000000..e6859af3e --- /dev/null +++ b/compose/snippets/src/main/res/drawable/favorite.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/favorite_filled.xml b/compose/snippets/src/main/res/drawable/favorite_filled.xml new file mode 100644 index 000000000..d49f76c61 --- /dev/null +++ b/compose/snippets/src/main/res/drawable/favorite_filled.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/compose/snippets/src/main/res/drawable/ic_hourglass_animated.xml b/compose/snippets/src/main/res/drawable/ic_hourglass_animated.xml index ad4c4a39f..c30879c0e 100644 --- a/compose/snippets/src/main/res/drawable/ic_hourglass_animated.xml +++ b/compose/snippets/src/main/res/drawable/ic_hourglass_animated.xml @@ -1,18 +1,18 @@ diff --git a/compose/snippets/src/main/res/drawable/ic_launcher_background.xml b/compose/snippets/src/main/res/drawable/ic_launcher_background.xml index 4c2360d21..2edadf928 100644 --- a/compose/snippets/src/main/res/drawable/ic_launcher_background.xml +++ b/compose/snippets/src/main/res/drawable/ic_launcher_background.xml @@ -1,20 +1,19 @@ + 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. +--> + 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. +--> diff --git a/compose/snippets/src/main/res/drawable/ic_moon_24.xml b/compose/snippets/src/main/res/drawable/ic_moon_24.xml index 527559f3f..b1c551d5e 100644 --- a/compose/snippets/src/main/res/drawable/ic_moon_24.xml +++ b/compose/snippets/src/main/res/drawable/ic_moon_24.xml @@ -1,3 +1,19 @@ + + diff --git a/compose/snippets/src/main/res/drawable/ic_sun_24.xml b/compose/snippets/src/main/res/drawable/ic_sun_24.xml index a63c6400e..4bac89d3e 100644 --- a/compose/snippets/src/main/res/drawable/ic_sun_24.xml +++ b/compose/snippets/src/main/res/drawable/ic_sun_24.xml @@ -1,3 +1,19 @@ + + diff --git a/compose/snippets/src/main/res/layout/activity_example.xml b/compose/snippets/src/main/res/layout/activity_example.xml index be37892ac..b85c61cf1 100644 --- a/compose/snippets/src/main/res/layout/activity_example.xml +++ b/compose/snippets/src/main/res/layout/activity_example.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/example_layout.xml b/compose/snippets/src/main/res/layout/example_layout.xml index d77d31543..91e49c045 100644 --- a/compose/snippets/src/main/res/layout/example_layout.xml +++ b/compose/snippets/src/main/res/layout/example_layout.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/example_view.xml b/compose/snippets/src/main/res/layout/example_view.xml index 77fff6e8c..86bdacae8 100644 --- a/compose/snippets/src/main/res/layout/example_view.xml +++ b/compose/snippets/src/main/res/layout/example_view.xml @@ -1,20 +1,19 @@ + 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. +--> diff --git a/compose/snippets/src/main/res/layout/fragment_example.xml b/compose/snippets/src/main/res/layout/fragment_example.xml index fd65463ef..d5517e3c4 100644 --- a/compose/snippets/src/main/res/layout/fragment_example.xml +++ b/compose/snippets/src/main/res/layout/fragment_example.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml b/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml index bb28c5685..b3a672cbe 100644 --- a/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml +++ b/compose/snippets/src/main/res/layout/interop_layout_preview_composable.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml b/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml index 2ef7e2dcb..a7bd893b7 100644 --- a/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml +++ b/compose/snippets/src/main/res/layout/migration_strategy_existing_screens.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml b/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml index ac462f26c..e5ed1b289 100644 --- a/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml +++ b/compose/snippets/src/main/res/layout/migration_strategy_xml_example.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/my_container_view.xml b/compose/snippets/src/main/res/layout/my_container_view.xml index 4d0ecb396..cec8feedc 100644 --- a/compose/snippets/src/main/res/layout/my_container_view.xml +++ b/compose/snippets/src/main/res/layout/my_container_view.xml @@ -1,21 +1,21 @@ + + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/my_fragment_layout.xml b/compose/snippets/src/main/res/layout/my_fragment_layout.xml index d2428b777..3b0ec2765 100644 --- a/compose/snippets/src/main/res/layout/my_fragment_layout.xml +++ b/compose/snippets/src/main/res/layout/my_fragment_layout.xml @@ -1,24 +1,23 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml b/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml index bbc6128e7..229f37a13 100644 --- a/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml +++ b/compose/snippets/src/main/res/layout/touchinput_gestures_nested_scroll_interop.xml @@ -1,20 +1,19 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 0b0b5355c..05f11b4d6 100644 --- a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,22 +1,21 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 0b0b5355c..05f11b4d6 100644 --- a/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/compose/snippets/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,22 +1,21 @@ + 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. +--> - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/values-es/strings.xml b/compose/snippets/src/main/res/values-es/strings.xml index 3ba327f49..7287beb35 100644 --- a/compose/snippets/src/main/res/values-es/strings.xml +++ b/compose/snippets/src/main/res/values-es/strings.xml @@ -1,19 +1,19 @@ + + 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. +--> snippets Golden Retriever in fall leaves @@ -53,4 +53,4 @@ Compras Perfil Esto es sólo un texto de marcador de posición. - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/values/colors.xml b/compose/snippets/src/main/res/values/colors.xml index 0205675f4..55242e03e 100644 --- a/compose/snippets/src/main/res/values/colors.xml +++ b/compose/snippets/src/main/res/values/colors.xml @@ -1,20 +1,19 @@ + 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. +--> #FFBB86FC #FF6200EE @@ -25,4 +24,4 @@ #FFFFFFFF #FFF #FFF - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/values/dimens.xml b/compose/snippets/src/main/res/values/dimens.xml index d8dec0639..99c6f584a 100644 --- a/compose/snippets/src/main/res/values/dimens.xml +++ b/compose/snippets/src/main/res/values/dimens.xml @@ -1,20 +1,19 @@ + 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. +--> 8dp - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/values/ids.xml b/compose/snippets/src/main/res/values/ids.xml index f97cf8a5b..6cc94fcf1 100644 --- a/compose/snippets/src/main/res/values/ids.xml +++ b/compose/snippets/src/main/res/values/ids.xml @@ -1,23 +1,22 @@ + 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. +--> - \ 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 02254e29a..d4b9a63a6 100644 --- a/compose/snippets/src/main/res/values/strings.xml +++ b/compose/snippets/src/main/res/values/strings.xml @@ -1,19 +1,19 @@ + + 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. +--> snippets Golden Retriever in fall leaves @@ -55,4 +55,4 @@ This is just a placeholder. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - \ No newline at end of file + diff --git a/compose/snippets/src/main/res/values/themes.xml b/compose/snippets/src/main/res/values/themes.xml index 3d9f463cd..9e7318559 100644 --- a/compose/snippets/src/main/res/values/themes.xml +++ b/compose/snippets/src/main/res/values/themes.xml @@ -1,21 +1,20 @@ + 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. +-->