From b840929b6f27b05e77e822b1ac7ee982d00f2d91 Mon Sep 17 00:00:00 2001 From: Simona Milanovic Date: Tue, 4 Aug 2026 18:08:48 +0200 Subject: [PATCH] Revert last update --- .claude-plugin/marketplace.json | 1 + .codex-plugin/plugin.json | 5 + device-ai/appfunctions/SKILL.md | 6 +- .../appfunctions/{references => }/context.md | 0 identity/verified-email/SKILL.md | 14 +- .../email-verification-implementation.md | 24 +- .../digital-credentials/email-verification.md | 145 ++ .../identity/passkeys/create-passkeys.md | 8 +- jetpack-compose/adaptive/SKILL.md | 2 +- .../layouts/adaptive/flexbox/get-started.md | 2 +- .../layouts/adaptive/grid/get-started.md | 2 +- performance/r8-analyzer/SKILL.md | 37 +- .../references/CONFIGURATION-ANALYZER.md | 287 ++++ .../references/REFLECTION-GUIDE.md | 139 ++ play/engage-sdk-integration/SKILL.md | 8 +- .../android/guide/playcore/engage/social.md | 702 ---------- .../playcore/engage/tv/getting-started.md | 18 +- .../references/common.md | 519 +++++++ .../SKILL.md | 93 ++ .../google/play/billing/release-notes.md | 1226 +++++++++++++++++ .../references/migration-logic.md | 78 ++ .../references/version-checklist.md | 54 + wear/wear-compose-m3/SKILL.md | 173 +-- .../wearables/compose/migrate-to-material3.md | 10 +- .../foundation/CurvedWorldSample.kt.md.txt | 430 ------ .../foundation/ExpandableSample.kt.md.txt | 99 -- .../HierarchicalFocusSample.kt.md.txt | 141 -- .../foundation/PagerSamples.kt.md.txt | 56 - .../foundation/RotarySamples.kt.md.txt | 153 -- .../ScalingLazyColumnSample.kt.md.txt | 131 -- .../SwipeToDismissBoxSample.kt.md.txt | 168 --- .../foundation/SwipeToRevealSample.kt.md.txt | 282 ---- .../TransformingLazyColumnSample.kt.md.txt | 335 ----- .../material3/AlertDialogSample.kt.md.txt | 377 ----- .../material3/AnimatedTextSample.kt.md.txt | 165 --- .../material3/ButtonGroupSample.kt.md.txt | 91 -- .../material3/ButtonSample.kt.md.txt | 496 ------- .../references/material3/CardSample.kt.md.txt | 470 ------- .../material3/CheckboxButtonSample.kt.md.txt | 69 - .../ConfirmationDialogSample.kt.md.txt | 162 --- .../material3/CurvedTextSamples.kt.md.txt | 78 -- .../material3/DatePickerSample.kt.md.txt | 131 -- .../DynamicColorSchemeSample.kt.md.txt | 74 - .../material3/EdgeButtonSample.kt.md.txt | 63 - .../FadingExpandingLabelSample.kt.md.txt | 60 - .../material3/IconButtonSample.kt.md.txt | 125 -- .../IconToggleButtonSample.kt.md.txt | 116 -- .../material3/LevelIndicatorSample.kt.md.txt | 68 - .../material3/ListHeaderSample.kt.md.txt | 177 --- .../OneHandedGestureSamples.kt.md.txt | 651 --------- .../OpenOnPhoneDialogSample.kt.md.txt | 57 - .../material3/PageIndicatorSample.kt.md.txt | 110 -- .../material3/PagerScaffoldSample.kt.md.txt | 204 --- .../material3/PickerSample.kt.md.txt | 111 -- .../material3/PlaceholderSample.kt.md.txt | 182 --- .../ProgressIndicatorSample.kt.md.txt | 263 ---- .../material3/RadioButtonSample.kt.md.txt | 109 -- .../material3/ScaffoldSample.kt.md.txt | 146 -- .../material3/ScrollAwaySample.kt.md.txt | 97 -- .../material3/ScrollIndicatorSample.kt.md.txt | 66 - .../material3/SliderSample.kt.md.txt | 86 -- .../material3/StepperSample.kt.md.txt | 148 -- .../SurfaceTransformationSample.kt.md.txt | 165 --- .../SwipeToDismissBoxSample.kt.md.txt | 171 --- .../material3/SwitchButtonSample.kt.md.txt | 68 - .../material3/TextButtonSample.kt.md.txt | 115 -- .../TextToggleButtonSample.kt.md.txt | 71 - .../material3/TimeTextSample.kt.md.txt | 65 - .../TransformationSpecSample.kt.md.txt | 246 ---- ...ingLazyColumnNotificationsSample.kt.md.txt | 89 -- .../TransformingLazyColumnSample.kt.md.txt | 309 ----- .../SKILL.md | 8 +- .../request-hardware-permissions.md | 179 +-- .../references/icon-source.md | 389 ++++++ .../references/listitem-source.md | 281 ++++ 75 files changed, 3408 insertions(+), 9048 deletions(-) rename device-ai/appfunctions/{references => }/context.md (100%) create mode 100644 identity/verified-email/references/android/identity/digital-credentials/email-verification.md create mode 100644 performance/r8-analyzer/references/CONFIGURATION-ANALYZER.md create mode 100644 performance/r8-analyzer/references/REFLECTION-GUIDE.md delete mode 100644 play/engage-sdk-integration/references/android/guide/playcore/engage/social.md create mode 100644 play/engage-sdk-integration/references/common.md create mode 100644 play/play-billing-library-version-upgrade/SKILL.md create mode 100644 play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md create mode 100644 play/play-billing-library-version-upgrade/references/migration-logic.md create mode 100644 play/play-billing-library-version-upgrade/references/version-checklist.md delete mode 100644 wear/wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/PagerSamples.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/RotarySamples.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/CardSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/DatePickerSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/IconButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/PickerSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/SliderSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/StepperSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TextButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TimeTextSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt delete mode 100644 wear/wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt create mode 100644 xr/display-glasses-with-jetpack-compose-glimmer/references/icon-source.md create mode 100644 xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fa5e1e5..d8900b3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -25,6 +25,7 @@ "./navigation/navigation-3", "./performance/r8-analyzer", "./play/engage-sdk-integration", + "./play/play-billing-library-version-upgrade", "./play/play-policy-insights", "./profilers/perfetto-sql", "./profilers/perfetto-trace-analysis", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 637f0b7..f28a37b 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -70,6 +70,11 @@ "path": "./play/engage-sdk-integration" } }, + { + "source": { + "path": "./play/play-billing-library-version-upgrade" + } + }, { "source": { "path": "./play/play-policy-insights" diff --git a/device-ai/appfunctions/SKILL.md b/device-ai/appfunctions/SKILL.md index cc5a2c6..ad75915 100644 --- a/device-ai/appfunctions/SKILL.md +++ b/device-ai/appfunctions/SKILL.md @@ -9,7 +9,7 @@ description: Analyzes Android apps to identify key user workflows for AppFunctio license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-31' + last-updated: '2026-07-28' keywords: - AppFunctions - Kotlin @@ -55,7 +55,7 @@ If users request a subset of steps, you must encourage them to use all steps. If they apply, you must load the following references: -- *[Context and terminology](references/context.md)*: Defines the ubiquitous language, architecture definitions, and design patterns across the AppFunctions skill suite. Load when you need to understand core architecture terminology or check the distinctions between modern and legacy AppFunctions APIs. +- *[Context and terminology](context.md)*: Defines the ubiquitous language, architecture definitions, and design patterns across the AppFunctions skill suite. Load when you need to understand core architecture terminology or check the distinctions between modern and legacy AppFunctions APIs. - *[Migration to service entry point](references/migrate-to-service-entry-point.md)* : Documents the systematic procedure for migrating Android applications using AppFunctions versions 1.0.0-alpha09 and lower to `AppFunctionServiceEntryPoint` architecture introduced in version 1.0.0-alpha10. Load when a user asks to migrate or upgrade existing AppFunctions code, or when you encounter legacy `AppFunctionConfiguration.Provider` implementations. ## Critical constraints @@ -67,5 +67,5 @@ If they apply, you must load the following references: - If you encounter build-time errors such as Kotlin Symbol Processing (KSP) issues, see [Implementation and configuration](references/implementation-configuration.md). - If you encounter runtime errors such as missing services or execution failures, see [Testing and debugging](references/adb-interaction-testing.md). -- If you need architecture definitions and vocabulary, see [Context and terminology](references/context.md). +- If you need architecture definitions and vocabulary, see [Context and terminology](context.md). - If you encounter issues when upgrading legacy configurations, see [Migration to service entry point](references/migrate-to-service-entry-point.md). diff --git a/device-ai/appfunctions/references/context.md b/device-ai/appfunctions/context.md similarity index 100% rename from device-ai/appfunctions/references/context.md rename to device-ai/appfunctions/context.md diff --git a/identity/verified-email/SKILL.md b/identity/verified-email/SKILL.md index b331f3e..784038e 100644 --- a/identity/verified-email/SKILL.md +++ b/identity/verified-email/SKILL.md @@ -8,7 +8,7 @@ description: Provides a complete workflow for implementing verified email retrie license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-30' + last-updated: '2026-07-02' keywords: - implementation - Android @@ -26,7 +26,7 @@ metadata: ## Fundamentals -- *[Overview of Digital Credentials](https://developer.android.com/identity/digital-credentials/email-verification)*: Learn about cryptographically verifiable documents and the role of Credential Manager. +- *[Overview of Digital Credentials](references/android/identity/digital-credentials/email-verification.md)*: Learn about cryptographically verifiable documents and the role of Credential Manager. - *[Glossary](references/android/identity/digital-credentials/email-verification-implementation.md)* : Definitions for `dcql_query`, `UserInfoCredential`, and `GetDigitalCredentialOption`. ### Standards \& Examples @@ -130,8 +130,8 @@ Manager: ```kotlin dependencies { - implementation("androidx.credentials:credentials:1.7.0-alpha03") - implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03") + implementation("androidx.credentials:credentials:1.7.0-alpha02") + implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") } ``` @@ -139,8 +139,8 @@ dependencies { ```groovy dependencies { - implementation "androidx.credentials:credentials:1.7.0-alpha03" - implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03" + implementation "androidx.credentials:credentials:1.7.0-alpha02" + implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" } ``` @@ -404,7 +404,7 @@ actual call to the Credential Manager API. ## See also -- [Overview of verified email retrieval](https://developer.android.com/identity/digital-credentials/email-verification) +- [Overview of verified email retrieval](references/android/identity/digital-credentials/email-verification.md) - [Credential Manager](references/android/identity/credential-manager/index.md) ## Critical Security Guidelines diff --git a/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md b/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md index cb4bcc4..9305e92 100644 --- a/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md +++ b/identity/verified-email/references/android/identity/digital-credentials/email-verification-implementation.md @@ -1,16 +1,6 @@ -## Android skills - -[View on GitHub](https://github.com/android/skills/tree/main/identity/verified-email) - -### Retrieve verified email - -Use an Android skill to integrate a secure, OTP-less email verification flow into your app. To install the skill from the [Android CLI](https://developer.android.com/tools/agents/android-cli), run: - - android skills add --skill verified-email - -
- -## Overview +> [!IMPORTANT] +> **Important:** We released an agent skill to help you implement email verification with the Digital Credentials API. Try out the skill from the [Android skills +> repository](https://github.com/android/skills). This guide describes how to implement verified email retrieval using the [Digital Credentials Verifier API](https://developer.android.com/identity/digital-credentials/credential-verifier) through an [OpenID for Verifiable @@ -25,8 +15,8 @@ Manager: ```kotlin dependencies { - implementation("androidx.credentials:credentials:1.7.0-alpha03") - implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03") + implementation("androidx.credentials:credentials:1.7.0-alpha02") + implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") } ``` @@ -34,8 +24,8 @@ dependencies { ```groovy dependencies { - implementation "androidx.credentials:credentials:1.7.0-alpha03" - implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03" + implementation "androidx.credentials:credentials:1.7.0-alpha02" + implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" } ``` diff --git a/identity/verified-email/references/android/identity/digital-credentials/email-verification.md b/identity/verified-email/references/android/identity/digital-credentials/email-verification.md new file mode 100644 index 0000000..2134577 --- /dev/null +++ b/identity/verified-email/references/android/identity/digital-credentials/email-verification.md @@ -0,0 +1,145 @@ +This document describes using Credential Manager to get a cryptographically +verified email address from a user's device. This process removes the need for +your app users to verify their email with one-time passwords (OTPs) or magic +links. + +This document explains the following areas: + +- Android compatibility +- User experience +- Accounts supported +- Implication of email deliverability +- Comparison with [Sign in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg) + +This guide assumes you are familiar with the following concepts: + +- [Credential Manager](https://developer.android.com/identity/credential-manager) +- [Digital Credentials](https://developer.android.com/identity/digital-credentials) +- [Verifiable Credentials](https://developer.android.com/identity/digital-credentials#verifiable-credentials) + +## Android compatibility + +This feature is supported on mobiles, tablets, and foldable devices running +Android 9 (API level 28) and higher. The minimum version of Google Play services +(GMS) required is 25.49.x. + +## User experience + +The following sections describe the user experience during the verification +flow, the need to include fallback verification methods, as well as the +recommended user experience for various use cases. + +### The verification flow + +The user experience for sharing a verified email is as follows: + +1. The user either focuses on an input field or taps a button that calls the + Credential Manager API. Depending on the design of the screen, you can also + call the API on your app's screen load. + +2. A bottom sheet appears, showing the information that will be shared with the + app. If no information is available on that device, the user sees a generic + error message. + +3. After the user taps **Agree and Continue**, display a success or failure + message. + + > [!NOTE] + > **Note:** If the verified email you receive does not match what you expect, inform the user about the mismatch and either ask them to try again with a different credential or provide an alternate verification method, such as through OTPs. + +4. (Optional, recommended) If the user is signing up for your service, you + should prompt the user to [create](https://developer.android.com/identity/passkeys/create-passkeys) a [passkey](https://developer.android.com/identity/passkeys) to make it easier for + them to sign in subsequently. + + > [!NOTE] + > **Note:** The email verification process doesn't automatically trigger passkey creation. However, it is highly recommended to include the steps for passkey creation. Passkeys help users by making it easier and more secure for them to sign in, and remove the need for the conventional username and password interaction. + +### Include primary and fallback flows + +To ensure a streamlined user experience, include the following options on +screens that require email verification: + +- **Primary verification option**: An email field or button to trigger the Credential Manager API flow for quick verification. +- **Alternate verification options**: A link or button for users to "Verify another way" or with "Other options" for manual email entry in case of failures, such as no information available on the device, or a mismatch between the retrieved and expected email. This should allow users to try verification with a different credential or by providing a manual OTP. + +### Use cases + +The following sections describe the recommended use cases, as well as the +suggested user experience, for email verification. + +#### Sign up + +Users can immediately create an account with a verified email without a separate +verification step. Optionally, prompt the user to add a passkey. If they opt to +add a passkey, trigger the [passkey creation](https://developer.android.com/identity/passkeys/create-passkeys) flow. +![Using email verification during sign up, and then creating passkeys](https://developer.android.com/static/identity/digital-credentials/images/signup_ux.png) Email verification during sign up + +#### Account recovery + +To eliminate the frustration of users searching for recovery codes in their spam +folders, allow them to recover their account using the verified email securely +stored on their device. Additionally, suggest that they create a passkey for +future use. +![Using email verification during account recovery](https://developer.android.com/static/identity/digital-credentials/images/account_recovery_ux.png) Email verification during account recovery + +#### Reauthentication for sensitive actions + +Protect sensitive user actions, such as changing settings or updating profile +details, by requiring a quick reauthentication step. +![Using email verification during reauthentication](https://developer.android.com/static/identity/digital-credentials/images/reauthentication_ux.png) Email verification during reauthentication + +## Accounts supported + +Email verification through Credential Manager only supports verification of +consumer Google Accounts. [Workspace accounts](https://knowledge.workspace.google.com/admin/getting-started/set-up-google-workspace-for-your-organization) and [supervised +accounts](https://support.google.com/families/answer/9499054) are not supported. + +A consumer Google Account can be created with an email address from any +provider, not necessarily @gmail.com. However, Google verifies these accounts +differently: + +- For @gmail.com accounts: Google is the authoritative source, and the email is known to be verified. +- For non-@gmail.com accounts: Google is not the authoritative source for these email addresses in the long term. While Google verifies the email when the account is created, the ownership of that email address might change over time. Therefore, for non-@gmail.com addresses, you should consider an additional verification step, such as sending an OTP, to ensure that the user still has access to the email account. + +For more information about what verification implies, see [Digital +Credentials](https://developer.android.com/identity/digital-credentials#verified). + +> [!NOTE] +> **Note:** Apart from a user's email information, you can request other unverified fields, such as the user's given name, family name, name, and the profile picture of their Google Account. However, only the email is verified by Google. +> For phone number verification, see [Verify phone numbers with digital credentials](https://developer.android.com/identity/digital-credentials/phone-number-verification). For a Firebase solution, see [Firebase Phone Number Verification](https://firebase.google.com/docs/phone-number-verification). Note that you can't verify a phone number and email in the same invocation as these are separate capabilities. + +## Validity and freshness + +The system issues [verifiable credentials](https://developer.android.com/identity/digital-credentials#verified) (VCs) based on the user's current +email from the active Google Accounts on the device. These credentials are +issued to the device in advance, typically while the device is idle. While these +credentials might remain valid for multiple days, the system performs a check at +the moment of sharing the credentials to ensure that the account still exists, +is on the device, and that the email address is valid---effectively prioritizing +the account's immediate status over the credential's validity window. + +To help ensure authenticity, a Key Binding (kb) signature is generated at the +time of sharing, incorporating the nonce. + +If a device is offline or the account is removed, the process fails rather than +providing an expired VC or a VC for an inactive Google Account. + +### Email deliverability + +While the process confirms the account's legitimacy, it does not guarantee inbox +delivery (for instance, the email might be diverted to spam). An OTP remains the +definitive method for confirming email deliverability. + +## Comparison with Sign in with Google + +While both Digital Credentials and [Sign in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg) solutions provide a +verified email, the user flows and use cases are different: + +- **Use cases**: The Credential Manager email verification flow is not exclusively used in sign up or sign in use cases, but rather can be used in any use case involving the retrieval of verified email. This could include account recovery as well. +- **Registration**: The Credential Manager flow does not require Google registration, unlike Sign in with Google. +- **Platform support**: The Credential Manager flow is an Android-only solution. +- **Scopes** : Unlike Sign in with Google, which can use OAuth 2.0 to request access to user data (such as Calendar or Drive through scopes), the Digital Credentials API is strictly for retrieving verified identity attributes. It cannot be used to request additional [authorization scopes](https://developers.google.com/identity/protocols/oauth2/scopes). + +## Next steps + +To implement this feature in your app, see the [Implementation guide](https://developer.android.com/identity/digital-credentials/email-verification-implementation). \ No newline at end of file diff --git a/identity/verified-email/references/android/identity/passkeys/create-passkeys.md b/identity/verified-email/references/android/identity/passkeys/create-passkeys.md index 089cc21..bfe19e5 100644 --- a/identity/verified-email/references/android/identity/passkeys/create-passkeys.md +++ b/identity/verified-email/references/android/identity/passkeys/create-passkeys.md @@ -44,8 +44,8 @@ Add the following dependencies to your app module's `build.gradle` file: ```kotlin dependencies { - implementation("androidx.credentials:credentials:1.7.0-alpha03") - implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03") + implementation("androidx.credentials:credentials:1.7.0-alpha02") + implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") } ``` @@ -53,8 +53,8 @@ dependencies { ```groovy dependencies { - implementation "androidx.credentials:credentials:1.7.0-alpha03" - implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03" + implementation "androidx.credentials:credentials:1.7.0-alpha02" + implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" } ``` diff --git a/jetpack-compose/adaptive/SKILL.md b/jetpack-compose/adaptive/SKILL.md index d78cc17..566374a 100644 --- a/jetpack-compose/adaptive/SKILL.md +++ b/jetpack-compose/adaptive/SKILL.md @@ -10,7 +10,7 @@ description: Instructions to make or update an app's UI so that it adapts to dif license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-30' + last-updated: '2026-07-02' keywords: - android - ui diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md index b7b2763..8b00b01 100644 --- a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md +++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md @@ -6,7 +6,7 @@ This page describes how to implement basic `FlexBox` layouts. `lib.versions.toml`. [versions] - compose = "1.12.0-rc01" + compose = "1.12.0-beta02" [libraries] androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" } diff --git a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md index b4d7eeb..bb78a97 100644 --- a/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md +++ b/jetpack-compose/adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md @@ -6,7 +6,7 @@ This page describes how to implement basic [`Grid`](https://developer.android.co `lib.versions.toml`. [versions] - compose = "1.12.0-rc01" + compose = "1.12.0-beta02" [libraries] androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" } diff --git a/performance/r8-analyzer/SKILL.md b/performance/r8-analyzer/SKILL.md index 027793d..2812549 100644 --- a/performance/r8-analyzer/SKILL.md +++ b/performance/r8-analyzer/SKILL.md @@ -7,7 +7,7 @@ description: Analyzes Android build files and R8 keep rules to identify redundan license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-08-01' + last-updated: '2026-07-18' keywords: - R8 - proguard @@ -20,40 +20,33 @@ metadata: - Inspect `build.gradle`, `build.gradle.kts`, and `gradle.properties`. - Use [references/CONFIGURATION.md](references/CONFIGURATION.md) to identify missing optimizations. -- **AGP** : If version is lower than 9.0, suggest migration to 9.0 for [build-time performance improvement](references/android/topic/performance/app-optimization/enable-app-optimization.md). +- **AGP** : If \< 9.0, suggest migration to 9.0 for [build time improvement + performance](references/android/topic/performance/app-optimization/enable-app-optimization.md) - **Full Mode** : Verify `android.enableR8.fullMode=false` is removed from gradle.properties. ## Step 2. Analysis path selection - Inspect `build.gradle`, `build.gradle.kts`, and `gradle.properties` and - `libs.versions.toml` to get the AGP and R8 versions. + `libs.versions.toml` to get the R8 version -- **If AGP \>= 9.3.0** : Proceed to **Path A (Standalone Task)**. +- **If R8 \>= 9.3.7-dev** : Proceed to **Path A (Quantitative)**. -- **If AGP \< 9.3.0 and R8 \>= 9.3.7-dev** : Proceed to **Path B (Quantitative)**. +- **If R8 \< 9.3.7-dev** : Proceed to **Path B (Heuristic)**. -- If none of the conditions are met, proceed to **Path C (Heuristic)**. +### Path A: Quantitative data generation (R8 \>= 9.3.7-dev) -### Path A: Standalone Gradle task (AGP \>= 9.3.0) +- **Check requirements** : Python and `protobuf` package are mandatory. +- **Generate and analyze** : You MUST run the shell commands described in [references/CONFIGURATION-ANALYZER.md](references/CONFIGURATION-ANALYZER.md) to generate the proto file using R8 configuration analyzer, convert it to json and analyze the result. +- **Report** : Rely entirely on the generated file `analysis.txt` for scores and rule impact metrics. Proceed to Step 3. -- **Step 1: Run standalone task** : Run `./gradlew :app:analyzeReleaseR8Config` to evaluate the R8 configuration. You MUST wait for this command to finish before proceeding. -- **Step 2: Convert to JSON** : The report is generated at `app/build/reports/r8/r8-config-analyzer-release.pb`. You MUST explicitly run the conversion script by executing: `python3 .agents/skills/r8-analyzer/scripts/convert_pb_to_json.py`. Wait for this command to finish. -- **Step 3: Analyze** : You MUST explicitly run the analysis script by executing: `python3 .agents/skills/r8-analyzer/scripts/analyze.py`. This outputs `tmp/keepradius/analysis_result.txt`. Wait for this command to finish. - -### Path B: Quantitative data generation (R8 \>= 9.3.7-dev and AGP \< 9.3.0) - -- **Step 1: Check requirements** : Python and `protobuf` package are mandatory. -- **Step 2: Generate and analyze** : You MUST run the shell commands described in [references/CONFIGURATION-ANALYZER.md](https://developer.android.com/agents/skills/performance/r8-analyzer/references/CONFIGURATION-ANALYZER) to generate the proto file using R8 configuration analyzer, convert it to JSON and analyze the result. -- **Step 3: Analyze** : You MUST ensure the analysis produces `tmp/keepradius/analysis_result.txt` for scores and rule impact metrics. - -### Path C: Heuristic evaluation and recommendation (R8 \< 9.3.7-dev) +### Path B: Heuristic evaluation and recommendation (R8 \< 9.3.7-dev) *(Use ONLY if quantitative data generation is not possible)* -- **Step 1: Manual evaluation** : Inspect `proguard-rules.pro`. -- **Step 2: Library check** : Compare rules against [references/REDUNDANT-RULES.md](references/REDUNDANT-RULES.md). Suggest **Remove** for bundled rules. -- **Step 3: Custom rule check** : Use [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md) and [references/REFLECTION-GUIDE.md](https://developer.android.com/agents/skills/performance/r8-analyzer/references/REFLECTION-GUIDE) to prioritize and evaluate. Suggest **Refine** for broad rules (for example, package-wide). -- **Step 4: Validation** : Suggest Macrobenchmark tests using [UI Automator](references/android/training/testing/other-components/ui-automator.md) for any proposed changes. Proceed to Step 3. +- **Manual evaluation** : Inspect `proguard-rules.pro`. +- **Library check** : Compare rules against [references/REDUNDANT-RULES.md](references/REDUNDANT-RULES.md). Suggest **Remove** for bundled rules. +- **Custom rule check** : Use [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md) and [references/REFLECTION-GUIDE.md](references/REFLECTION-GUIDE.md) to prioritize and evaluate. Suggest **Refine** for broad rules (for example, package-wide). +- **Validation** : Suggest Macrobenchmark tests using [UI Automator](references/android/training/testing/other-components/ui-automator.md) for any proposed changes. Proceed to Step 3. ## Step 3. Report generation diff --git a/performance/r8-analyzer/references/CONFIGURATION-ANALYZER.md b/performance/r8-analyzer/references/CONFIGURATION-ANALYZER.md new file mode 100644 index 0000000..4039dcf --- /dev/null +++ b/performance/r8-analyzer/references/CONFIGURATION-ANALYZER.md @@ -0,0 +1,287 @@ +On each step, keep the user informed of the progress by displaying the output. + +### 1. Requirements + +- R8 Version: 9.3.7-dev or later + +### 2. Generate proto + +The report and files must be generated at `{project_root}/tmp/r8analysis`. If +the folder is not present, create it. For example: + + mkdir -p "$PWD/tmp/r8analysis" + +### 3. Remove existing files + +To make sure that this invocation doesn't source data from previous runs, remove +the intermediate files `keepruleradius.json` and `analysis_result.txt` and +remove the proto files in the `{project_root}/tmp/r8analysis` folder. Example +bash commands: + + # Remove the intermediate JSON and the directory containing protobuf files + rm tmp/r8analysis/keepruleradius.json + rm tmp/r8analysis/*.pb + + # Copy the previous result to history before deleting the analysis + if [ -f tmp/r8analysis/analysis_result.txt ]; + then cat tmp/r8analysis/analysis_result.txt > tmp/r8analysis/history.txt && + rm tmp/r8analysis/analysis_result.txt; fi + +### 4. Generate the Configuration Analyzer report + +Run the R8 enabled build with the system property +"-Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis" to +generate Configuration Analyzer report + + ./gradlew assembleRelease \ + -Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis + +### 5. Convert to JSON + +To convert the generated protobuf files in `{project_root}/tmp/r8analysis` into +json, run the following script. The json must be generated in +`{project_root}/tmp/r8analysis`. Ensure `keep_radius_pb2.py` (from Step 10) is +in the same directory. + + import sys + import os + import glob + from google.protobuf import json_format + import keep_radius_pb2 + + def convert_pb_to_json(input_pb_path, output_json_path): + bundle = keep_radius_pb2.BlastRadiusContainer() + + try: + with open(input_pb_path, "rb") as pb_file: + binary_data = pb_file.read() + except Exception as e: + print(f"Error reading file {input_pb_path}: {e}", file=sys.stderr) + return False + + try: + bundle.ParseFromString(binary_data) + except Exception as e: + print(f"Error parsing protobuf: {e}", file=sys.stderr) + return False + + try: + json_string = json_format.MessageToJson( + bundle, + always_print_fields_with_no_presence=True, + preserving_proto_field_name=True, + indent=4 + ) + with open(output_json_path, "w", encoding="utf-8") as json_file: + json_file.write(json_string) + return True + except Exception as e: + print(f"Error writing JSON: {e}", file=sys.stderr) + return False + + if __name__ == "__main__": + input_pb = sys.argv[1] if len(sys.argv) > 1 else None + if not input_pb: + pb_files = glob.glob("tmp/r8analysis/*.pb") + if not pb_files: + print("Error: No .pb file found in tmp/r8analysis", file=sys.stderr) + sys.exit(1) + input_pb = sorted(pb_files)[-1] # Use the most recent one + output_json = sys.argv[2] if len(sys.argv) > 2 else "tmp/r8analysis/keepruleradius.json" + if not convert_pb_to_json(input_pb, output_json): + sys.exit(1) + +### 6. Analyze + +Run the following analysis script on the generated JSON to get the impact of the +keep rules and sort it. + + import json, sys + + def analyze(path): + try: + with open(path, 'r') as f: + d = json.load(f) + except Exception as e: + print(f"Error loading JSON: {e}") + return + + # Build reference map + c_map = {c.get('id'): set(c.get('constraints', [])) for c in d.get('keep_constraints_table', [])} + r_map = {r.get('id'): c_map.get(r.get('constraints_id'), set()) for r in d.get('keep_rule_blast_radius_table', [])} + + tot_opt = tot_obf = tot_shr = tot_items = 0 + + # Tally constraints across all kept items + for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table'): + for i in d.get(tbl, []): + tot_items += 1 + kb = i.get('kept_by', []) + if any('DONT_OPTIMIZE' in r_map.get(r, set()) for r in kb): tot_opt += 1 + if any('DONT_OBFUSCATE' in r_map.get(r, set()) for r in kb): tot_obf += 1 + if any('DONT_SHRINK' in r_map.get(r, set()) for r in kb): tot_shr += 1 + + # Find denominator + bi = d.get('build_info', {}) + live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count')) + denom = live if live > 0 else tot_items + + # Check for globals + globals_src = [g.get('source', '').lower() for g in d.get('global_keep_rule_blast_radius_table', [])] + def score(cnt, flag): + if any(flag in src for src in globals_src): return 0.0 + return max(0.0, 100.0 - ((cnt / denom * 100) if denom > 0 else 0)) + + result = [ + f"Optimization Score: {score(tot_opt, '-dontoptimize'):.2f}%", + f"Obfuscation Score: {score(tot_obf, '-dontobfuscate'):.2f}%", + f"Shrinking Score: {score(tot_shr, '-dontshrink'):.2f}%" + ] + for line in result: + print(line) + with open("tmp/r8analysis/analysis_result.txt", "w") as f: + f.write("\n".join(result)) + + if __name__ == "__main__": + path = sys.argv[1] if len(sys.argv) > 1 else "tmp/r8analysis/keepruleradius.json" + analyze(path) + +Outputs `analysis_result.txt` containing scores and rule impacts. + +### 7. Report impactful rules + +Identify the keep rules with the highest impact and the subsumed rules using the +following script. + + import json, sys + + def report(path): + try: + with open(path, 'r') as f: + data = json.load(f) + except Exception as e: + print(f"Error loading JSON: {e}") + return + + # Calculate denominator for percentage + bi = data.get('build_info', {}) + live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count')) + denom = live if live > 0 else sum(len(data.get(tbl, [])) for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table')) + + processed = [] + for r in data.get('keep_rule_blast_radius_table', []): + br = r.get('blast_radius', {}) + c, f, m = len(br.get('class_blast_radius', [])), len(br.get('field_blast_radius', [])), len(br.get('method_blast_radius', [])) + impact = c + f + m + if impact == 0: + continue + impact_pct = (impact / denom * 100) if denom > 0 else 0.0 + processed.append({ + 'id': r.get('id'), + 'source': r.get('source'), + 'impact': impact, + 'impact_pct': f"{impact_pct:.2f}%", + 'classes': c, + 'fields': f, + 'methods': m, + 'subsumed_by': br.get('subsumed_by', []) + }) + + processed.sort(key=lambda x: x['impact'], reverse=True) + + # Output JSON for the agent to fetch and process + print(json.dumps({ + "top_5_impact_keep_rules": [r for r in processed if not r['subsumed_by']][:5], + "subsumed": [r for r in processed if r['subsumed_by']] + }, indent=2)) + + if __name__ == "__main__": + report("tmp/r8analysis/keepruleradius.json") + +Add this data to the `analysis_result.txt` with the top impactful rules and +subsumed rules. + +### 8. Compare with previous report + +If `{project_root}/tmp/r8analysis/history.txt` exists, use the following script +to compare the previous run. Use this to compare with the current values + +### 9. Remove generated files + +After the final report and analysis results are generated, remove the +intermediate files `keepruleradius.json` and `analysis_result.txt` and remove +the proto files in "{project_root}/tmp/r8analysis" folder + + rm tmp/r8analysis/keepruleradius.json + rm tmp/r8analysis/*.pb + +### 10. Protobuf Python bindings + +The following script `keep_radius_pb2.py` is required by the conversion script +in Step 5. + + from google.protobuf import descriptor as _descriptor + from google.protobuf import descriptor_pool as _descriptor_pool + from google.protobuf import runtime_version as _runtime_version + from google.protobuf import symbol_database as _symbol_database + from google.protobuf.internal import builder as _builder + _runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 4, + '', + 'keep_radius.proto' + ) + # @@protoc_insertion_point(imports) + + _sym_db = _symbol_database.Default() + DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11keep_radius.proto\x12&com.android.tools.r8.blastradius.proto\"\x9f\x02\n\x13KeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x16\n\x0e\x63onstraints_id\x18\x03 \x01(\x05\x12\x46\n\x06origin\x18\x04 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\x12I\n\x0c\x62last_radius\x18\x05 \x01(\x0b\x32\x33.com.android.tools.r8.blastradius.proto.BlastRadius\x12\x41\n\x04tags\x18\x06 \x03(\x0e\x32\x33.com.android.tools.r8.blastradius.proto.KeepRuleTag\"w\n\x0b\x42lastRadius\x12\x13\n\x0bsubsumed_by\x18\x01 \x03(\x05\x12\x1a\n\x12\x63lass_blast_radius\x18\x02 \x03(\x05\x12\x1a\n\x12\x66ield_blast_radius\x18\x03 \x03(\x05\x12\x1b\n\x13method_blast_radius\x18\x04 \x03(\x05\"\x7f\n\x19GlobalKeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x46\n\x06origin\x18\x03 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\"j\n\x0fKeepConstraints\x12\n\n\x02id\x18\x01 \x01(\x05\x12K\n\x0b\x63onstraints\x18\x02 \x03(\x0e\x32\x36.com.android.tools.r8.blastradius.proto.KeepConstraint\"`\n\rKeptClassInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"`\n\rKeptFieldInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x66ield_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"b\n\x0eKeptMethodInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1b\n\x13method_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"a\n\x0e\x46ieldReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x19\n\x11type_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"c\n\x0fMethodReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x1a\n\x12proto_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"K\n\x0eProtoReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x15\n\rparameters_id\x18\x02 \x01(\x05\x12\x16\n\x0ereturn_type_id\x18\x03 \x01(\x05\"4\n\rTypeReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x17\n\x0fjava_descriptor\x18\x02 \x01(\t\";\n\x11TypeReferenceList\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12type_reference_ids\x18\x02 \x03(\x05\"\x9f\x01\n\nFileOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08\x66ilename\x18\x02 \x01(\t\x12Q\n\x10maven_coordinate\x18\x03 \x01(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12 \n\x18provided_by_build_system\x18\x04 \x01(\x08\"I\n\x14\x43lassFileInJarOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x02 \x01(\x05\x12\r\n\x05\x65ntry\x18\x03 \x01(\t\"T\n\x0eTextFileOrigin\x12\x16\n\x0e\x66ile_origin_id\x18\x01 \x01(\x05\x12\x13\n\x0bline_number\x18\x02 \x01(\x05\x12\x15\n\rcolumn_number\x18\x03 \x01(\x05\"U\n\x0fMavenCoordinate\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\"\x9a\x01\n\tBuildInfo\x12\x13\n\x0b\x63lass_count\x18\x01 \x01(\x05\x12\x13\n\x0b\x66ield_count\x18\x02 \x01(\x05\x12\x14\n\x0cmethod_count\x18\x03 \x01(\x05\x12\x18\n\x10live_class_count\x18\x04 \x01(\x05\x12\x18\n\x10live_field_count\x18\x05 \x01(\x05\x12\x19\n\x11live_method_count\x18\x06 \x01(\x05\"\xd5\n\n\x14\x42lastRadiusContainer\x12M\n\x11\x66ile_origin_table\x18\x01 \x03(\x0b\x32\x32.com.android.tools.r8.blastradius.proto.FileOrigin\x12\x64\n\x1e\x63lass_file_in_jar_origin_table\x18\x02 \x03(\x0b\x32<.com.android.tools.r8.blastradius.proto.ClassFileInJarOrigin\x12W\n\x16maven_coordinate_table\x18\x03 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12U\n\x15\x66ield_reference_table\x18\x04 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.FieldReference\x12W\n\x16method_reference_table\x18\x05 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MethodReference\x12U\n\x15proto_reference_table\x18\x06 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.ProtoReference\x12S\n\x14type_reference_table\x18\x07 \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.TypeReference\x12\\\n\x19type_reference_list_table\x18\x08 \x03(\x0b\x32\x39.com.android.tools.r8.blastradius.proto.TypeReferenceList\x12T\n\x15kept_class_info_table\x18\t \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptClassInfo\x12T\n\x15kept_field_info_table\x18\n \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptFieldInfo\x12V\n\x16kept_method_info_table\x18\x0b \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.KeptMethodInfo\x12W\n\x16keep_constraints_table\x18\x0c \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.KeepConstraints\x12\x61\n\x1ckeep_rule_blast_radius_table\x18\r \x03(\x0b\x32;.com.android.tools.r8.blastradius.proto.KeepRuleBlastRadius\x12n\n#global_keep_rule_blast_radius_table\x18\x0e \x03(\x0b\x32\x41.com.android.tools.r8.blastradius.proto.GlobalKeepRuleBlastRadius\x12\x45\n\nbuild_info\x18\x0f \x01(\x0b\x32\x31.com.android.tools.r8.blastradius.proto.BuildInfo*\x1f\n\x0bKeepRuleTag\x12\x10\n\x0cPACKAGE_WIDE\x10\x00*H\n\x0eKeepConstraint\x12\x12\n\x0e\x44ONT_OBFUSCATE\x10\x00\x12\x11\n\rDONT_OPTIMIZE\x10\x01\x12\x0f\n\x0b\x44ONT_SHRINK\x10\x02\x42\x45\n&com.android.tools.r8.blastradius.protoB\x19KeepRuleBlastRadiusProtosP\001\x62\x06proto3') + + _globals = globals() + _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) + _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'keep_radius_pb2', _globals) + if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n&com.android.tools.r8.blastradius.protoB\031KeepRuleBlastRadiusProtosP\001' + _globals['_KEEPRULETAG']._serialized_start = 3332 + _globals['_KEEPRULETAG']._serialized_end = 3363 + _globals['_KEEPCONSTRAINT']._serialized_start = 3365 + _globals['_KEEPCONSTRAINT']._serialized_end = 3437 + _globals['_KEEPRULEBLASTRADIUS']._serialized_start = 62 + _globals['_KEEPRULEBLASTRADIUS']._serialized_end = 349 + _globals['_BLASTRADIUS']._serialized_start = 351 + _globals['_BLASTRADIUS']._serialized_end = 470 + _globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_start = 472 + _globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_end = 599 + _globals['_KEEPCONSTRAINTS']._serialized_start = 601 + _globals['_KEEPCONSTRAINTS']._serialized_end = 707 + _globals['_KEPTCLASSINFO']._serialized_start = 709 + _globals['_KEPTCLASSINFO']._serialized_end = 805 + _globals['_KEPTFIELDINFO']._serialized_start = 807 + _globals['_KEPTFIELDINFO']._serialized_end = 903 + _globals['_KEPTMETHODINFO']._serialized_start = 905 + _globals['_KEPTMETHODINFO']._serialized_end = 1003 + _globals['_FIELDREFERENCE']._serialized_start = 1005 + _globals['_FIELDREFERENCE']._serialized_end = 1102 + _globals['_METHODREFERENCE']._serialized_start = 1104 + _globals['_METHODREFERENCE']._serialized_end = 1203 + _globals['_PROTOREFERENCE']._serialized_start = 1205 + _globals['_PROTOREFERENCE']._serialized_end = 1280 + _globals['_TYPEREFERENCE']._serialized_start = 1282 + _globals['_TYPEREFERENCE']._serialized_end = 1334 + _globals['_TYPEREFERENCELIST']._serialized_start = 1336 + _globals['_TYPEREFERENCELIST']._serialized_end = 1395 + _globals['_FILEORIGIN']._serialized_start = 1398 + _globals['_FILEORIGIN']._serialized_end = 1557 + _globals['_CLASSFILEINJARORIGIN']._serialized_start = 1559 + _globals['_CLASSFILEINJARORIGIN']._serialized_end = 1632 + _globals['_TEXTFILEORIGIN']._serialized_start = 1634 + _globals['_TEXTFILEORIGIN']._serialized_end = 1718 + _globals['_MAVENCOORDINATE']._serialized_start = 1720 + _globals['_MAVENCOORDINATE']._serialized_end = 1805 + _globals['_BUILDINFO']._serialized_start = 1808 + _globals['_BUILDINFO']._serialized_end = 1962 + _globals['_BLASTRADIUSCONTAINER']._serialized_start = 1965 + _globals['_BLASTRADIUSCONTAINER']._serialized_end = 3330 + # @@protoc_insertion_point(module_scope) \ No newline at end of file diff --git a/performance/r8-analyzer/references/REFLECTION-GUIDE.md b/performance/r8-analyzer/references/REFLECTION-GUIDE.md new file mode 100644 index 0000000..8f23679 --- /dev/null +++ b/performance/r8-analyzer/references/REFLECTION-GUIDE.md @@ -0,0 +1,139 @@ +A categorized summary of the keep rule examples, including the code patterns to +look for (imports/usage) and the corresponding suggested rules. + +### 1. Reflection: Classes Loaded by Name + +**Scenario:** A library or app loads a class dynamically using a string name + +- **Look for:** + `Class.forName("...")`, + `getDeclaredConstructor().newInstance()`, or interfaces used for dynamic loading. + +- **Example Code:** + `kotlin + val taskClass = Class.forName(className) + val task = taskClass.getDeclaredConstructor().newInstance() as StartupTask` + +- **Suggested Keep Rule:** + \`\`\`proguard + + -keep class \* implements com.example.library.StartupTask { + (); } \`\`\` + +### 2. Reflection: Classes Passed using `::class.java` + +**Scenario:** An app passes a class reference directly to a library function. + +- **Look for:** `::class.java` (Kotlin) or `.class` (Java) passed as an argument. +- **Example Code:** + `kotlin + fun register(clazz: Class) { } + // Usage: + register(MyService::class.java)` + +- **Suggested Keep Rule:** + \`\`\`proguard + + # Keep the class itself (R8 usually handles this, but explicit rules ensure stability) + + -keep class com.example.app.MyService { + (); } \`\`\` + +### 3. Annotation-Based Reflection (Methods/Classes) + +**Scenario:** Using custom annotations to mark methods or classes for reflective +execution. + +**Look for:** Custom `@interface` definitions and `getDeclaredMethods()` +filtered by annotation. +**Example Code:** +`kotlin +annotation class ReflectiveExecutor +// Logic: find methods annotated with @ReflectiveExecutor and invoke them` + +- **Suggested Keep Rule:** \`\`\`proguard # Keep the annotation itself -keep @interface com.example.library.ReflectiveExecutor + +# Keep members of any class annotated with this specific annotation +-keepclassmembers class \* { +@com.example.library.ReflectiveExecutor \*; +} +\`\`\` + +### 4. Optional Dependencies (Soft Dependencies) + +**Scenario:** A core library checks if an optional module is present in the +classpath. + +- **Look for:** `try-catch` blocks around `Class.forName()` used to toggle features. +- **Example Code:** \`\`\`kotlin private const val VIDEO_TRACKER_CLASS = "com.example.analytics.video.VideoEventTracker" + +try { +Class.forName(VIDEO_TRACKER_CLASS).getDeclaredConstructor().newInstance() +} catch (e: ClassNotFoundException) { /\* skip feature \*/ } +\`\`\` + +- **Suggested Keep Rule:** `proguard + # Preserve the optional class so the check doesn't fail due to shrinking + -keep class com.example.analytics.video.VideoEventTracker { + (); + }` + +### 5. Accessing Private Members + +**Scenario:** Using reflection to access internal fields or methods not exposed +with public APIs. + +- **Look for:** `getDeclaredField("...")` or `getDeclaredMethod("...")` followed by `isAccessible = true`. +- **Example Code:** + `kotlin + val secretField = instance::class.java.getDeclaredField("secretMessage") + secretField.isAccessible = true` + +- **Suggested Keep Rule:** + \`\`\`proguard + + # Specifically keep the private field/method by name and type + + -keepclassmembers class com.example.LibraryClass { + private java.lang.String secretMessage; + } + \`\`\` + +### 6. Parcelable (Manual Implementation) + +**Scenario:** Implementing `Parcelable` without using the `@Parcelize` +annotation. + +- **Look for:** `implements Parcelable` and a static `CREATOR` field. +- **Example Code:** + `kotlin + class MyData : Parcelable { + // Manual implementation with CREATOR field + }` + +- **Suggested Keep Rule:** + *(Note: If using `import kotlinx.parcelize.Parcelize`, R8/ProGuard rules are + generated automatically. If manual, use the following:)* + `proguard + -keepclassmembers class * implements android.os.Parcelable { + static android.os.Parcelable$Creator CREATOR; + }` + +### 7. Enums and Obfuscation + +**Scenario:** App uses `Enum.valueOf("STRING_NAME")` indirectly (e.g.,using JSON +deserialization) and the enum names get obfuscated. + +- **Look for:** Unnecessary generic Enum keep rules in ProGuard files. +- **Example Code:** + \`\`\`proguard + + # Unnecessary rule + + -keepclassmembers enum \* { \*; } + \`\`\` +- **Suggested Keep Rule:** + \*(Note: The default `proguard-android-optimize.txt` already contains the optimal + rules for Enums (keeping `values()` and `valueOf(String)`). Any additional + manual rules for Enums are redundant.) # No manual rule needed. Use default + proguard-android-optimize.txt. \ No newline at end of file diff --git a/play/engage-sdk-integration/SKILL.md b/play/engage-sdk-integration/SKILL.md index d17590e..0493cd7 100644 --- a/play/engage-sdk-integration/SKILL.md +++ b/play/engage-sdk-integration/SKILL.md @@ -6,7 +6,7 @@ description: Helps developers integrate, debug, and resolve Play Engage SDK impl license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-31' + last-updated: '2026-07-18' keywords: - android - engage @@ -29,7 +29,7 @@ Follow these steps to assist the developer: - Check if the integration is for TV or mobile. If the integration is for TV, read the TV-specific sections in [patterns.md](references/patterns.md) as well. - Use `{VERTICAL}.md` in the **[references/schemas/](references/schemas)** directory to identify the corresponding Engage entities and the `client` class name. The `client` field in the JSON provides the full class name. For example, `com.google.android.engage.food.service.AppEngageFoodClient`. - **Note:** Initializing the client class requires a `Context` parameter. For example, `AppEngageFoodClient(context)`. - - Always refer to [common.md](https://developer.android.com/agents/skills/play/engage-sdk-integration/references/common) for common entities. + - Always refer to [common.md](references/common.md) for common entities. - Ask which cluster type they want to publish from the supported cluster types for that vertical. - Find the method to call from `{VERTICAL}.md` in the **[references/schemas/](references/schemas)** directory for the specified cluster. Each method will specify the request it expects. - Get the request structure from [requests.md](references/requests.md) and clusters from [clusters.md](references/clusters.md). Then suggest and use sources to fill the fields in the request structure correctly, along with the required entities and clusters. @@ -69,7 +69,7 @@ Follow these steps to assist the developer: - Perform a Gradle sync. - If errors occur, follow this resolution order: - - Fix import errors. For package `com.google.android.engage` or classes starting with `AppEngage`, verify the package name in the `{VERTICAL}.md` in **[references/schemas/](references/schemas)** directory or [common.md](https://developer.android.com/agents/skills/play/engage-sdk-integration/references/common). + - Fix import errors. For package `com.google.android.engage` or classes starting with `AppEngage`, verify the package name in the `{VERTICAL}.md` in **[references/schemas/](references/schemas)** directory or [common.md](references/common.md). - Fix any other errors. - Execute a full Gradle build and resolve any remaining compilation issues. Repeat this step until the Gradle build is successful. 7. **User checklist:** At the end of code generation, notify the user to go @@ -102,7 +102,7 @@ Follow these steps to assist the developer: - [Listen Vertical](references/android/guide/playcore/engage/listen.md) - [Read Vertical](references/android/guide/playcore/engage/read.md) - [Shopping Vertical](references/android/guide/playcore/engage/shopping.md) - - [Social Vertical](references/android/guide/playcore/engage/social.md) + - [Social Vertical](https://developer.android.com/guide/playcore/engage/social) - [Travel Vertical](references/android/guide/playcore/engage/travel.md) - [Health and Fitness Vertical](references/android/guide/playcore/engage/healthandfitness.md) - [Other Verticals](references/android/guide/playcore/engage/otherverticals.md) diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md deleted file mode 100644 index b1e8f78..0000000 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/social.md +++ /dev/null @@ -1,702 +0,0 @@ -Boost app engagement by reaching your users where they are. Integrate Engage SDK -to deliver personalized recommendations and continuation content directly to -users across multiple on-device surfaces, like -**[Collections](https://android-developers.googleblog.com/2024/07/introducing-collections-powered-by-engage-sdk.html)** , **[Entertainment -Space](https://blog.google/products/android/entertainment-space/)** , and the Play Store. The integration adds -less than 50 KB (compressed) to the average APK and takes most apps about a -week of developer time. Learn more at our **[business -site](http://play.google.com/console/about/programs/EngageSDK)**. - -This guide contains instructions for developer partners to deliver social media -content to Engage content surfaces. - -## Integration detail - -The following section captures the integration detail. - -### Terminology - -***Recommendation*** clusters show personalized suggestions from an individual -developer partner. - -Your recommendations take the following structure: - -**Recommendation Cluster**: UI view that contains a group of recommendations -from the same developer partner. - -Each Recommendation Cluster consists of one of the following two types of -entities : - -- PortraitMediaEntity -- SocialPostEntity - -**PortraitMediaEntity** must contain 1 portrait image for the post. Profile and -Interaction related metadata are optional. - -- Post - - - Image in portrait mode and Timestamp, or - - Image in portrait mode + text content and Timestamp -- Profile - - - Avatar, Name or Handle, Additional image -- Interactions - - - Count and label only, or - - Count and visual (icon) - -**SocialPostEntity** contains profile, post and interaction related metadata. - -- Profile - - - Avatar, Name or Handle, additional text, additional image -- Post - - - Text and Timestamp, or - - Rich media (image or rich URL) and Timestamp, or - - Text and rich media (image or rich URL) and Timestamp, or - - Video preview (thumbnail and duration) and Timestamp -- Interactions - - - Count \& label only, or - - Count \& visual (icon) - -### Pre-work - -Minimum API level: 19 - -Add the `com.google.android.engage:engage-core` library to your app: - - dependencies { - // Make sure you also include that repository in your project's build.gradle file. - implementation 'com.google.android.engage:engage-core:1.6.0' - } - -### Summary - -The design is based on an implementation of a [bound -service](https://developer.android.com/guide/components/bound-services). - -The data a client can publish is subject to the following limits for different -cluster types: - -| Cluster type | Cluster limits | Minimum entity limits in a cluster | Maximum entity limits in a cluster | -|---|---|---|---| -| Recommendation Cluster(s) | At most 7 | At least 1 (`PortraitMediaEntity`, or `SocialPostEntity`) | At most 50 (`PortraitMediaEntity`, or `SocialPostEntity`) | - -### Step 1: Provide entity data - -The SDK has defined different entities to represent each item type. The SDK -supports the following entities for the Social category: - -1. `PortraitMediaEntity` -2. `SocialPostEntity` - -The charts below outline available attributes and requirements for each type. - -#### `PortraitMediaEntity` - -| Attribute | Requirement | Description | Format | -|---|---|---|---| -| Action URI | **Required** for all surfaces other than Google TV | Deep Link to the entity in the provider app. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | URI | -| PlatformSpecificPlayback | **Required** for Google TV surface | Deep Link to the entity in the provider app for platforms like Google TV and Mobile. | List of PlatformSpecificPlayback objects | -| Recommendation Reason | Optional | The justification for recommending the content to the user. | RecommendationReason object | -| Comments Summary | Optional | Summary of comments for the post. | String | -| **Post related metadata (Required)** |||| -| Image(s) | Required | Image(s) should be in **portrait aspect ratio.** The UI may show only 1 image when multiple images are provided. However, the UI may provide visual indication that there are more images in the app. *If the post is a video, the provider should provide a thumbnail of the video to be shown as an image.* | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| Text content | Optional | The main text of a post, update, etc. | String (recommended max 140 chars) | -| Timestamp | Optional | Time when the post was published. | Epoch timestamp in milliseconds | -| Is video content | Optional | Is the post a video? | boolean | -| Video duration | Optional | The duration of the video in milliseconds. | Long | -| **Profile related metadata (Optional)** |||| -| Name | Required | Profile name or id or handle, eg "John Doe", "@TeamPixel" | String(recommended max 25 chars) | -| Avatar | Required | Profile picture or avatar image of the user. **Square 1:1 image** | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| Additional Image | Optional | Profile badge. for example - verified badge **Square 1:1 image** | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| **Interactions related metadata (Optional)** |||| -| Count | Optional | Indicate the number of interactions, for example - "3.7 M.". **Note:** If both Count and Count Value are provided, Count will be used. **Note:** Partners should use either **Count** or **CountWithOptionalLabel**. | String | -| CountWithOptionalLabel | Optional | Indicate the number of interactions with an optional label, for example - "3.7 M Likes.". **Note:** If both CountWithOptionalLabel and Count Value are provided, one of them will be used. **Note:** Partners should use either **Count** or **CountWithOptionalLabel**. | String | -| Count Value | Optional | The number of interactions as a value. **Note:** Provide Count Value instead of Count if your app doesn't handle logic on how a large number should be optimized for different display sizes. If both Count and Count Value are provided, Count is used. | Long | -| Label | Optional | Indicate what the interaction label is for. For example - "Likes". | String | -| Visual | Optional | Indicate what the interaction is for. For example - Image showing Likes icon, emoji. Can provide more than 1 image, though not all may not be shown on all form factors. **Note:** Must be Square 1:1 image | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| **DisplayTimeWindow (Optional) - Set a time window for a content to be shown on the surface** |||| -| Start Timestamp | Optional | The epoch timestamp after which the content should be shown on the surface. If not set, content is eligible to be shown on the surface. | Epoch timestamp in milliseconds | -| End Timestamp | Optional | The epoch timestamp after which the content is no longer shown on the surface. If not set, content is eligible to be shown on the surface. | Epoch timestamp in milliseconds | - -#### `SocialPostEntity` - -| Attribute | Requirement | Description | Format | -|---|---|---|---| -| Action URI | **Required** | Deep Link to the entity in the provider app. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | URI | -| PlatformSpecificPlayback URIs | **Required** for Google TV surface | Deep Link to the entity in the provider app for platforms like Google TV and Mobile. | List of PlatformSpecificPlayback objects | -| Recommendation Reason | Optional | The justification for recommending the content to the user. | RecommendationReason object | -| Comments Summary | Optional | Summary of comments for the post. | String | -| **Post related metadata (Required)** At least one of TextContent, Image or WebContent is required |||| -| Image(s) | Optional | Image(s) should be in **portrait aspect ratio.** The UI may show only 1 image when multiple images are provided. However, the UI may provide visual indication that there are more images in the app. *If the post is a video, the provider should provide a thumbnail of the video to be shown as an image.* | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| Text content | Optional | The main text of a post, update, etc. | String (recommended max 140 chars) | -| **Video Content (Optional)** |||| -| Duration | Required | The duration of the video in milliseconds. | Long | -| Image | Required | Preview image of the video content. | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| **Link Preview (Optional)** |||| -| Link Preview - Title | Required | Text to indicate the title of the web page content | String | -| Link Preview - Hostname | Required | Text to indicate the web page owner, eg "INSIDER" | String | -| Link Preview - Image | Optional | Hero image for the web content | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| Timestamp | Optional | Time when the post was published. | Epoch timestamp in milliseconds | -| **Profile related metadata (Optional)** |||| -| Name | Required | Profile name or id or handle, eg "John Doe", "@TeamPixel." | String(recommended max 25 chars) | -| Additional Text | Optional | Could be used as profile id or handle or additional metadata For example "@John-Doe", "5M followers", "You might like", "Trending", "5 new posts" | String(recommended max 40 chars) | -| Avatar | Required | Profile picture or avatar image of the user. **Square 1:1 image** | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| Additional Image | Optional | Profile badge, for example - verified badge **Square 1:1 image** | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| **Interactions related metadata (Optional)** |||| -| Count | Required | Indicate the number of interactions, for example - "3.7 M." **Note:** Partners should use either **Count** or **CountWithOptionalLabel**. | String | -| CountWithOptionalLabel | Required | Indicate the number of interactions with an optional label, for example - "3.7 M Likes." **Note:** Partners should use either **Count** or **CountWithOptionalLabel**. | String | -| Label | Optional If not provided, **Visual** must be provided. | Indicate what the interaction is for. For example - "Likes." | String (recommended max 20 chars for count + label combined) | -| Visual | Optional If not provided, **Label** must be provided. | Indicate what the interaction is for. For example - Image showing Likes icon, emoji. Can provide more than 1 image, though not all may not be shown on all form factors. **Square 1:1 image** | See [Image Specifications](https://developer.android.com/guide/playcore/engage/social#image-specs) for guidance. | -| **DisplayTimeWindow (Optional) - Set a time window for a content to be shown on the surface** |||| -| Start Timestamp | Optional | The epoch timestamp after which the content should be shown on the surface. If not set, content is eligible to be shown on the surface. | Epoch timestamp in milliseconds | -| End Timestamp | Optional | The epoch timestamp after which the content is no longer shown on the surface. If not set, content is eligible to be shown on the surface. | Epoch timestamp in milliseconds | - -#### Image specifications - -The images are required to be hosted on public CDNs so that Google can access -them. - -*File formats* - -PNG, JPG, static GIF, WebP - -*Maximum file size* - -5120 KB - -*Additional recommendations* - -- **Image safe area:** Put your important content in the center 80% of the image. -- Use a transparent background so that the image can be properly displayed in Dark and Light theme settings. - -### Step 2: Provide Cluster data - -It is recommended to have the content publish job executed in the background -(for example, using [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)) -and scheduled on a regular basis or on an event basis (for example, every time -the user opens the app or when the user just followed a new account) - -`AppEngageSocialClient` is responsible for publishing social clusters. - -There are following APIs to publish clusters in the client: - -- `isServiceAvailable` -- `publishRecommendationClusters` -- `publishUserAccountManagementRequest` -- `updatePublishStatus` -- `deleteRecommendationsClusters` -- `deleteUserManagementCluster` -- `deleteClusters` - -#### `isServiceAvailable` - -This API is used to check if the service is available for integration and -whether the content can be presented on the device. - -##### For Engage SDK v1.6.0 and higher (Recommended) - -You can check the service availability for every cluster type that you intend to -publish. The `isServiceAvailable` API accepts a request object, -`ServiceAvailabilityRequest`, which contains the cluster types for which service -availability needs to be checked. You can find the `ClusterType` enum values -required for `ServiceAvailabilityRequest` from the following table. - -| Cluster Type | Cluster Type Constant | Integer Value | -|---|---|---| -| Unknown | `TYPE_UNKNOWN` | 0 | -| Recommendation Cluster | `TYPE_RECOMMENDATION` | 1 | -| Featured Cluster | `TYPE_FEATURED` | 2 | -| Continuation Cluster | `TYPE_CONTINUATION` | 3 | -| User Management Cluster | `TYPE_ENGAGEMENT` | 8 | -| Subscription Cluster | `TYPE_SUBSCRIPTION` | 12 | - -### Kotlin - - val request = ServiceAvailabilityRequest.Builder() - .addIntendedClusterType(ClusterType.TYPE_CONTINUATION) - .addIntendedClusterType(ClusterType.TYPE_RECOMMENDATION) - .build() - - client.isServiceAvailable(request).addOnCompleteListener { task -> - if (task.isSuccessful) { - val availabilityMap = task.result - if (availabilityMap[ClusterType.TYPE_CONTINUATION] == true) { - // Proceed with publishing continuation content - } - if (availabilityMap[ClusterType.TYPE_RECOMMENDATION] == true) { - // Proceed with publishing recommendation content - } - } else { - // The IPC call itself fails, proceed with error handling logic here, - // such as retry. - } - } - -### Java - - ServiceAvailabilityRequest request = - new ServiceAvailabilityRequest.Builder() - .addIntendedClusterType(ClusterType.TYPE_CONTINUATION) - .addIntendedClusterType(ClusterType.TYPE_RECOMMENDATION) - .build(); - - client.isServiceAvailable(request).addOnCompleteListener(task -> { - if (task.isSuccessful()) { - Map availabilityMap = task.getResult(); - if (Boolean.TRUE.equals(availabilityMap.get(ClusterType.TYPE_CONTINUATION))) { - // Proceed with publishing continuation content - } - if (Boolean.TRUE.equals(availabilityMap.get(ClusterType.TYPE_RECOMMENDATION))) { - // Proceed with publishing recommendation content - } - } else { - // The IPC call itself fails, proceed with error handling logic here, - // such as retry. - } - }); - -###### Conditional Service Availability Feature - -Some integrated apps request a special configuration that enables and disables -the Engage service intermittently in order to reduce their serving cost. This -intermittent content ingestion strategy, although possible, negatively affects -the user and the product -- stale content will not be presented and some surfaces -will not be served at all. - -Starting with v1.6.0, the Engage SDK allows checking availability for specific -cluster types. If you are interested in opting into this feature for any cluster type, -please contact engage-developers@google.com. - -##### For SDK versions prior to v1.6.0 (Deprecated) - -### Kotlin - - client.isServiceAvailable.addOnCompleteListener { task -> - if (task.isSuccessful) { - // Handle IPC call success - if(task.result) { - // Service is available on the device, proceed with content publish - // calls. - } else { - // Service is not available, no further action is needed. - } - } else { - // The IPC call itself fails, proceed with error handling logic here, - // such as retry. - } - } - -### Java - - client.isServiceAvailable().addOnCompleteListener(task - > { - if (task.isSuccessful()) { - // Handle success - if(task.getResult()) { - // Service is available on the device, proceed with content publish - // calls. - } else { - // Service is not available, no further action is needed. - } - } else { - // The IPC call itself fails, proceed with error handling logic here, - // such as retry. - } - }); - -> [!NOTE] -> **Note:** We highly recommend keeping a periodic job running to check if the service becomes available at a later point in time. The availability of the service may change with Android version upgrades, app upgrades, installs, and uninstalls. By ensuring periodic job checks at a certain time interval, data can be published once the service becomes available. - -#### `publishRecommendationClusters` - -This API is used to publish a list `RecommendationCluster` objects. - -A `RecommendationCluster` object can have the following attributes: - -| Attribute | Requirement | Description | -|---|---|---| -| List of SocialPostEntity, or PortraitMediaEntity | **Required** | A list of entities that make up the recommendations for this Recommendation Cluster. Entities in a single cluster must be of the same type. | -| Title | **Required** | The title for the Recommendation Cluster (for example, *Latest from your friends*). **Recommended text size: under 25 chars** (Text that is too long may show ellipses) | -| Subtitle | Optional | The subtitle for the Recommendation Cluster. | -| Action Uri | Optional | The deep link to the page in the partner app where users can see the complete list of recommendations. Note: You can use deep links for attribution. [Refer to this FAQ](https://developer.android.com/guide/playcore/engage/faq#deeplinks-attribution) | - -> [!IMPORTANT] -> **Important:** The publish APIs are upsert APIs; it replaces the existing content. **Don't** call delete and publish APIs subsequently to replace the content as the publish APIs do that inherently. - -> [!IMPORTANT] -> **Important:** For social apps, it's critical to update recommendations after each app usage. Social app users are more interested in the most recent recommendations and ideally would like to see a post at most once. - -### Kotlin - - client.publishRecommendationClusters( - PublishRecommendationClustersRequest.Builder() - .addRecommendationCluster( - RecommendationCluster.Builder() - .addEntity(entity1) - .addEntity(entity2) - .setTitle("Latest from your friends") - .build()) - .build()) - -### Java - - client.publishRecommendationClusters( - new PublishRecommendationClustersRequest.Builder() - .addRecommendationCluster( - new RecommendationCluster.Builder() - .addEntity(entity1) - .addEntity(entity2) - .setTitle("Latest from your friends") - .build()) - .build()); - -When the service receives the request, the following actions take place within -one transaction: - -- All existing Recommendation Cluster data is removed. -- Data from the request is parsed and stored in new Recommendation Clusters. - -In case of an error, the entire request is rejected and the existing state is -maintained. - -#### `publishUserAccountManagementRequest` - -This API is used to publish a Sign In card . The signin action directs users to -the app's sign in page so that the app can publish content (or provide more -personalized content) - -The following metadata is part of the Sign In Card - - -| Attribute | Requirement | Description | -|---|---|---| -| Action Uri | Required | Deeplink to Action (i.e. navigates to app sign in page) | -| Image | Optional - If not provided, Title must be provided | Image Shown on the Card 16x9 aspect ratio images with a resolution of 1264x712 | -| Title | Optional - If not provided, Image must be provided | Title on the Card | -| Action Text | Optional | Text Shown on the CTA (i.e. Sign in) | -| Subtitle | Optional | Optional Subtitle on the Card | - -> [!IMPORTANT] -> **Important:** The publish APIs are upsert APIs; it replaces the existing content. **Don't** call delete and publish APIs subsequently to replace the content as the publish APIs do that inherently. - -### Kotlin - - var SIGN_IN_CARD_ENTITY = - SignInCardEntity.Builder() - .addPosterImage( - Image.Builder() - .setImageUri(Uri.parse("http://www.x.com/image.png")) - .setImageHeightInPixel(500) - .setImageWidthInPixel(500) - .build()) - .setActionText("Sign In") - .setActionUri(Uri.parse("http://xx.com/signin")) - .build() - - client.publishUserAccountManagementRequest( - PublishUserAccountManagementRequest.Builder() - .setSignInCardEntity(SIGN_IN_CARD_ENTITY) - .build()); - -### Java - - SignInCardEntity SIGN_IN_CARD_ENTITY = - new SignInCardEntity.Builder() - .addPosterImage( - new Image.Builder() - .setImageUri(Uri.parse("http://www.x.com/image.png")) - .setImageHeightInPixel(500) - .setImageWidthInPixel(500) - .build()) - .setActionText("Sign In") - .setActionUri(Uri.parse("http://xx.com/signin")) - .build(); - - client.publishUserAccountManagementRequest( - new PublishUserAccountManagementRequest.Builder() - .setSignInCardEntity(SIGN_IN_CARD_ENTITY) - .build()); - -When the service receives the request, the following actions take place within -one transaction: - -- Existing `UserAccountManagementCluster` data from the developer partner is removed. -- Data from the request is parsed and stored in the updated UserAccountManagementCluster Cluster. - -In case of an error, the entire request is rejected and the existing state is -maintained. - -#### `updatePublishStatus` - -If for any internal business reason, none of the clusters is published, -we **strongly recommend** updating the publish status using the -**updatePublishStatus** API. -This is important because : - -- Providing the status in all scenarios, even when the content is published (STATUS == PUBLISHED), is critical to populate dashboards that use this explicit status to convey the health and other metrics of your integration. -- If no content is published but the integration status isn't broken (STATUS == NOT_PUBLISHED), Google can avoid triggering alerts in the app health dashboards. It confirms that content is not published due to an **expected** situation from the provider's standpoint. -- It helps developers provide insights into when the data is published versus not. -- Google may use the status codes to nudge the user to do certain actions in the app so they can see the app content or overcome it. - -The list of eligible publish status codes are : - - // Content is published - AppEngagePublishStatusCode.PUBLISHED, - - // Content is not published as user is not signed in - AppEngagePublishStatusCode.NOT_PUBLISHED_REQUIRES_SIGN_IN, - - // Content is not published as user is not subscribed - AppEngagePublishStatusCode.NOT_PUBLISHED_REQUIRES_SUBSCRIPTION, - - // Content is not published as user location is ineligible - AppEngagePublishStatusCode.NOT_PUBLISHED_INELIGIBLE_LOCATION, - - // Content is not published as there is no eligible content - AppEngagePublishStatusCode.NOT_PUBLISHED_NO_ELIGIBLE_CONTENT, - - // Content is not published as the feature is disabled by the client - // Available in v1.3.1 - AppEngagePublishStatusCode.NOT_PUBLISHED_FEATURE_DISABLED_BY_CLIENT, - - // Content is not published as the feature due to a client error - // Available in v1.3.1 - AppEngagePublishStatusCode.NOT_PUBLISHED_CLIENT_ERROR, - - // Content is not published as the feature due to a service error - // Available in v1.3.1 - AppEngagePublishStatusCode.NOT_PUBLISHED_SERVICE_ERROR, - - // Content is not published due to some other reason - // Reach out to engage-developers@ before using this enum. - AppEngagePublishStatusCode.NOT_PUBLISHED_OTHER - -If the content is not published due to a user not logged in, -Google would recommend publishing the Sign In Card. -If for any reason providers are not able to publish the Sign In Card -then we recommend calling the **updatePublishStatus** API -with the status code **NOT_PUBLISHED_REQUIRES_SIGN_IN** - -### Kotlin - - client.updatePublishStatus( - PublishStatusRequest.Builder() - .setStatusCode(AppEngagePublishStatusCode.NOT_PUBLISHED_REQUIRES_SIGN_IN) - .build()) - -### Java - - client.updatePublishStatus( - new PublishStatusRequest.Builder() - .setStatusCode(AppEngagePublishStatusCode.NOT_PUBLISHED_REQUIRES_SIGN_IN) - .build()); - -#### `deleteRecommendationClusters` - -This API is used to delete the content of Recommendation Clusters. - -> [!IMPORTANT] -> **Important:** Delete APIs should only be called when there is no content to publish. **Don't** call delete and publish APIs subsequently to replace the content as the publish APIs do that inherently. Reach out to [`engage-developers@google.com`](mailto:engage-developers@google.com) before using delete APIs. - -### Kotlin - - client.deleteRecommendationClusters() - -### Java - - client.deleteRecommendationClusters(); - -When the service receives the request, it removes the existing data from the -Recommendation Clusters. In case of an error, the entire request is rejected -and the existing state is maintained. - -#### `deleteUserManagementCluster` - -This API is used to delete the content of UserAccountManagement Cluster. - -> [!IMPORTANT] -> **Important:** Delete APIs should only be called when there is no content to publish. **Don't** call delete and publish APIs subsequently to replace the content as the publish APIs do that inherently. Reach out to [`engage-developers@google.com`](mailto:engage-developers@google.com) before using delete APIs. - -### Kotlin - - client.deleteUserManagementCluster() - -### Java - - client.deleteUserManagementCluster(); - -When the service receives the request, it removes the existing data from the -UserAccountManagement Cluster. In case of an error, the entire request is -rejected and the existing state is maintained. - -#### `deleteClusters` - -This API is used to delete the content of a given cluster type. - -> [!IMPORTANT] -> **Important:** Delete APIs should only be called when there is no content to publish. **Don't** call delete and publish APIs subsequently to replace the content as the publish APIs do that inherently. Reach out to [`engage-developers@google.com`](mailto:engage-developers@google.com) before using delete APIs. - -### Kotlin - - client.deleteClusters( - DeleteClustersRequest.Builder() - .addClusterType(ClusterType.TYPE_RECOMMENDATION) - ... - .build()) - -### Java - - client.deleteClusters( - new DeleteClustersRequest.Builder() - .addClusterType(ClusterType.TYPE_RECOMMENDATION) - ... - .build()); - -When the service receives the request, it removes the existing data from all -clusters matching the specified cluster types. Clients can choose to pass one or -many cluster types. In case of an error, the entire request is rejected and the -existing state is maintained. - -#### Error handling - -It is highly recommended to listen to the task result from the publish APIs such -that a follow-up action can be taken to recover and resubmit an successful task. - - client.publishRecommendationClusters( - new PublishRecommendationClustersRequest.Builder() - .addRecommendationCluster(...) - .build()) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - // do something - } else { - Exception exception = task.getException(); - if (exception instanceof AppEngageException) { - @AppEngageErrorCode - int errorCode = ((AppEngageException) exception).getErrorCode(); - if (errorCode == AppEngageErrorCode.SERVICE_NOT_FOUND) { - // do something - } - } - } - }); - -The error is returned as an `AppEngageException` with the cause included as an -error code. - -| Error code | Error name | Note | -|---|---|---| -| `1` | `SERVICE_NOT_FOUND` | The service is not available on the given device. | -| `2` | `SERVICE_NOT_AVAILABLE` | The service is available on the given device, but it is not available at the time of the call (for example, it is explicitly disabled). | -| `3` | `SERVICE_CALL_EXECUTION_FAILURE` | The task execution failed due to threading issues. In this case, it can be retried. | -| `4` | `SERVICE_CALL_PERMISSION_DENIED` | The caller is not allowed to make the service call. | -| `5` | `SERVICE_CALL_INVALID_ARGUMENT` | The request contains invalid data (for example, more than the allowed number of clusters). | -| `6` | `SERVICE_CALL_INTERNAL` | There is an error on the service side. | -| `7` | `SERVICE_CALL_RESOURCE_EXHAUSTED` | The service call is made too frequently. | - -### Step 3: Handle broadcast intents - -In addition to making publish content API calls through a job, it is also -required to set up a -[`BroadcastReceiver`](https://developer.android.com/reference/android/content/BroadcastReceiver) to receive -the request for a content publish. - -The goal of broadcast intents is mainly for app reactivation and forcing data -sync. Broadcast intents are not designed to be sent very frequently. It is only -triggered when the Engage Service determines the content might be stale (for -example, a week old). That way, there is more confidence that the user can have -a fresh content experience, even if the application has not been executed for a -long period of time. - -The `BroadcastReceiver` must be set up in the following two ways: - -- Dynamically register an instance of the `BroadcastReceiver` class using - `Context.registerReceiver()`. This enables communication from applications - that are still live in memory. - -### Kotlin - - class AppEngageBroadcastReceiver : BroadcastReceiver(){ - // Trigger recommendation cluster publish when PUBLISH_RECOMMENDATION - // broadcast is received - } - - fun registerBroadcastReceivers(context: Context){ - var context = context - context = context.applicationContext - - // Register Recommendation Cluster Publish Intent - context.registerReceiver(AppEngageBroadcastReceiver(), - IntentFilter(Intents.ACTION_PUBLISH_RECOMMENDATION), - com.google.android.engage.service.BroadcastReceiverPermissions.BROADCAST_REQUEST_DATA_PUBLISH_PERMISSION, - /*scheduler=*/null) - } - -### Java - - class AppEngageBroadcastReceiver extends BroadcastReceiver { - // Trigger recommendation cluster publish when PUBLISH_RECOMMENDATION broadcast - // is received - } - - public static void registerBroadcastReceivers(Context context) { - - context = context.getApplicationContext(); - - // Register Recommendation Cluster Publish Intent - context.registerReceiver(new AppEngageBroadcastReceiver(), - new IntentFilter(com.google.android.engage.service.Intents.ACTION_PUBLISH_RECOMMENDATION), - com.google.android.engage.service.BroadcastReceiverPermissions.BROADCAST_REQUEST_DATA_PUBLISH_PERMISSION, - /*scheduler=*/null); - } - -- Statically declare an implementation with the `` tag in your - `AndroidManifest.xml` file. This allows the application to receive broadcast - intents when it is not running, and also allows the application to publish - the content. - - - - - - - - - -The following [intents](https://developer.android.com/reference/android/content/Intent) will be sent by the -service: - -- `com.google.android.engage.action.PUBLISH_RECOMMENDATION` It is recommended to start a `publishRecommendationClusters` call when receiving this intent. - -## Integration workflow - -For a step-by-step guide on verifying your integration after it is complete, see -[Engage developer integration workflow](https://developer.android.com/guide/playcore/engage/workflow). - -## FAQs - -See [Engage SDK Frequently Asked Questions](https://developer.android.com/guide/playcore/engage/faq) for -FAQs. - -## Contact - -Contact -[`engage-developers@google.com`](mailto:engage-developers@google.com) if there are -any questions during the integration process. Our team will reply as soon as -possible. - -## Next steps - -After completing this integration, your next steps are as follows: - -- Send an email to [`engage-developers@google.com`](mailto:engage-developers@google.com) and attach your integrated APK that is ready for testing by Google. -- Google performs a verification and reviews internally to make sure the integration works as expected. If changes are needed, Google contacts you with any necessary details. -- When testing is complete and no changes are needed, Google contacts you to notify you that you can start publishing the updated and integrated APK to the Play Store. -- After Google has confirmed that your updated APK has been published to the Play Store, your **Recommendation**, clusters will be published and visible to users. \ No newline at end of file diff --git a/play/engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md b/play/engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md index c0c0058..49d8975 100644 --- a/play/engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md +++ b/play/engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md @@ -48,18 +48,12 @@ Before you begin, complete the following steps: 6. Test your implementation using the verification app as outlined in the [Testing section](https://developer.android.com/guide/playcore/engage/tv/getting-started#testing). -7. In your production app, set the Engage service environment to production by - adding the `` element directly under the `` tag - in your `AndroidManifest.xml` file. - Don't place this tag inside an ``. - - - - - - +7. In your production app, set the Engage service environment to production in + the `AndroidManifest.xml` file. + + > [!IMPORTANT] > **Important:** The Engage service environment declaration is only needed for your release build. For your local testing with the verification app, don't include this element. diff --git a/play/engage-sdk-integration/references/common.md b/play/engage-sdk-integration/references/common.md new file mode 100644 index 0000000..52c3d5e --- /dev/null +++ b/play/engage-sdk-integration/references/common.md @@ -0,0 +1,519 @@ +This file defines common data models and entities used across the Engage SDK. + + { + "entities": { + "Image": { + "package": "com.google.android.engage.common.datamodel.Image", + "fields": { + "imageUri": { + "type": "Uri", + "requirement": "Required", + "setter": "setImageUri(Uri)", + "getter": "getImageUri()" + }, + "height": { + "type": "Integer", + "requirement": "Optional" + }, + "width": { + "type": "Integer", + "requirement": "Optional" + }, + "accessibilityText": { + "type": "String", + "requirement": "Required", + "setter": "setAccessibilityText(String)", + "getter": "getAccessibilityText()" + }, + "theme": { + "type": "ImageThemeEnum", + "requirement": "Optional" + }, + "cropType": { + "type": "ImageCropTypeEnum", + "requirement": "Optional" + }, + "imageHeightInPixel": { + "requirement": "Required", + "setter": "setImageHeightInPixel(int)", + "type": "Integer", + "getter": "getImageHeightInPixel()" + }, + "imageWidthInPixel": { + "requirement": "Required", + "setter": "setImageWidthInPixel(int)", + "type": "Integer", + "getter": "getImageWidthInPixel()" + }, + "imageTheme": { + "requirement": "Optional", + "setter": "setImageTheme(@ImageTheme int)", + "type": "@ImageTheme int", + "getter": "getImageTheme()" + }, + "imageCropType": { + "requirement": "Optional", + "setter": "setImageCropType(@ImageCropType int)", + "type": "@ImageCropType int", + "getter": "getImageCropType()" + } + } + }, + "Price": { + "package": "com.google.android.engage.common.datamodel.Price", + "fields": { + "currentPrice": { + "type": "String", + "requirement": "Required", + "setter": "setCurrentPrice(String)", + "getter": "getCurrentPrice()" + }, + "strikethroughPrice": { + "type": "String", + "requirement": "Optional", + "setter": "setStrikethroughPrice(String)", + "getter": "getStrikethroughPrice()" + } + } + }, + "Rating": { + "package": "com.google.android.engage.common.datamodel.Rating", + "fields": { + "ratingValue": { + "type": "Float", + "requirement": "Required" + }, + "maxValue": { + "type": "Double", + "requirement": "Required", + "setter": "setMaxValue(double)", + "getter": "getMaxValue()" + }, + "ratingCount": { + "type": "Integer", + "requirement": "Optional" + }, + "ratingCountValue": { + "type": "String", + "requirement": "Optional" + }, + "currentValue": { + "requirement": "Required", + "setter": "setCurrentValue(double)", + "type": "Double", + "getter": "getCurrentValue()" + }, + "count": { + "requirement": "Optional", + "setter": "setCount(String)", + "type": "String", + "getter": "getCount()" + }, + "countValue": { + "requirement": "Optional", + "setter": "setCountValue(long)", + "type": "Long", + "getter": "getCountValue()" + } + } + }, + "DisplayTimeWindow": { + "package": "com.google.android.engage.common.datamodel.DisplayTimeWindow", + "fields": { + "startTimeMillis": { + "type": "Long", + "requirement": "Required" + }, + "endTimeMillis": { + "type": "Long", + "requirement": "Required" + }, + "startTimestampMillis": { + "requirement": "Optional", + "setter": "setStartTimestampMillis(long)", + "type": "Long", + "getter": "getStartTimestampMillis()" + }, + "endTimestampMillis": { + "requirement": "Optional", + "setter": "setEndTimestampMillis(long)", + "type": "Long", + "getter": "getEndTimestampMillis()" + } + } + }, + "AccountProfile": { + "package": "com.google.android.engage.common.datamodel.AccountProfile", + "fields": { + "accountId": { + "type": "@NonNull String", + "requirement": "Required", + "setter": "setAccountId(@NonNull String)", + "getter": "getAccountId()" + }, + "profileId": { + "type": "@NonNull String", + "requirement": "Optional", + "setter": "setProfileId(@NonNull String)", + "getter": "getProfileId()" + }, + "accountName": { + "type": "String", + "requirement": "Optional" + }, + "profileImage": { + "type": "Image", + "requirement": "Optional" + }, + "locale": { + "requirement": "Optional", + "setter": "setLocale(@NonNull String)", + "type": "@NonNull String", + "getter": "getLocale()" + } + } + }, + "Address": { + "package": "com.google.android.engage.common.datamodel.Address", + "fields": { + "city": { + "type": "@NonNull String", + "requirement": "Required", + "setter": "setCity(@NonNull String)", + "getter": "getCity()" + }, + "country": { + "type": "@NonNull String", + "requirement": "Required", + "setter": "setCountry(@NonNull String)", + "getter": "getCountry()" + }, + "displayAddress": { + "type": "@NonNull String", + "requirement": "Required", + "setter": "setDisplayAddress(@NonNull String)", + "getter": "getDisplayAddress()" + }, + "streetAddress": { + "type": "String", + "requirement": "Optional", + "setter": "setStreetAddress(String)", + "getter": "getStreetAddress()" + }, + "state": { + "type": "String", + "requirement": "Optional", + "setter": "setState(String)", + "getter": "getState()" + }, + "zipCode": { + "type": "String", + "requirement": "Optional", + "setter": "setZipCode(String)", + "getter": "getZipCode()" + }, + "neighborhood": { + "type": "String", + "requirement": "Optional", + "setter": "setNeighborhood(String)", + "getter": "getNeighborhood()" + } + } + }, + "Badge": { + "package": "com.google.android.engage.common.datamodel.Badge", + "fields": { + "text": { + "type": "String", + "requirement": "Optional", + "setter": "setText(String)", + "getter": "getText()" + }, + "image": { + "type": "Image", + "requirement": "Optional", + "setter": "setImage(Image)", + "getter": "getImage()" + } + } + }, + "LocalizedTimestamp": { + "package": "com.google.android.engage.common.datamodel.LocalizedTimestamp", + "fields": { + "timestamp": { + "type": "Instant", + "requirement": "Required", + "setter": "setTimestamp(Instant)", + "getter": "getTimestamp()" + }, + "timezone": { + "type": "DateTimeZone", + "requirement": "Required", + "setter": "setTimezone(DateTimeZone)", + "getter": "getTimezone()" + } + } + }, + "SignInCardEntity": { + "package": "com.google.android.engage.common.datamodel.SignInCardEntity", + "fields": { + "actionText": { + "type": "String", + "requirement": "Required", + "setter": "setActionText(String)", + "getter": "getActionText()" + }, + "actionUri": { + "type": "Uri", + "requirement": "Required", + "setter": "setActionUri(Uri)", + "getter": "getActionUri()" + }, + "title": { + "type": "String", + "requirement": "Optional", + "setter": "setTitle(String)", + "getter": "getTitle()" + }, + "subtitle": { + "type": "String", + "requirement": "Optional", + "setter": "setSubtitle(String)", + "getter": "getSubtitle()" + }, + "entityId": { + "type": "String", + "requirement": "Optional", + "setter": "setEntityId(String)", + "getter": "getEntityId()" + }, + "posterImages": { + "type": "List", + "requirement": "Required", + "adder": "addPosterImage(Image)", + "getter": "getPosterImages()", + "adderAll": "addPosterImages(List)" + } + } + }, + "GenericFeaturedEntity": { + "package": "com.google.android.engage.common.datamodel.GenericFeaturedEntity", + "fields": { + "entityId": { + "type": "String", + "requirement": "Optional", + "setter": "setEntityId(String)", + "getter": "getEntityId()" + }, + "posterImages": { + "type": "List", + "requirement": "Required", + "adder": "addPosterImage(Image)", + "getter": "getPosterImages()", + "adderAll": "addPosterImages(List)" + }, + "actionUri": { + "type": "Uri", + "requirement": "Required", + "setter": "setActionUri(Uri)", + "getter": "getActionUri()" + }, + "title": { + "type": "String", + "requirement": "Optional", + "setter": "setTitle(String)", + "getter": "getTitle()" + }, + "description": { + "type": "String", + "requirement": "Optional", + "setter": "setDescription(String)", + "getter": "getDescription()" + }, + "subtitles": { + "type": "List", + "requirement": "Optional", + "adder": "addSubtitle(String)", + "adderAll": "addSubtitles(List)" + }, + "badges": { + "type": "List", + "requirement": "Optional", + "adder": "addBadge(Badge)", + "adderAll": "addBadges(List)" + }, + "contentCategories": { + "type": "List<@EligibleContentCategory int>", + "requirement": "Optional", + "adder": "addContentCategory(@EligibleContentCategory int)", + "adderAll": "addContentCategories(List)" + }, + "displayTimeWindows": { + "type": "List", + "requirement": "Optional", + "adder": "addDisplayTimeWindow(DisplayTimeWindow)", + "getter": "getDisplayTimeWindows()" + }, + "allDisplayTimeWindows": { + "type": "List>", + "requirement": "Optional", + "adder": "addAllDisplayTimeWindow(DisplayTimeWindow)", + "adderAll": "addAllDisplayTimeWindow(List)" + } + } + }, + "SubscriptionEntity": { + "package": "com.google.android.engage.common.datamodel.SubscriptionEntity", + "fields": {} + }, + "UserSettingsCardEntity": { + "package": "com.google.android.engage.common.datamodel.UserSettingsCardEntity", + "fields": { + "actionText": { + "type": "String", + "requirement": "Required", + "setter": "setActionText(String)", + "getter": "getActionText()" + }, + "actionUri": { + "type": "Uri", + "requirement": "Required", + "setter": "setActionUri(Uri)", + "getter": "getActionUri()" + }, + "title": { + "type": "String", + "requirement": "Optional", + "setter": "setTitle(String)", + "getter": "getTitle()" + }, + "subtitle": { + "type": "String", + "requirement": "Optional", + "setter": "setSubtitle(String)", + "getter": "getSubtitle()" + }, + "entityId": { + "type": "String", + "requirement": "Optional", + "setter": "setEntityId(String)", + "getter": "getEntityId()" + }, + "posterImages": { + "type": "List", + "requirement": "Required", + "adder": "addPosterImage(Image)", + "getter": "getPosterImages()", + "adderAll": "addPosterImages(List)" + } + } + }, + "ArticleEntity": { + "package": "com.google.android.engage.common.datamodel.ArticleEntity", + "fields": { + "entityId": { + "type": "String", + "requirement": "Optional", + "setter": "setEntityId(String)", + "getter": "getEntityId()" + }, + "posterImages": { + "type": "List", + "requirement": "Required", + "adder": "addPosterImage(Image)", + "getter": "getPosterImages()", + "adderAll": "addPosterImages(List)" + }, + "actionUri": { + "type": "Uri", + "requirement": "Required", + "setter": "setActionUri(Uri)", + "getter": "getActionUri()" + }, + "title": { + "type": "String", + "requirement": "Optional", + "setter": "setTitle(String)", + "getter": "getTitle()" + }, + "description": { + "type": "String", + "requirement": "Optional", + "setter": "setDescription(String)", + "getter": "getDescription()" + }, + "subtitles": { + "type": "List", + "requirement": "Optional", + "adder": "addSubtitle(String)", + "adderAll": "addSubtitles(List)" + }, + "badges": { + "type": "List", + "requirement": "Optional", + "adder": "addBadge(Badge)", + "adderAll": "addBadges(List)" + }, + "contentCategories": { + "type": "List<@EligibleContentCategory int>", + "requirement": "Optional", + "adder": "addContentCategory(@EligibleContentCategory int)", + "adderAll": "addContentCategories(List)" + }, + "progressPercentage": { + "type": "Integer", + "requirement": "Required", + "setter": "setProgressPercentage(int)", + "getter": "getProgressPercentage()", + "requiredFor": [ + "ContinuationCluster" + ] + }, + "lastEngagementTimestampMillis": { + "type": "Long", + "requirement": "Required", + "setter": "setLastEngagementTimestampMillis(long)", + "getter": "getLastEngagementTimestampMillis()", + "requiredFor": [ + "ContinuationCluster" + ] + }, + "source": { + "type": "Badge", + "requirement": "Optional", + "setter": "setSource(Badge)", + "getter": "getSource()" + }, + "lastContentPublishTimestampMillis": { + "type": "Long", + "requirement": "Optional", + "setter": "setLastContentPublishTimestampMillis(Long)", + "getter": "getLastContentPublishTimestampMillis()" + }, + "displayTimeWindows": { + "type": "List", + "requirement": "Optional", + "adder": "addDisplayTimeWindow(DisplayTimeWindow)", + "getter": "getDisplayTimeWindows()" + }, + "allDisplayTimeWindows": { + "type": "List>", + "requirement": "Optional", + "adder": "addAllDisplayTimeWindow(DisplayTimeWindow)", + "adderAll": "addAllDisplayTimeWindow(List)" + } + } + } + }, + "intents": { + "ACTION_PUBLISH_RECOMMENDATION": "com.google.android.engage.action.PUBLISH_RECOMMENDATION", + "ACTION_PUBLISH_FEATURED": "com.google.android.engage.action.PUBLISH_FEATURED", + "ACTION_PUBLISH_CONTINUATION": "com.google.android.engage.action.PUBLISH_CONTINUATION" + }, + "imports": [ + "com.google.android.engage.service.AppEngagePublishClient", + "com.google.android.engage.service.AppEngageErrorCode", + "com.google.android.engage.service.AppEngageException", + "com.google.android.engage.service.AppEngagePublishStatusCode" + ] + } \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/SKILL.md b/play/play-billing-library-version-upgrade/SKILL.md new file mode 100644 index 0000000..6784606 --- /dev/null +++ b/play/play-billing-library-version-upgrade/SKILL.md @@ -0,0 +1,93 @@ +--- +name: play-billing-library-version-upgrade +description: Use this skill when upgrading or migrating an Android project from any + legacy Google Play Billing Library (PBL) version to the latest stable version of + PBL. +license: Complete terms in LICENSE.txt +metadata: + author: Google LLC + last-updated: '2026-07-02' + keywords: + - android + - play billing + - play billing library + - pbl + - upgrade + - migration + - deprecation + - google play +--- + +## Phase 0: Intent Message + +**Reporting Action**: Before proceeding, immediately tell the user: "I will +upgrade Play Billing Library to the latest version." + +## Phase 1: Discovery \& Situational Awareness + +1. **Primary Check (Build Version)** : Locate the project's billing dependency (e.g., `com.android.billingclient:billing`) in `build.gradle`, `build.gradle.kts`, or `libs.versions.toml`. +2. **Initial Compilation Test**: Attempt to sync and build the project immediately. +3. **Fallback Discovery (Effective Version)** : + - **Trigger**: Only if the build fails immediately, scan the source code for deprecated artifacts. + - **Logic** : The presence of deprecated APIs indicates the **"Effective + Version"** ---defined as the version where those specific APIs were **last + available**, not when they were introduced. + - **Example** : If `SkuDetails` is present, treat the baseline as **PBL v7** or earlier (regardless of the version string in `build.gradle`). +4. **Identify Target \& Path** : Access the version tool or release notes to find the latest stable version and calculate a \[Direct/Stepped\] migration path based on the **Effective Version** baseline. + +- **Calculate Migration Path** : + - If the **Effective Version** is within 2 major versions of the target: Plan a **Direct Migration**. + - If it is more than 2 major versions behind: Plan a **Stepped + Migration**. Migrate by two major versions at a time (e.g., v4 -\> v6 -\> v8) until you are within two versions of the target. +- **Reporting Action**: Before proceeding, tell the user: "I've detected you are effectively on PBL \[Current\] and the latest is \[Target\]. I am planning a \[direct/stepped\] migration path." + +## Phase 2: Contextual Document Mapping \& Planning + +For every major version jump identified in your path, you **MUST** synthesize +instructions from: + +- **[Migration Guide](https://developer.android.com/google/play/billing/migrate-gpblv%5BX%5D)** (where `[X]` is the target major version). +- **Release Highlights** : The "Deprecations" and "Breaking Changes" sections of the relevant [Release Notes](references/android/google/play/billing/release-notes.md). +- **Developer Documentation**: Consult your knowledge of the Google Play Billing documentation regarding the relevant features used in this app (e.g., Subscriptions, One-Time Products). +- **Develop the Plan**: Identify every specific code change required (API removals, class replacements, logic shifts) and print this out as a checklist. + +## Phase 3: Instructions for Execution + +*Reporting Action: For each of the following steps, give a brief explanation of +what you will be doing prior to execution, and a brief summary of what you +accomplished afterwards.* + +### Step 1: SDK \& Environment Alignment + +- **Action** : Update `build.gradle` to meet SDK requirements (e.g., "PBL 9 requires `compileSdk` 35"). +- **Gradle Version**: Verify if the new library requires a newer Android Gradle Plugin (AGP) or Kotlin version. + +### Step 2: Intent-based Refactoring + +Analyze the intent of the existing code rather than performing purely textual +string replacement. + +- **Action** : You **MUST** follow all deprecation instructions and refactor patterns from **both** the [references/migration-logic.md](references/migration-logic.md) section, the official migration guides, and the general documentation pages identified in Phase 2. +- **Verification** : Verify you are doing **all steps from all documentation** and then making sure you follow the specific directions from the checklist in the references. + +### Step 3: Sequential Verification (Only applicable for Stepped Migrations) + +1. **Upgrade** to the first major intermediate version in your path. +2. **Run `./gradlew assembleDebug`** to verify no intermediate breaking changes were missed. +3. **Repeat** until you reach the final target version. + +### Step 4: Final Validation Checklist + +1. **Smart Checklist Verification**: +2. Open [references/version-checklist.md](references/version-checklist.md) and locate the **Smart Version-Specific Checklist**. +3. **Action**: For every version between your \[Detected Effective Version\] and \[Detected New Version\], verify that every item has been addressed in the code using "Find in Files" or structural analysis. +4. **Tests** : Run all unit and implementation tests (`./gradlew test`). +5. **Clean Build** : Verify the project completes a full clean build: `./gradlew clean assembleDebug`. Then, run `./gradlew sync` and `./gradlew build` so that the user can immediately test the new version manually. + +## Final Report + +Explain the "Why" to the developer: + +- "I updated your SDK to \[Version\] because PBL \[Version\] requires it for \[Reason from docs\]." +- "I removed your custom `retryConnection()` logic because it is now handled natively by the library using `enableAutoServiceReconnection()`." +- "Successfully upgraded from PBL \[Old\] to PBL \[New\] and verified with unit tests. Based on an analysis of features in the latest library and this application's current feature set, I suggest exploring \[New Feature\] (e.g., Prepaid Plans or Installments) from the latest release because it is now available but not yet implemented." diff --git a/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md b/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md new file mode 100644 index 0000000..462f373 --- /dev/null +++ b/play/play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md @@ -0,0 +1,1226 @@ +This document contains release notes for the Google Play Billing Library. + +> [!NOTE] +> **Note:** You can use the [Play Billing Library Version Upgrade Skill](https://github.com/android/skills/tree/main/play/play-billing-library-version-upgrade) to automate your upgrade to the latest version. + +## Google Play Billing Library 9.1.0 Release (2026-06-18) + +Version 9.1.0 of the Google Play Billing Library and Kotlin extensions are now available. + +### Summary of changes + +- New APIs for [Billing Choice](https://developer.android.com/google/play/billing/billingchoice): + - Added [`BillingClient.getBillingChoiceInfoAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#getBillingChoiceInfoAsync(com.android.billingclient.api.GetBillingChoiceInfoParams,com.android.billingclient.api.BillingChoiceInfoResponseListener)) to retrieve information about billing choices available to the user. + - Added [`BillingChoiceInfo`](https://developer.android.com/reference/com/android/billingclient/api/BillingChoiceInfo) class which contains the response details, including image URLs and loyalty information. + - Added [`GetBillingChoiceInfoParams`](https://developer.android.com/reference/com/android/billingclient/api/GetBillingChoiceInfoParams) to configure the request. + - Added [`BillingChoiceInfoResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/BillingChoiceInfoResponseListener) to receive the callback. + - Added Kotlin extension [`BillingClient.getBillingChoiceInfo()`](https://developer.android.com/reference/kotlin/com/android/billingclient/api/package-summary#(com.android.billingclient.api.BillingClient).getBillingChoiceInfo(com.android.billingclient.api.GetBillingChoiceInfoParams)) suspend function. + - Added [`BillingClient.showBillingProgramInformationDialog()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showBillingProgramInformationDialog(android.app.Activity,com.android.billingclient.api.BillingProgramInformationDialogParams,com.android.billingclient.api.BillingProgramInformationDialogListener)) to show an information dialog for a billing program. + - Added [`BillingProgramInformationDialogParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramInformationDialogParams) to configure the dialog. + - Added [`BillingProgramInformationDialogListener`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramInformationDialogListener) to receive the callback. + - Added [`BillingProgramAvailabilityDetails.BillingChoiceAvailabilityDetails`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramAvailabilityDetails.BillingChoiceAvailabilityDetails) to provide details about billing choice availability. + - Added [`ChoiceScreenType`](https://developer.android.com/reference/com/android/billingclient/api/BillingProgramAvailabilityDetails.BillingChoiceAvailabilityDetails.ChoiceScreenType) to specify the type of choice screen. + +## Google Play Billing Library 9.0.0 Release (2026-05-19) + +Version 9.0.0 of the Google Play Billing Library and Kotlin extensions are +now available. See the [PBL 9 migration guide](https://developer.android.com/google/play/billing/migrate-pbl-latest) if you want to migrate from +the previous versions of PBL. + +### Summary of changes + +- **Updated error codes for blocked Play Store activity** : Error codes for + blocked Play Store apps have been updated. For instances where + the Play Store app is blocked by the system + (for example, in OEM-customized kids mode), the response code has + changed from `ERROR` to `BILLING_UNAVAILABLE`. Additionally, the + `BillingResult` for such cases now provides a *Play Store is blocked* + debug message. + + > [!NOTE] + > **Note:** For this feature to work, you need [AndroidX.core library](https://developer.android.com/jetpack/androidx/releases/core#core_and_core-ktx_version_190_2) version 1.9 or later. + +- **Nullability update for developer-provided billing** : The + [`DeveloperProvidedBillingDetails.getLinkUri()`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingDetails#getLinkUri()) method has + been updated to be `@Nullable`. This change supports scenarios where + the direct link URI for external payments is unavailable during the + payment selection stage. + + To handle this change safely, ensure your integration code handles both + `null` and empty string (`""`) values from the + [`DeveloperProvidedBillingDetails.getLinkUri()`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingDetails#getLinkUri()) method before parsing + or launching browser intents. +- Updated `targetSdkVersion` to 35. + +- You can now use [in-app messaging](https://developer.android.com/google/play/billing/subscriptions#in-app-messaging) to notify users + of an upcoming opt-in price increase. This lets users confirm the price increase without leaving the app. The message for an outstanding opt-in price increase is shown starting on the first day the user can accept the price increase, and the message is shown a maximum of once every 7 days. + +## Google Play Billing Library 8.3.0 Release (2025-12-23) + +Version 8.3.0 of the Google Play Billing Library and Kotlin extensions are now available. + +### Summary of changes + +- New APIs for [external payments](https://developer.android.com/google/play/billing/externalpaymentlinks): + + - Added classes to support external payments flow: + - [`BillingProgram.EXTERNAL_PAYMENTS`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingProgram#EXTERNAL_PAYMENTS) + - [`EnableBillingProgramParams`](https://developer.android.com/reference/com/android/billingclient/api/EnableBillingProgramParams) + - [`DeveloperBillingOptionParams`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperBillingOptionParams) + - [`DeveloperProvidedBillingDetails`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingDetails) + - [`DeveloperProvidedBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/DeveloperProvidedBillingListener) + - Added [`enableBillingProgram(EnableBillingProgramParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableBillingProgram(com.android.billingclient.api.EnableBillingProgramParams)) to enable external payments. + - Added [`BillingFlowParams.Builder.enableDeveloperBillingOption`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#enableDeveloperBillingOption(com.android.billingclient.api.DeveloperBillingOptionParams)) to launch the external payments flow. + +## Google Play Billing Library 8.2.1 Release (2025-12-15) + +Version 8.2.1 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Bug fixes + +- Fixed a bug in [`isBillingProgramAvailableAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isBillingProgramAvailableAsync(int,com.android.billingclient.api.BillingProgramAvailabilityListener)) and [`createBillingProgramReportingDetailsAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#createBillingProgramReportingDetailsAsync(com.android.billingclient.api.BillingProgramReportingDetailsParams,com.android.billingclient.api.BillingProgramReportingDetailsListener)). Update to version 8.2.1 to use these APIs introduced in 8.2.0. + +## Google Play Billing Library 8.2.0 Release (2025-12-09) + +Version 8.2.0 of the Google Play Billing Library and Kotlin extensions are now available. + +### Summary of changes + +- New APIs for [external content links](https://developer.android.com/google/play/billing/externalcontentlinks) and [external offers](https://developer.android.com/google/play/billing/external): + + - Added [`enableBillingProgram`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableBillingProgram(int)) to setup the `BillingClient` for these programs. + - Added [`isBillingProgramAvailableAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isBillingProgramAvailableAsync(int,com.android.billingclient.api.BillingProgramAvailabilityListener)) to determine user eligibility. + - Added [`createBillingProgramReportingDetailsAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#createBillingProgramReportingDetailsAsync(com.android.billingclient.api.BillingProgramReportingDetailsParams,com.android.billingclient.api.BillingProgramReportingDetailsListener)) to create the external transaction token that must be used for reporting. + - Added [`launchExternalLink`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchExternalLink(android.app.Activity,com.android.billingclient.api.LaunchExternalLinkParams,com.android.billingclient.api.LaunchExternalLinkResponseListener)) to initiate the external link to a digital content offer or an app download. +- Changes to the [external offers program](https://developer.android.com/google/play/billing/external): + + - There are policy changes for the external offers program. See [program changes](https://support.google.com/googleplay/android-developer/answer/16505463) for details. To understand how to launch external offer flows with the new APIs, see the [integration guide](https://developer.android.com/google/play/billing/external/integration). + - Deprecated the [`BillingClient.Builder.enableExternalOffer`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableExternalOffer()) API. + - Deprecated the [`isExternalOfferAvailableAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isExternalOfferAvailableAsync(com.android.billingclient.api.ExternalOfferAvailabilityListener)) API. + - Deprecated the [`createExternalOfferReportingDetailsAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#createExternalOfferReportingDetailsAsync(com.android.billingclient.api.ExternalOfferReportingDetailsListener)) API. + - Deprecated the [`showExternalOfferInformationDialog`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showExternalOfferInformationDialog(android.app.Activity,com.android.billingclient.api.ExternalOfferInformationDialogListener)) API. + +## Google Play Billing Library 8.1.0 Release (2025-11-06) + +Version 8.1.0 of the Google Play Billing Library and Kotlin extensions are now available. + +### Summary of changes + +- Suspended subscriptions + + A new parameter has been added to the + [`BillingClient.queryPurchasesAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,com.android.billingclient.api.PurchasesResponseListener)) + method to include suspended subscriptions when querying for subscriptions. + Suspended subscriptions are still attributed to the user, but are not active, + either because the user paused the subscription or their renewal payment method + was declined. + + The [`Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase) object returned in the listener will return `isSuspended() = true` + for any suspended subscriptions. In this case, you shouldn't grant access + to the purchased subscription, and instead guide the user to the + [subscriptions center](https://play.google.com/store/account/subscriptions) + where the user can manage their payment methods + or pause state to re-activate their subscription. +- Updates to [subscriptions](https://developer.android.com/google/play/billing/subscriptions): + + - The [`BillingFlowParams.ProductDetailsParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProductDetailsParams) + object now has the `setSubscriptionProductReplacementParams()` method + in which you can specify product level replacement information. + + - The `SubscriptionProductReplacementParams` object has + two setter methods: + + - `setOldProductId`: The old product that needs to be replaced by the product in current [`ProductDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails). + - `setReplacementMode`: This is the item level replacement mode. The modes are essentially the same as SubscriptionUpdateParams, but the value mapping has been updated. A new replacement mode `KEEP_EXISTING` is introduced that lets you keep the existing payment schedule unchanged for an item. + - [SubscriptionUpdateParams setSubscriptionReplacementMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int)) will be + deprecated. You should use `SubscriptionProductReplacementParams.setReplacementMode` + instead. + +- Updated `minSdkVersion` to 23. + +- Enabled [pre-order APIs for one-time products](https://developer.android.com/google/play/billing/one-time-product-multi-purchase-options-offers#pre-order) + + The `ProductDetails.oneTimePurchaseOfferDetails.getPreorderDetails()` API + that gets the pre-order details is now available for use. +- Google Play Billing Library now supports [Kotlin version 2.2.0](https://kotlinlang.org/docs/whatsnew22.html). + +## Google Play Billing Library 8.0.0 Release (2025-06-30) + +Version 8.0.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- *In-app items* will now be referred to as *one-time products*. + +- Multiple purchase options and offers for one-time products. + + You can now have multiple purchase options and offers for your one-time + products. This provides you flexibility in how you sell your products and + reduces the complexity of managing them. +- Improved the [`queryProductDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryProductDetailsAsync(com.android.billingclient.api.QueryProductDetailsParams,com.android.billingclient.api.ProductDetailsResponseListener)) method. + + Prior to PBL 8.0.0, the [`queryProductDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryProductDetailsAsync(com.android.billingclient.api.QueryProductDetailsParams,com.android.billingclient.api.ProductDetailsResponseListener)) method didn't + return products that couldn't be fetched. This could be due to reasons such + as the product is not found or no offers are available to the user. With PBL + 8.0.0, unfetched products are returned with a new product-level status code + that provides information about unfetched products. Note that there is a + change in the signature of the + [`ProductDetailsResponseListener.onProductDetailsResponse()`](https://developer.android.com/google/play/billing/integrate#automatic-service-reconnection) which + requires changes in your app. For more information, see [process the + result](https://developer.android.com/google/play/billing/integrate#process-the-result). +- Automatic service reconnection. + + The new `BillingClient.Builder.enableAutoServiceReconnection()` builder + parameter lets developers opt-in to automatic service reconnection, which + simplifies connection management by handling reconnections to + the Play Billing Service automatically and eliminating the need to manually + call `startConnection()` in the event of a service disconnection. + For more information, see [Automatically Re-establish a Connection](https://developer.android.com/google/play/billing/integrate#automatic-service-reconnection). +- Sub-response codes for the [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchBillingFlow(android.app.Activity,com.android.billingclient.api.BillingFlowParams)) method. + + The [BillingResult](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) returned from [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchBillingFlow(android.app.Activity,com.android.billingclient.api.BillingFlowParams)) will now + include a sub-response code field. This field will only be populated in some + cases to provide a more specific reason for the failure. The sub-response + field can have the following values: + - `PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS` - Returned when the user's funds are less than the price of the item they are attempting to purchase. + - `USER_INELIGIBLE` - Returned when the user doesn't meet the configured eligibility requirements for a subscription offer. + - `NO_APPLICABLE_SUB_RESPONSE_CODE` - The default value, returned when no other sub-response code is applicable. +- Removed the `queryPurchaseHistory()` method. + + The `queryPurchaseHistory()` method that was previously marked as + deprecated has now been removed. See [Query Purchase History](https://developer.android.com/google/play/billing/query-purchase-history) for + details on what alternative APIs to use instead. +- Removed the `querySkuDetailsAsync()` method. + + The `querySkuDetailsAsync()` method that was previously marked as deprecated + has now been removed. You should use [queryProductDetailsAsync](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryProductDetailsAsync(com.android.billingclient.api.QueryProductDetailsParams,com.android.billingclient.api.ProductDetailsResponseListener)) instead. +- Removed the `BillingClient.Builder.enablePendingPurchases()` method. + + The `enablePendingPurchases()` method with no parameters that was previously + marked as deprecated has now been removed. You should use + `enablePendingPurchases(PendingPurchaseParams params)` instead. Note that + the deprecated `enablePendingPurchases()` is functionally equivalent to + `enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())`. +- Removed the overloaded `queryPurchasesAsync()` method that takes a [skuType](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.SkuType). + + The `queryPurchasesAsync(String skuType, PurchasesResponseListener + listener)` method that was previously marked as + deprecated has now been removed. Alternately, use + [`queryPurchasesAsync(QueryPurchasesParams queryPurchasesParams, + PurchasesResponseListener listener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,com.android.billingclient.api.PurchasesResponseListener)). + +## Google Play Billing Library 7.1.1 Release (2024-10-03) + +Version 7.1.1 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Bug fixes + +- Fixed a bug in Play Billing Library 7.1.0 related to [testing + `BillingResult` response codes](https://developer.android.com/google/play/billing/test-response-codes). + +## Google Play Billing Library 7.1.0 Release (2024-09-19) + +Version 7.1.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Improved thread safety related to connection status and management. +- Introduced partial changes for testing [`BillingResult`](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) response codes which is fully released in Play Billing Library 7.1.1. To test your integration using this feature, you'll need to upgrade to Play Billing Library 7.1.1. A bug exists that will only impact applications with [billing overrides testing enabled](https://developer.android.com/google/play/billing/test-response-codes#enable-billing-overrides-testing) and does not affect regular usage. For more information, see [Test `BillingResult` + response codes](https://developer.android.com/google/play/billing/test-response-codes). + +## Google Play Billing Library 7.0.0 Release (2024-05-14) + +Version 7.0.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added APIs to support installment subscriptions. + + - Added [`ProductDetails.InstallmentPlanDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.InstallmentPlanDetails) for installment base plans that users are eligible to purchase. This API helps your app identify the installment plan and its commitment setup to provide related information to the user. To learn more, see our [subscription installments guide](https://developer.android.com/google/play/billing/subscriptions#installments). +- Added [`PendingPurchasesParams`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams) and + [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(PendingPurchaseParams)) + to replace + [`BillingClient.Builder.enablePendingPurchases()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases()), + which has been deprecated in this release. + + - The deprecated `enablePendingPurchases()` is functionally equivalent to `enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())`. +- Added APIs to support pending transactions for subscription prepaid plans: + + - Use [`PendingPurchasesParams.Builder.enablePrepaidPlans()`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enablePrepaidPlans()) along with [`BillingClient.Builder.enablePendingPurchases(PendingPurchaseParams)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases(PendingPurchaseParams)) to enable pending transactions for subscription prepaid plans. When adding support, be sure that your app also correctly manages subscription lifecycles. To learn more see our [pending purchases + guide](https://developer.android.com/google/play/billing/subscriptions#pending). + - Added [`Purchase.PendingPurchaseUpdate`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PendingPurchaseUpdate) and [`Purchase.getPendingPurchaseUpdate()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.getPendingPurchaseUpdate()) for retrieving the pending top-up or upgrade or downgrade to an existing subscription. +- Removed + [`BillingClient.Builder.enableAlternativeBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableAlternativeBilling(com.android.billingclient.api.AlternativeBillingListener)), + [`AlternativeBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingListener), and + [`AlternativeChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeChoiceDetails). + + - Developers should use [`BillingClient.Builder.enableUserChoiceBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableUserChoiceBilling(com.android.billingclient.api.UserChoiceBillingListener)) with [`UserChoiceBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceBillingListener) and [`UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) in the listener callback instead. +- Removed [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode), + [`BillingFlowParams.SubscriptionUpdateParams.Builder.setReplaceProrationMode()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setReplaceProrationMode(int)), + and + [`BillingFlowParams.SubscriptionUpdateParams.Builder.setReplaceSkusProrationMode()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setReplaceSkusProrationMode(int)). + + - Developers should use [`BillingFlowParams.SubscriptionUpdateParams.ReplacementMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) with [`BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int)`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int)) instead. +- Removed + [`BillingFlowParams.SubscriptionUpdateParams.Builder#setOldSkuPurchaseToken()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setOldSkuPurchaseToken(java.lang.String)). + + - Developers should use [`BillingFlowParams.SubscriptionUpdateParams.Builder#setOldPurchaseToken(java.lang.String)`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setOldPurchaseToken(java.lang.String)) instead. +- [`BillingClient.queryPurchaseHistoryAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,com.android.billingclient.api.PurchaseHistoryResponseListener)) + has been deprecated and will be removed in a future release. Developers + should use the following alternatives instead: + + - Acknowledged and pending purchases: Use [`BillingClient.queryPurchasesAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,%20com.android.billingclient.api.PurchasesResponseListener)) to fetch the active purchases. + - Consumed purchases: Developers should keep track of consumed purchases on their own servers. + - Canceled purchases: Use the [voided-purchases](https://developers.google.com/android-publisher/voided-purchases) developer API. + - For more details, see [Query Purchase History](https://developer.android.com/google/play/billing/query-purchase-history) +- [`BillingFlowParams.ProductDetailsParams.setOfferToken()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProductDetailsParams.Builder#setOfferToken(java.lang.String)) + now throws an exception when developers specify an empty `offerToken`. + +- Updated `minSdkVersion` to 21 and `targetSdkVersion` to 34. + +## Google Play Billing Library 6.2.1 Release (2024-04-16) + +Version 6.2.1 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Fixed a bug in [`BillingClient.showAlternativeBillingOnlyInformationDialog()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showAlternativeBillingOnlyInformationDialog(android.app.Activity,%20com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener)) where the [`AlternativeBillingOnlyInformationDialogListener`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingOnlyInformationDialogListener) may not be called in certain cases when the dialog completes. + +## Google Play Billing Library 6.2.0 Release (2024-03-06) + +Version 6.2.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added APIs to support [external offers](https://developer.android.com/google/play/billing/external) + - Added [`BillingClient.Builder.enableExternalOffer()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableExternalOffer()) to enable the ability to provide external offers. + - Added [`BillingClient.isExternalOfferAvailableAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isExternalOfferAvailableAsync(com.android.billingclient.api.ExternalOfferAvailabilityListener)) to check the availability of providing external offers functionality. + - Added [`BillingClient.showExternalOfferInformationDialog()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showExternalOfferInformationDialog(android.app.Activity,%20com.android.billingclient.api.ExternalOfferInformationDialogListener)) to show an information dialog to users before leading users outside the app. + - Added [`BillingClient.createExternalOfferReportingDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#createExternalOfferReportingDetailsAsync(com.android.billingclient.api.ExternalOfferReportingDetailsListener)) to create a payload required to report transactions made through external offers. + +## Google Play Billing Library 6.1.0 Release (2023-11-14) + +Version 6.1.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added APIs to support [alternative billing only (i.e. without user choice)](https://developer.android.com/google/play/billing/alternative) + - Added [`BillingClient.Builder.enableAlternativeBillingOnly()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableAlternativeBillingOnly()) to functionally enable the ability to offer alternative billing only. + - Added [`BillingClient.isAlternativeBillingOnlyAvailableAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isAlternativeBillingOnlyAvailableAsync(com.android.billingclient.api.AlternativeBillingOnlyAvailabilityListener)) to check the availability of offering alternative billing only. + - Added [`BillingClient.showAlternativeBillingOnlyInformationDialog()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showAlternativeBillingOnlyInformationDialog(android.app.Activity,%20com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener)) to show an information dialog to inform users when alternative billing only is being used. + - Added [`BillingClient.createAlternativeBillingOnlyReportingDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#createAlternativeBillingOnlyReportingDetailsAsync(com.android.billingclient.api.AlternativeBillingOnlyReportingDetailsListener)) to create a payload required to report transactions made through alternative billing only. +- Updated the user choice billing APIs + - Added [`UserChoiceBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceBillingListener) to replace [AlternativeBillingListener](https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingListener) which has been marked as deprecated. + - Added [`UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) to replace [`AlternativeChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeChoiceDetails) which has been marked as deprecated. + - Added [`BillingClient.Builder.enableUserChoiceBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableUserChoiceBilling(com.android.billingclient.api.UserChoiceBillingListener)) to replace [`BillingClient.Builder.enableAlternativeBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableAlternativeBilling(com.android.billingclient.api.AlternativeBillingListener)) which has been marked as deprecated. +- Added [`BillingClient.getBillingConfigAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#getBillingConfigAsync(com.android.billingclient.api.GetBillingConfigParams,%20com.android.billingclient.api.BillingConfigResponseListener)) to retrieve Google Play country. + +## Google Play Billing Library 6.0.1 Release (2023-06-22) + +Version 6.0.1 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +Update Play Billing Library to be compatible with Android 14. + +## Google Play Billing Library 6.0 Release (2023-05-10) + +Version 6.0.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added new [`ReplacementMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) + enum to replace + [`ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode). + + Note that `ProrationMode` is still available for backward compatibility + reasons. +- Removed order ID for [`PENDING`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PENDING) + purchases. + + Previously, the order ID would always be created even if the purchase was + pending. Starting with version 6.0.0, an order ID will not be created for + pending purchases, and for these purchases, the order ID will be populated + after the purchase is moved to the + [`PURCHASED`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#PURCHASED) + state. +- Removed `queryPurchases` and `launchPriceConfirmationFlow` methods. + + The `queryPurchases` and `launchPriceConfirmationFlow` methods that have + previously been marked as deprecated have now been removed in Play Billing + Library 6.0.0. Developers should use + [`queryPurchasesAsync`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,%20com.android.billingclient.api.PurchasesResponseListener)) + instead of `queryPurchases`. For `launchPriceConfirmationFlow` alternatives, + see [Price changes](https://developer.android.com/google/play/billing/price-changes). +- Added new network error response code. + + A new network error response code, + [`NETWORK_ERROR`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#NETWORK_ERROR), + has been added starting with PBL version 6.0.0. This code is returned when + an error occurs due to a network connection issue. These network connection + errors were previously reported as `SERVICE_UNAVAILABLE`. +- Updated [`SERVICE_UNAVAILABLE`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_UNAVAILABLE) + and + [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT). + + Starting with PBL version 6.0.0, errors due to timeout in processing will be + returned as `SERVICE_UNAVAILABLE` instead of the current `SERVICE_TIMEOUT`. + + The behavior does not change in earlier versions of PBL. +- Removed + [`SERVICE_TIMEOUT`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#SERVICE_TIMEOUT). + + Starting with PBL version 6.0.0, `SERVICE_TIMEOUT` will no longer be + returned. Previous versions of PBL will still return this code. +- Added additional logging. + + The Play Billing Library 6 release includes additional logging, which + provides insight into API usage (such as success and failure) and service + connection issues. This information will be used to improve the performance + of the Play Billing Library and provide better support for errors. + +## Google Play Billing Library 5.2.1 Release (2023-06-22) + +Version 5.2.1 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +Update Play Billing Library to be compatible with Android 14. + +## Google Play Billing Library 5.2 Release (2023-04-06) + +Version 5.2.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added classes to support alternative billing flows on mobile/tablet for users in South Korea: + - [`AlternativeBillingListener`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingListener) + - [`AlternativeChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeChoiceDetails) + - [`AlternativeChoiceDetails.Product`](https://developer.android.com/reference/com/android/billingclient/api/AlternativeChoiceDetails.Product) +- Added [`BillingFlowParams.SubscriptionUpdateParams.Builder.setOriginalExternalTransactionId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setOriginalExternalTransactionId(java.lang.String)) method to specify the external transaction id of the originating subscription. +- Added [`BillingClient.Builder.enableAlternativeBilling()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enableAlternativeBilling(com.android.billingclient.api.AlternativeBillingListener)) method to allow users in South Korea to select an alternative billing option. + +## Google Play Billing Library 5.1 Release (2022-10-31) + +Version 5.1.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +This version contains the following changes. + +### Summary of changes + +- Added [`ProductDetails.SubscriptionOfferDetails.getOfferId()`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.SubscriptionOfferDetails#getOfferId()) method to retrieve the offer ID. +- Added [`ProductDetails.SubscriptionOfferDetails.getBasePlanId()`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.SubscriptionOfferDetails#getBasePlanId()) method to retrieve the base plan ID. +- Updated the `targetSdkVersion` to 31. + +## Google Play Billing Library 5.0 Release (2022-05-11) + +Version 5.0.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +This version contains the following changes. + +### Summary of changes + +- Introduced a new model for subscriptions, including new entities that enable you to create multiple offers for a single subscription product. For more information, see the [migration guide](https://developer.android.com/google/play/billing/migrate-gpblv5). +- Added [`BillingClient.queryProductDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryProductDetailsAsync(com.android.billingclient.api.QueryProductDetailsParams,%20com.android.billingclient.api.ProductDetailsResponseListener)) to replace `BillingClient.querySkuDetailsAsync()`. +- Added `setIsOfferPersonalized()` method for EU personalized pricing disclosure requirements. To learn more about how to use this method, see [Indicate a personalized + price](https://developer.android.com/google/play/billing/integrate#personalized-price). +- Removed `queryPurchases()`, which was previously deprecated and replaced by queryPurchasesAsync introduced in Google Play Billing Library 4.0.0. +- `launchPriceChangeFlow` has been deprecated and will be removed in a future release. To learn more about alternatives, see [Launch a price change + confirmation flow](https://developer.android.com/google/play/billing/subscriptions#price-change-launch). +- Removed [`setVrPurchaseFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setVrPurchaseFlow(boolean)), which was previously used when instantiating a purchase flow. In previous versions, this method redirected the user to complete the purchase on their Android-powered device. Once you remove this method, users will complete the purchase through the standard purchase flow. + +## Google Play Billing Library 4.1 release (2022-02-23) + +Version 4.1.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +This version contains the following changes. + +### Summary of changes + +- Added [`BillingClient.showInAppMessages()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showInAppMessages(android.app.Activity,%20com.android.billingclient.api.InAppMessageParams,%20com.android.billingclient.api.InAppMessageResponseListener)) to help with handling subscription payment declines. To learn more about how to use in-app messaging for handling subscriptions payment declines, see [Handling payment + declines](https://developer.android.com/google/play/billing/subscriptions#payment-declines). + +## Google Play Billing Library 4.0 Release (2021-05-18) + +Version 4.0.0 of the Google Play Billing Library and Kotlin extensions are now +available. + +### Summary of changes + +- Added + [`BillingClient.queryPurchasesAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(java.lang.String,%20com.android.billingclient.api.PurchasesResponseListener)) + to replace + [`BillingClient.queryPurchases()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchases(java.lang.String)) + which will be removed in a future release. + +- Added new subscription replacement mode + [`IMMEDIATE_AND_CHARGE_FULL_PRICE`](https://developer.android.com/google/play/billing/subscriptions#change). + +- Added + [`BillingClient.getConnectionState()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#getConnectionState()) + method to retrieve the Play Billing Library's connection state. + +- Updated Javadoc and implementation to indicate which thread a method can be + called on and which thread results are posted. + +- Added + [`BillingFlowParams.Builder.setSubscriptionUpdateParams()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setSubscriptionUpdateParams(com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams)) + as a new way to initiate subscription updates. This replaces + `BillingFlowParams#getReplaceSkusProrationMode`, + `BillingFlowParams#getOldSkuPurchaseToken`, `BillingFlowParams#getOldSku`, + `BillingFlowParams.Builder#setReplaceSkusProrationMode`, + `BillingFlowParams.Builder#setOldSku` which have been removed. + +- Added + [`Purchase.getQuantity()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getQuantity()) + and + [`PurchaseHistoryRecord.getQuantity()`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord#getQuantity()). + +- Added + [`Purchase#getSkus()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getSkus()) + and + [`PurchaseHistoryRecord#getSkus()`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord#getSkus()). + These replace `Purchase#getSku` and `PurchaseHistoryRecord#getSku` which + have been removed. + +- Removed `BillingFlowParams#getSku`, `BillingFlowParams#getSkuDetails` and + `BillingFlowParams#getSkuType`. + +## Google Play Billing Library 3.0.3 Release (2021-03-12) + +Version 3.0.3 of the Google Play Billing Library, Kotlin extension, and Unity +plugin are now available. + +### Java and Kotlin Bug fixes + +- Fix memory leak when [`endConnection()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#endConnection()) is called. +- Fix issue when the Google Play Billing Library is used by apps which use the single task launch mode. A [`onPurchasesUpdated()`](https://developer.android.com/reference/com/android/billingclient/api/PurchasesUpdatedListener#onpurchasesupdated) callback will be triggered when an app is resumed from the Android launcher and the billing dialog was visible prior to being suspended. + +### Unity Bug fixes + +- Update to Java version 3.0.3 to fix memory leak and resolve issue preventing purchases when an app is resumed from the Android launcher and the billing dialog was visible prior to being suspended. + +## Google Play Billing Library 3.0.2 Release (2020-11-24) + +Version 3.0.2 of the Google Play Billing Library and Kotlin extension are now +available. + +### Bug fixes + +- Fixed a bug in the Kotlin extension where the coroutine fails with error "Already resumed". +- Fixed unresolved references when the Kotlin extension is used with the kotlinx.coroutines library version 1.4+. + +## Google Play Billing Library 3.0.1 Release (2020-09-30) + +Version 3.0.1 of the Google Play Billing Library and Kotlin extension are now +available. + +### Bug fixes + +- Fixed a bug where if the app was killed and restored during the billing flow, `PurchasesUpdatedListener` may not be called with the purchase result. + +## Google Play Billing Library 3.0 Release (2020-06-08) + +Version 3.0.0 of the Google Play Billing Library, Kotlin extension, and Unity +plugin are now available. + +### Summary of changes + +- Removed rewarded SKU support. +- Removed the `ChildDirected` and `UnderAgeOfConsent` parameters. +- Removed deprecated developer payload methods. +- Removed deprecated methods `BillingFlowParams.setAccountId()` and `BillingFlowParams.setDeveloperId()`. +- Removed deprecated methods `BillingFlowParams.setOldSkus(String oldSku)` and `BillingFlowParams.addOldSku(String oldSku)`. +- Added nullability annotations. + +### Bug fixes + +- [`SkuDetails.getIntroductoryPriceCycles()`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails#getIntroductoryPriceCycles()) now returns `int` instead of `String`. +- Fixed a bug where the billing flow would be treated as having extra params even if no extra params were set. + +## Google Play Billing Library 2.2.1 Release (2020-05-20) + +Version 2.2.1 of the Google Play Billing library is now available. + +### Bug fixes + +- Updated the default version of the Java Play Billing library that the Kotlin extension depends on. + +## Google Play Billing Library 2.2.0 release and Unity support (2020-03-23) + +Version 2.2.0 of the Google Play Billing provides functionality that helps +developers ensure purchases are attributed to the correct user. These changes +replace the need to build custom solutions based on developer payload. As part +of this update, the developer payload functionality has been deprecated and will +be removed in a future release. For more information, including recommended +alternatives, see [Developer payload](https://developer.android.com/google/play/billing/developer-payload). + +### Google Play Billing Billing Library 2 for Unity + +In addition to the current Java and Kotlin versions of Google Play Billing +Library 2, we released a version of the library for use with Unity. Game +developers using the Unity in-app purchase API can upgrade now to take advantage +of all Google Play Billing Library 2 features and to make the subsequent +upgrades to future versions of the Google Play Billing Library easier. + +To learn more, see [Use Google Play Billing with +Unity](https://developer.android.com/google/play/billing/unity). + +### Summary of changes + +- Java Google Play Billing Library + - In [`AcknowledgePurchaseParams`](https://developer.android.com/reference/com/android/billingclient/api/AcknowledgePurchaseParams), deprecated [`setDeveloperPayload()`](https://developer.android.com/reference/com/android/billingclient/api/AcknowledgePurchaseParams.Builder#setDeveloperPayload(java.lang.String)) and [`getDeveloperPayload()`](https://developer.android.com/reference/com/android/billingclient/api/AcknowledgePurchaseParams#getDeveloperPayload()) methods. + - In [`ConsumeParams`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeParams), deprecated [`setDeveloperPayload()`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeParams.Builder#setDeveloperPayload(java.lang.String)) and [`getDeveloperPayload()`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeParams#getdeveloperpayload) methods. + - In [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder), renamed [`setAccountId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setAccountId(java.lang.String)) to [`setObfuscatedAccountId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId(java.lang.String)), and documented length restriction of 64 characters and restriction disallowing Personally Identifiable Information (PII) in this field. [`setAccountId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setaccountid) has been marked as deprecated and will be removed in a future library version. + - In `BillingFlowParams`, added [`setObfuscatedProfileId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setobfuscatedprofileid) which works similarly to [`setObfuscatedAccountId()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId(java.lang.String)). For more information, see [Developer payload updates and + alternatives](https://developer.android.com/google/play/billing/developer-payload). + - In [`Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase), added the [`getAccountIdentifiers()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getAccountIdentifiers()) method to return the obfuscated account identifiers set in `BillingFlowParams`. + - In [`BillingClient`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient), the [`loadRewardedSku()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#loadRewardedSku(com.android.billingclient.api.RewardLoadParams,%20com.android.billingclient.api.RewardResponseListener)) method has been marked deprecated as part of deprecating rewarded SKUs. You can find more information about the deprecation in the [Play Console + Help + Center](https://support.google.com/googleplay/android-developer/answer/9155268). + +## Google Play Billing Library 2.1.0 Release and Kotlin Extension 2.1.0 Release (2019-12-10) + +Version 2.1.0 of the Google Play Billing library and the new Kotlin extension +are now available. The Play Billing Library Kotlin extension provides idiomatic +API alternatives for Kotlin consumption, featuring better null-safety and +coroutines. For code examples, see [Use the Google Play Billing +Library](https://developer.android.com/google/play/billing/billing_library_overview). + +This version contains the following changes. + +### Summary of changes + +- In [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams), deprecated `setOldSku(String oldSku)` and replaced with `setOldSku(String + oldSku, String purchaseToken)`, to disambiguate when multiple accounts on the device own the same sku. + +## Google Play Billing Library 2.0.3 Release (2019-08-05) + +Version 2.0.3 of the Google Play Billing library is now available. + +### Bug fixes + +- Fixed a bug where `querySkuDetailsAsync()` would occasionally fail with code `DEVELOPER_ERROR` instead of returning a successful result. + +## Google Play Billing Library 2.0.2 Release (2019-07-08) + +Version 2.0.2 of the Google Play Billing library is now available. This release +contains updates to the reference documentation and does not change library +functionality. + +## Google Play Billing Library 2.0.1 Release (2019-06-06) + +Version 2.0.1 of the Google Play Billing library is now available. This version +contains the following changes. + +### Bug fixes + +- Fixed a bug where debug messages were being returned as `null` in some cases. +- Fixed a potential memory leak issue. + +## Google Play Billing Library 2.0 Release (2019-05-07) + +Version 2.0 of the Google Play Billing library is now available. This version +contains the following changes. + +### Purchases must be acknowledged within three days + +> [!NOTE] +> **Note:** This section describes a new requirement for acknowledging all purchases. If you are already consuming purchases using `consumeAsync()`, you don't need to make any further changes if you consume purchases within 3 days, as `consumeAsync()` automatically acknowledges a purchase. Failure to properly acknowledge purchases will result in purchases being refunded. + +> [!NOTE] +> **Note:** This requirement applies only to apps that use the Google Play Billing Library version 2.0 and newer. This requirement doesn't apply if you use an older version of the Google Play Billing Library, or if you use the AIDL API. + +Google Play supports purchasing products from inside of your app (in-app) or +outside of your app (out-of-app). In order for Google Play to ensure a +consistent purchase experience regardless of where the user purchases your +product, you must acknowledge all purchases received through the Google Play +Billing Library as soon as possible after granting entitlement to the user. If +you don't acknowledge a purchase within three days, the user automatically +receives a refund, and Google Play revokes the purchase. For [pending +transactions](https://developer.android.com/google/play/billing/release-notes#2_0_pending) (new in version 2.0), the three-day window starts +when the purchase has moved to the `PURCHASED` state and does not apply while +the purchase is in a `PENDING` state. + +For subscriptions, you must acknowledge any purchase that has a new purchase +token. This means that all initial purchases, plan changes, and re-signups need +to be acknowledged, but you don't need to acknowledge subsequent renewals. To +determine if a purchase needs acknowledgment, you can check the acknowledgement +field in the purchase. + +The `Purchase` object now includes an +[`isAcknowledged()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#isacknowledged) +method that indicates whether a purchase has been acknowledged. In addition, the +Google Play Developer API includes acknowledgement boolean values for both +[`Purchases.products`](https://developers.google.com/android-publisher/api-ref/purchases/products) +and +[`Purchases.subscriptions`](https://developers.google.com/android-publisher/api-ref/purchases/subscriptions). +Before acknowledging a purchase, be sure to use these methods to determine if +the purchase has already been acknowledged. + +You can acknowledge a purchase by using one of the following methods: + +- For consumable products, use `consumeAsync()`, found in the client API. +- For products that aren't consumed, use `acknowledgePurchase()`, found in the client API. +- A new `acknowledge()` method is also available in the Server API. + +### BillingFlowParams.setSku() has been removed + +The previously-deprecated `BillingFlowParams#setSku()` method has been removed +in this release. Before rendering products in a purchase flow, you must now call +[`BillingClient.querySkuDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryskudetailsasync), +passing the resulting +[`SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails) object to +[`BillingFlowParams.Builder.setSkuDetails()`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setskudetails). + +> [!NOTE] +> **Note:** Caching `SkuDetails` between user sessions is not recommended, as `SkuDetails` objects are valid only for a limited time before you must refresh them again by calling `querySkuDetailsAsync()`. + +For code examples, see [Use the Google Play Billing +Library](https://developer.android.com/google/play/billing/billing_library_overview). + +### Developer payload is supported + +Version 2.0 of the Google Play Billing library adds support for *developer +payload* ---arbitrary strings that can be attached to purchases. You can +attach a developer payload parameter to a purchase, but only when the purchase +is acknowledged or consumed. This is unlike developer payload in AIDL, where the +payload could be specified when launching the purchase flow. Because purchases +can now be initiated [from outside of your app](https://developer.android.com/google/play/billing/release-notes#2_0_acknowledge), this change +ensures that you always have an opportunity to add a payload to purchases. + +To access the payload in the new library, `Purchase` objects now include a +[`getDeveloperPayload()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getdeveloperpayload) +method. + +> [!NOTE] +> **Note:** You cannot modify a payload after it is assigned. + +### Consistent offers + +> [!NOTE] +> **Note:** This feature is being tested, and general availability is not guaranteed. + +When you offer a discounted SKU, Google Play now returns the original price of +the SKU so that you can show users that they are receiving a discount. + +[`SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails) contains two +new methods for retrieving the original SKU price: + +- [`getOriginalPriceAmountMicros()`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails#getOriginalPriceAmountMicros()) + - returns the unformatted original price of the SKU before discount. +- [`getOriginalPrice()`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails#getOriginalPrice()) + - returns the original price with additional currency formatting. + +### Pending transactions + +With version 2.0 of the Google Play Billing library, you *must* support +purchases where additional action is required before granting entitlement. For +example, a user might choose to purchase your in-app product at a physical store +using cash. This means that the transaction is completed outside of your app. In +this scenario, you should grant entitlement only after the user has completed +the transaction. + +To enable pending purchases, call +[`enablePendingPurchases()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablependingpurchases) +as part of initializing your app. + +Use +[`Purchase.getPurchaseState()`](https://developer.android.com/reference/com/android/billingclient/api/Purchase#getpurchasestate) +to determine whether the purchase state is `PURCHASED` or `PENDING`. Note that +you should grant entitlement only when the state is `PURCHASED`. You should +check for `Purchase` status updates by doing the following: + +1. When starting your app, call [`BillingClient.queryPurchases()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchases) to retrieve the list of unconsumed products associated with the user. +2. Call `Purchase.getPurchaseState()` on each returned `Purchase` object. +3. Implement the [`onPurchasesUpdated()`](https://developer.android.com/reference/com/android/billingclient/api/PurchasesUpdatedListener#onpurchasesupdated) method to respond to changes to `Purchase` objects. + +In addition, the Google Play Developer API includes a `PENDING` state for +[`Purchases.products`](https://developers.google.com/android-publisher/api-ref/purchases/products). +Pending transactions are not supported for subscriptions. + +This release also introduces a new real-time developer notification type, +`OneTimeProductNotification`. This notification type contains a single message +whose value is either `ONE_TIME_PRODUCT_PURCHASED` or +`ONE_TIME_PRODUCT_CANCELED`. This notification type is sent only for purchases +associated with delayed forms of payment, such as cash. + +When acknowledging pending purchases, be sure to acknowledge only when the +purchase state is `PURCHASED` and not `PENDING`. + +> [!NOTE] +> **Note:** Pending transactions can be tested using license testers. In addition to two test credit cards, license testers have access to two new test instruments for delayed forms of payment which automatically complete or cancel after a couple of minutes. While testing your application, you should verify that your application does not grant entitlement or acknowledge the purchase immediately after purchasing with either of these two new instruments. When purchasing using the new test instrument that automatically completes, you should verify that your application grants entitlement and acknowledges the purchase once the purchase completes. + +### API changes + +Version 2.0 of the Google Play Billing library contains several API changes to +support new features and clarify existing functionality. + +#### consumeAsync + +[`consumeAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#consumeasync) +now takes a +[`ConsumeParams`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeParams) object +instead of a `purchaseToken`. `ConsumeParams` contains the `purchaseToken` as +well as an optional developer payload. + +The previous version of `consumeAsync()` has been removed in this release. + +#### queryPurchaseHistoryAsync + +To minimize confusion, +[`queryPurchaseHistoryAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(java.lang.String,%20com.android.billingclient.api.PurchaseHistoryResponseListener)) +now returns a +[`PurchaseHistoryRecord`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord) +object instead of a `Purchase` object. The `PurchaseHistoryRecord` object is the +same as a `Purchase` object, except that it reflects only the values returned by +`queryPurchaseHistoryAsync()` and does not contain the `autoRenewing`, +`orderId`, and `packageName` fields. Note that nothing has changed with the +returned data---`queryPurchaseHistoryAsync()` returns the same data as +before. + +#### BillingResult return values + +APIs that previously returned a `BillingResponse` integer value now return a +[`BillingResult`](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) +object. `BillingResult` contains the `BillingResponse` integer as well as a +debug string that you can use to diagnose errors. The debug string uses an en-US +locale and is not meant to be shown to end users. + +### Bug fixes + +- [`SkuDetails.getIntroductoryPriceAmountMicros()`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails#getIntroductoryPriceAmountMicros()) now returns a `long` instead of a `String`. + +## Google Play Billing Library 1.2.2 Release (2019-03-07) + +Version 1.2.2 of the Google Play Billing library is now available. This version +contains the following changes. + +### Bug fixes + +- Fixed a threading issue introduced in v1.2.1. Background calls no longer block the main thread. + +### Other changes + +- Although using the main thread is still recommended, you can now instantiate the Google Play Billing Library from a background thread. +- Instantiation has been fully migrated to the background thread to reduce the chance of causing ANRs. + +## Play Billing Library 1.2.1 Release (2019-03-04) + +Version 1.2.1 of the Google Play Billing library is now available. This version +contains the following changes. + +### Major changes + +- Added support for [rewarded + products](https://developer.android.com/google/play/billing/billing_rewarded_products). For more information on monetization options, see [Add rewarded-product-specific + features](https://developer.android.com/distribute/best-practices/earn/monetization-options). + +### Other changes + +- Added public constructors for `PurchasesResult` and `SkuDetailsResult` to make testing easier. +- `SkuDetails` objects can use a new method, `getOriginalJson()`. +- All AIDL service calls are now handled by background threads. + +### Bug fixes + +- Null callback listeners are no longer passed into public APIs. + +## Google Play Billing Library 1.2 Release (2018-10-18) + +Version 1.2 of the Google Play Billing library is now available. This version +contains the following changes. + +### Summary of changes + +- The Google Play Billing Library is now licensed under the [Android Software + Development Kit License Agreement](https://developer.android.com/studio/terms). +- Added the `launchPriceChangeConfirmationFlow` API, which prompts users to review a pending change to a subscription price. +- Added support for a new proration mode, `DEFERRED`, when upgrading or downgrading a user's subscription. +- In the `BillingFlowParams` class, replaced `setSku()` with `setSkuDetails()`. +- Minor bug fixes and code optimizations. + +#### Price change confirmation + +You can now change the price of a subscription in Google Play Console +and prompt users to review and accept the new price when they enter your app. + +To use this API, create a `PriceChangeFlowParams` object by using the +`skuDetails` of the subscription product, and then call +`launchPriceChangeConfirmationFlow()`. Implement the +`PriceChangeConfirmationListener` to handle the result when the price change +confirmation flow finishes, as shown in the following code snippet: + +### Kotlin + +```kotlin +val priceChangeFlowParams = PriceChangeFlowParams.newBuilder() + .setSkuDetails(skuDetailsOfThePriceChangedSubscription) + .build() + +billingClient.launchPriceChangeConfirmationFlow(activity, + priceChangeFlowParams, + object : PriceChangeConfirmationListener() { + override fun onPriceChangeConfirmationResult(responseCode: Int) { + // Handle the result. + } + }) +``` + +### Java + +```java +PriceChangeFlowParams priceChangeFlowParams = + PriceChangeFlowParams.newBuilder() + .setSkuDetails(skuDetailsOfThePriceChangedSubscription) + .build(); + +billingClient.launchPriceChangeConfirmationFlow(activity, + priceChangeFlowParams, + new PriceChangeConfirmationListener() { + @Override + public void onPriceChangeConfirmationResult(int responseCode) { + // Handle the result. + } + }); +``` + +The price change confirmation flow displays a dialog containing the new pricing +information, asking users to accept the new price. This flow returns a response +code of type +[`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse). + +#### New proration mode + +When upgrading or downgrading a user's subscription, you can use a new proration +mode, `DEFERRED`. This mode updates the user's subscription when it next renews. +To learn more about how to set this proration mode, see [Set proration +mode](https://developer.android.com/google/play/billing/billing_subscriptions#set-proration-mode). + +#### New method for setting SKU details + +In the `BillingFlowParams` class, the `setSku()` method has been deprecated. +This change serves to optimize the Google Play Billing flow. + +When constructing a new instance of `BillingFlowParams` in your in-app billing +client, we recommend that you instead work with the JSON object directly using +`setSkuDetails()`, as shown in the following code snippet: + +In the `BillingFlowParams` Builder class, the `setSku()` method has been +deprecated. Instead, use the `setSkuDetails()` method, as shown in the following +code snippet. The object passed into `setSkuDetails()` object comes from the +[`querySkuDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querySkuDetailsAsync(com.android.billingclient.api.SkuDetailsParams,%20com.android.billingclient.api.SkuDetailsResponseListener)) +method. + +### Kotlin + +```kotlin +private lateinit var mBillingClient: BillingClient +private val mSkuDetailsMap = HashMap() + +private fun querySkuDetails() { + val skuDetailsParamsBuilder = SkuDetailsParams.newBuilder() + mBillingClient.querySkuDetailsAsync(skuDetailsParamsBuilder.build() + ) { responseCode, skuDetailsList -> + if (responseCode == 0) { + for (skuDetails in skuDetailsList) { + mSkuDetailsMap[skuDetails.sku] = skuDetails + } + } + } +} + +private fun startPurchase(skuId: String) { + val billingFlowParams = BillingFlowParams.newBuilder() + .setSkuDetails(mSkuDetailsMap[skuId]) + .build() +} +``` + +### Java + +```java +private BillingClient mBillingClient; +private Map mSkuDetailsMap = new HashMap<>(); + +private void querySkuDetails() { + SkuDetailsParams.Builder skuDetailsParamsBuilder + = SkuDetailsParams.newBuilder(); + mBillingClient.querySkuDetailsAsync(skuDetailsParamsBuilder.build(), + new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(int responseCode, + List skuDetailsList) { + if (responseCode == 0) { + for (SkuDetails skuDetails : skuDetailsList) { + mSkuDetailsMap.put(skuDetails.getSku(), skuDetails); + } + } + } + }); +} + +private void startPurchase(String skuId) { + BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() + .setSkuDetails(mSkuDetailsMap.get(skuId)) + .build(); +} +``` + +## Play Billing Library 1.1 Release (2018-05-07) + +Version 1.1 of the Google Play Billing library is now available. This version +contains the following changes. + +### Summary of changes + +- Added support to specify a proration mode in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) when upgrading/downgrading an existing subscription. +- The `replaceSkusProration` boolean flag in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) is no longer supported. Use `replaceSkusProrationMode` instead. +- [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) now triggers a callback for failed responses. + +### Behavior changes + +Version 1.1 of the Google Play Billing library contains the following behavior +changes. + +#### Developers can set `replaceSkusProrationMode` in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) class + +A `ProrationMode` provides further details on the type of proration when +upgrading or downgrading a user's subscription. + +### Kotlin + +```kotlin +BillingFlowParams.newBuilder() + .setSku(skuId) + .setType(billingType) + .setOldSku(oldSku) + .setReplaceSkusProrationMode(replaceSkusProrationMode) + .build() +``` + +
+ +
+ +### Java + +```java +BillingFlowParams.newBuilder() + .setSku(skuId) + .setType(billingType) + .setOldSku(oldSku) + .setReplaceSkusProrationMode(replaceSkusProrationMode) + .build(); +``` + +
+ +Google Play supports following proration modes: + +|---|---| +| `IMMEDIATE_WITH_TIME_PRORATION` | Replacement takes effect immediately, and the new expiration time will be prorated and credited or charged to the user. This is the current default behavior. | +| `IMMEDIATE_AND_CHARGE_PRORATED_PRICE` | Replacement takes effect immediately, and the billing cycle remains the same. The price for the remaining period will be charged. **Note**: This option is only available for subscription upgrade. | +| `IMMEDIATE_WITHOUT_PRORATION` | Replacement takes effect immediately, and the new price will be charged on next recurrence time. The billing cycle stays the same. | + +### `replaceSkusProration` is no longer supported in [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.html) class + +Developers used to be able to set a boolean flag to charge a prorated amount for +a subscription upgrade request. Given that we are supporting `ProrationMode`, +which contains more detailed proration instruction, this boolean flag is no +longer supported. + +### [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%20com.android.billingclient.api.BillingFlowParams)) now triggers a callback for failed responses + +The Billing Library will always trigger the +[`PurhcasesUpdatedListener`](https://developer.android.com/reference/com/android/billingclient/api/PurchasesUpdatedListener) +callback and return a +[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse) +asynchronously. The synchronous return value of +[`BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse) +is kept as well. + +### Bug fixes + +- Properly exits early in async methods when service is disconnected. +- [`Builder`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder) param objects no longer mutates built objects. +- Issue [68087141](https://issuetracker.google.com/issues/68087141): [`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) now trigger callback for failed responses. + +## Google Play Billing Library 1.0 Release (2017-09-19, [Announcement](https://android-developers.googleblog.com/2017/09/google-play-billing-library-10-released.html)) + +Version 1.0 of the Google Play Billing library is now available. This version +contains the following changes. + +### Important changes + +- Embedded billing permission inside library's manifest. It's not necessary to add the `com.android.vending.BILLING` permission inside Android manifest anymore. +- New builder added to [`BillingClient.Builder`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html) class. +- Introduced builder pattern for [`SkuDetailsParams`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetailsParams.html) class to be used on methods to query SKUs. +- Several API methods were updated for consistency (the same return argument names and order). + +### Behavior changes + +Version 1.0 of the Google Play Billing library contains the following behavior +changes. + +#### BillingClient.Builder class + +[`BillingClient.Builder`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html) +is now initialized via the newBuilder pattern: + +### Kotlin + +```kotlin +billingClient = BillingClient.newBuilder(context).setListener(this).build() +``` + +### Java + +```java +billingClient = BillingClient.newBuilder(context).setListener(this).build(); +``` + +#### launchBillingFlow method is now called using a BillingFlowParams class + +To initiate the billing flow for a purchase or subscription, the +[`launchBillingFlow()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#launchBillingFlow(android.app.Activity,%0Acom.android.billingclient.api.BillingFlowParams)) method receives a +[`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.html) +instance initialized with parameters specific to the request: + +### Kotlin + +```kotlin +BillingFlowParams.newBuilder().setSku(skuId) + .setType(billingType) + .setOldSku(oldSku) + .build() + +// Then, use the BillingFlowParams to start the purchase flow +val responseCode = billingClient.launchBillingFlow(builder.build()) +``` + +### Java + +```java +BillingFlowParams.newBuilder().setSku(skuId) + .setType(billingType) + .setOldSku(oldSku) + .build(); + +// Then, use the BillingFlowParams to start the purchase flow +int responseCode = billingClient.launchBillingFlow(builder.build()); +``` + +#### New way to query available products + +Arguments for +[`queryPurchaseHistoryAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#queryPurchaseHistoryAsync(java.lang.String,%0Acom.android.billingclient.api.PurchaseHistoryResponseListener)) and +[`querySkuDetailsAsync()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#querySkuDetailsAsync(com.android.billingclient.api.SkuDetailsParams,%0Acom.android.billingclient.api.SkuDetailsResponseListener)) methods were wrapped +into a Builder pattern: + +### Kotlin + +```kotlin +val params = SkuDetailsParams.newBuilder() +params.setSkusList(skuList) + .setType(itemType) +billingClient.querySkuDetailsAsync(params.build(), object : SkuDetailsResponseListener() { + ... +}) +``` + +### Java + +```java +SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); +params.setSkusList(skuList) + .setType(itemType); +billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {...}) +``` + +The result is now returned via result code and a list of +[`SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails.html) +objects instead of previous wrapper class for your convenience and to be +consistent across our API: + +### Kotlin + +```kotlin +fun onSkuDetailsResponse(@BillingResponse responseCode: Int, skuDetailsList: List) +``` + +### Java + +```java +public void onSkuDetailsResponse(@BillingResponse int responseCode, List skuDetailsList) +``` + +#### Parameters order changed on `onConsumeResponse()` method + +The order of arguments for +[`onConsumeResponse`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeResponseListener.html#onConsumeResponse(int,%0Ajava.lang.String)) from the +[`ConsumeResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/ConsumeResponseListener.html) +interface has changed to be consistent across our API: + +### Kotlin + +```kotlin +fun onConsumeResponse(@BillingResponse responseCode: Int, outToken: String) +``` + +### Java + +```java +public void onConsumeResponse(@BillingResponse int responseCode, String outToken) +``` + +#### Unwrapped PurchaseResult object + +[`PurchaseResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult.html) +has been unwraped to be consistent across our API: + +### Kotlin + +```kotlin +fun onPurchaseHistoryResponse(@BillingResponse responseCode: Int, purchasesList: List) +``` + +### Java + +```java +void onPurchaseHistoryResponse(@BillingResponse int responseCode, List purchasesList) +``` + +### Bug fixes + +- [No response code in PURCHASES_UPDATED Bundle](https://issuetracker.google.com/issues/64075043) +- [Fix ProxyBillingActivity and PurchasesUpdatedListener issues during device + rotation](https://issuetracker.google.com/issues/63266562) + +## Developer Preview 1 Release (2017-06-12, [Announcement](https://android-developers.googleblog.com/2017/06/money-made-easily-with-new-google-play.html)) + +Developer preview launched, aimed to simplify the development process when it +comes to billing, allowing developers to focus their efforts on implementing +logic specific to the Android app, such as application architecture and +navigation structure. + +The library includes several convenient classes and features for you to use when +integrating your Android apps with the Google Play Billing API. The library also +provides an abstraction layer on top of the Android Interface Definition +Language (AIDL) service, making it easier for developers to define the interface +between the app and the Google Play Billing API. \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/references/migration-logic.md b/play/play-billing-library-version-upgrade/references/migration-logic.md new file mode 100644 index 0000000..227a537 --- /dev/null +++ b/play/play-billing-library-version-upgrade/references/migration-logic.md @@ -0,0 +1,78 @@ +## High-Impact Migration Logic + +### 1. Connection Management (The v8+ Reconnection Shift) + +**Intent**: Move from developer-managed state (manual retries) to +library-managed state. + +- **Remove** : Manual `startConnection()` calls or retry timers inside `onServiceDisconnected()`. +- **Add** : `.enableAutoServiceReconnection()` to `BillingClient.Builder`. +- **Logic** : In v8+, the library handles transient disconnections. Your `onServiceDisconnected` must only be used for logging or updating UI state (e.g., "Billing service temporarily unavailable"). + +### 2. Product Querying \& Models (The v5-v8 Architectural Shift) + +**Intent**: Support the "One Product, Multiple Offers" model introduced in v5 +and refined in v8. + +- **Data Model Swap** : + - **Legacy** : `SkuDetails` (1:1 mapping of ID to price). + - **Modern** : `ProductDetails`. A single `ProductDetails` can contain multiple `SubscriptionOfferDetails` (Base Plans + Offers). +- **Result Handling (v8+ Logic)** : + - **Change** : `queryProductDetailsAsync` no longer returns a list in the listener. + - **New Intent** : You must receive a `QueryProductDetailsResult` object. + - **Refactor** : `kotlin + // PBL 8+ Pattern + billingClient.queryProductDetailsAsync(params) { result: QueryProductDetailsResult -> + val responseCode = result.billingResult.responseCode + val productDetailsList = result.productDetailsList // Retrieve list from result object + // Process list... + }` + +### 3. Subscription Modernization (v6 \& v7) + +**Intent**: Support "Base Plans" and "Offers" instead of legacy standalone SKUs. + +- **Subscription Upgrades/Downgrades (v6+)**: + + - **Legacy** : `setOldSkuPurchaseToken()` in `BillingFlowParams`. + - **Modern** : Use `SubscriptionUpdateParams`. You must specify the `PurchaseToken` of the existing subscription and the `ReplacementMode` (which replaces the deprecated `ProrationMode`). + - **Logic** : Verify that the `ReplacementMode` matches the business intent (e.g., `CHARGE_FULL_PRICE` versus `WITH_TIME_PRORATION`). +- **Installment Plans (v7+)**: + + - **Intent**: Allow users to pay for a subscription in monthly installments. + - **Check** : Look for `InstallmentPlanDetails` within `SubscriptionOfferDetails`. If the app supports high-ticket subscriptions, it is mandatory to implement the `installmentPlanDetails` UI. + +### 4. Purchase Handling \& History (v6+) + +**Intent**: Move away from local-only purchase caches to real-time status +checks. + +- **Active Purchases** : + - **Deprecated** : `queryPurchases()` (synchronous). + - **Mandatory** : `queryPurchasesAsync()`. You must pass `QueryPurchasesParams` containing the `ProductType` (`INAPP` or `SUBS`). +- **Purchase History (Pagination Intent)** : + - **v6+ Change** : `queryPurchaseHistoryAsync` is optimized for pagination. + - **Logic** : If the app has thousands of historical transactions, verify you are using the `PurchaseHistoryRecord` list correctly to avoid memory overhead. + +### 5. Security \& Pending Transactions (The "Always On" Rule) + +- **Mandatory** : `enablePendingPurchases()` has been required since v3, but in v8+, verify that it is called before `.build()`. You must also include `.enableOneTimeProducts()` on the `enablePendingPurchases()` builder. +- **Optional** : If the app sells prepaid subscriptions, you must also include `.enablePrepaidPlans()`. +- **Intent**: This handles "Slow/Delayed" payments (like cash or bank transfers). Without this, the app will crash on initialization in modern versions. + +### 6. SDK \& Environment Requirements + +- **PBL 7.0** : Requires `compileSdk 34` or higher. +- **PBL 8.0** : Requires `compileSdk 34` or higher. +- **PBL 9.0** : Requires `compileSdk 35` or higher. +- **Kotlin** : Verify that `kotlin-stdlib` is updated to at least 1.9.x to support new library coroutine extensions. + +### 7. User-Facing Features (Post-Upgrade Recommendations) + +Once the upgrade is complete, the following features are enabled by these +versions: + +- **v7** : **Installments** (Monthly payments for annual plans). +- **v8** : **Prepaid Plans** (Users can top-up time without auto-renewing). +- **v8** : **Personalized Pricing** (Show legal disclosure if price varies by user). +- **v9** : **In-App Price Reviews** (Users can accept upcoming subscription price increases directly inside the app without leaving for the Play Store; capped by Google at a maximum of once every 7 days). \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/references/version-checklist.md b/play/play-billing-library-version-upgrade/references/version-checklist.md new file mode 100644 index 0000000..98f5831 --- /dev/null +++ b/play/play-billing-library-version-upgrade/references/version-checklist.md @@ -0,0 +1,54 @@ +## Play Billing Library: Smart Version-Specific Checklist + +Use this checklist to verify that every technical requirement between your +\[Current Version\] and \[Target Version\] has been met. + +## PBL v1.x through v3.x + +- \[ \] **\[v1.0\] Builder Pattern** : Verify `BillingClient.newBuilder(context)` is used. +- \[ \] **\[v2.0\] Mandatory Acknowledgment** : Verify `acknowledgePurchase()` or `consumeAsync()` is called within 3 days. +- \[ \] **\[v2.0\] Response Types** : Logic must handle `BillingResult` objects instead of raw integers. +- \[ \] **\[v3.0\] Legacy Removal** : Verify `ChildDirected` and `UnderAgeOfConsent` parameters are deleted. + +## PBL v4.x Series + +- \[ \] **Async Purchasing** : Confirm `queryPurchases()` is replaced with `queryPurchasesAsync()`. +- \[ \] **Multi-SKU Accessors** : Replace `getSku()` with `getSkus()` (returns a list) in `Purchase` objects. +- \[ \] **Subscription Refactor** : Verify `setSubscriptionUpdateParams()` is used for change logic. + +## PBL v5.x Series + +- \[ \] **Data Model Swap** : Replace all instances of `SkuDetails` with `ProductDetails`. +- \[ \] **Personalized Pricing** : Implement `setIsOfferPersonalized()` for EU price disclosures. + +## PBL v6.x Series + +- \[ \] **Replacement Mode** : Replace `ProrationMode` with the `ReplacementMode` enum. +- \[ \] **User Choice Billing** : Replace `AlternativeBillingListener` with `UserChoiceBillingListener`. + +## PBL v7.x Series + +- \[ \] **SDK Compliance** : `compileSdk` is set to 34 or higher. +- \[ \] **Pending Purchases** : Replace parameterless `enablePendingPurchases()` with `enablePendingPurchases(PendingPurchaseParams)`. +- \[ \] **API Cleanup** : Replace `setOldSkuPurchaseToken()` with `setOldPurchaseToken()`. + +## PBL v8.x Series + +- \[ \] **SDK Compliance** : `compileSdk` is set to 34 or higher. +- \[ \] **Terminology Shift**: Rename "in-app items" to "one-time products" in UI/strings. +- \[ \] **Signature Enforcement** : `onProductDetailsResponse` signature MUST be `(BillingResult, QueryProductDetailsResult)`. +- \[ \] **SUBS Offer Token** : For `SUBS` type, `ProductDetailsParams` MUST include an `offerToken`. +- \[ \] **Auto-Reconnection** : Verify `enableAutoServiceReconnection()` is used in the builder. +- \[ \] **Min SDK Increase** : Verify `minSdkVersion` is at least 23. + +## PBL v9.x Series + +- \[ \] **SDK Compliance** : `compileSdk` is set to 35 or higher. +- \[ \] **API Removals** : Ensure all usages of `SkuDetails`, `SkuDetailsParams`, and `SkuDetailsResponseListener` are removed and replaced with their `ProductDetails` equivalents. +- \[ \] **Blocked Store Error Handling** : If targeting restricted device modes (e.g., kids modes), ensure `androidx.core` is at v1.9+ and update error logic to catch `BILLING_UNAVAILABLE` instead of `ERROR`. +- \[ \] **External Payment Program Null-Safety** : If using developer-provided billing, update `DeveloperProvidedBillingDetails.getLinkUri()` logic to explicitly check for both `null` and empty string (`""`) values before parsing. + +## Future Versions (PBL 10.0.0+) + +- \[ \] **Dynamic Checklist Generation** : For any version \>=10.0.0, you **MUST** synthesize a new checklist for each new version header found in the [Release Notes](https://developer.android.com/google/play/billing/release-notes). +- \[ \] **Identify Version Delta** : Review "Breaking Changes" and "Removed APIs" for the new version and create a list of terms to `grep`. \ No newline at end of file diff --git a/wear/wear-compose-m3/SKILL.md b/wear/wear-compose-m3/SKILL.md index 071ba14..8d21d93 100644 --- a/wear/wear-compose-m3/SKILL.md +++ b/wear/wear-compose-m3/SKILL.md @@ -9,7 +9,7 @@ description: Expert guidance for working with Wear OS Compose Material3. Use thi license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-31' + last-updated: '2026-07-18' keywords: - Wear OS - Compose @@ -67,117 +67,66 @@ features. ### Capability 3: Component samples -Use this table of reference to find canonical samples for Wear Compose -components. -When working with a Wear Compose component, you must use the samples linked -from the table to ensure you know how to correctly use it. - -#### Material 3 components in `androidx.wear.compose.material3.*` - -| Component / Symbol | Reference Samples | -|---|---| -| `AlertDialog`, `AlertDialogDefaults` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt) | -| `AnimatedPage`, `HorizontalPagerScaffold`, `VerticalPagerScaffold` | [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt) | -| `AnimatedText`, `rememberAnimatedTextFontRegistry` | [AnimatedTextSample](references/material3/AnimatedTextSample.kt.md.txt) | -| `AppCard` | [CardSample](references/material3/CardSample.kt.md.txt) | -| `AppScaffold` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ArcProgressIndicator`, `ArcProgressIndicatorDefaults`, `CircularProgressIndicator`, `CircularProgressIndicatorDefaults`, `ProgressIndicatorDefaults`, `SegmentedCircularProgressIndicator`, `drawCircularProgressIndicator` | [ProgressIndicatorSample](references/material3/ProgressIndicatorSample.kt.md.txt) | -| `Button` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [AnimatedTextSample](references/material3/AnimatedTextSample.kt.md.txt), [ButtonGroupSample](references/material3/ButtonGroupSample.kt.md.txt), [ButtonSample](references/material3/ButtonSample.kt.md.txt), [DatePickerSample](references/material3/DatePickerSample.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [FadingExpandingLabelSample](references/material3/FadingExpandingLabelSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [PickerSample](references/material3/PickerSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [StepperSample](references/material3/StepperSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TimePickerSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/TimePickerSample.kt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ButtonDefaults` | [ButtonSample](references/material3/ButtonSample.kt.md.txt), [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [EdgeButtonSample](references/material3/EdgeButtonSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [PlaceholderSample](references/material3/PlaceholderSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [TextButtonSample](references/material3/TextButtonSample.kt.md.txt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ButtonGroup` | [ButtonGroupSample](references/material3/ButtonGroupSample.kt.md.txt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt) | -| `Card` | [CardSample](references/material3/CardSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `CardDefaults` | [CardSample](references/material3/CardSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `CheckboxButton` | [CheckboxButtonSample](references/material3/CheckboxButtonSample.kt.md.txt), [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt) | -| `ChildButton`, `OutlinedButton` | [ButtonSample](references/material3/ButtonSample.kt.md.txt) | -| `ColorScheme`, `dynamicColorScheme` | [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt) | -| `CompactButton`, `CompactButtonDefaults` | [ButtonSample](references/material3/ButtonSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ConfirmationDialog`, `ConfirmationDialogDefaults`, `FailureConfirmationDialog`, `FavoriteIcon`, `SuccessConfirmationDialog`, `confirmationDialogCurvedText` | [ConfirmationDialogSample](references/material3/ConfirmationDialogSample.kt.md.txt) | -| `CurvedTextDefaults` | [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt) | -| `DatePicker`, `DatePickerType` | [DatePickerSample](references/material3/DatePickerSample.kt.md.txt) | -| `EdgeButton` | [EdgeButtonSample](references/material3/EdgeButtonSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `EdgeButtonSize` | [EdgeButtonSample](references/material3/EdgeButtonSample.kt.md.txt) | -| `FadingExpandingLabel` | [FadingExpandingLabelSample](references/material3/FadingExpandingLabelSample.kt.md.txt) | -| `FilledIconButton`, `FilledTonalIconButton`, `IconButtonColors`, `IconButtonShapes`, `OutlinedIconButton` | [IconButtonSample](references/material3/IconButtonSample.kt.md.txt) | -| `FilledTonalButton` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [ButtonSample](references/material3/ButtonSample.kt.md.txt), [ConfirmationDialogSample](references/material3/ConfirmationDialogSample.kt.md.txt), [OpenOnPhoneDialogSample](references/material3/OpenOnPhoneDialogSample.kt.md.txt), [PlaceholderSample](references/material3/PlaceholderSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt) | -| `GestureAction`, `OneHandedGestureClickIndicator`, `OneHandedGestureClickIndicatorState`, `oneHandedGesture`, `rememberOneHandedGestureConfiguration` | [ButtonSample](references/material3/ButtonSample.kt.md.txt), [CardSample](references/material3/CardSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt) | -| `GesturePriority`, `LocalOneHandedGestureEnabled`, `OneHandedGestureDefaults`, `OneHandedGestureHorizontalPageIndicator`, `OneHandedGesturePageIndicatorState`, `OneHandedGestureScrollIndicator`, `OneHandedGestureScrollIndicatorState`, `OneHandedGestureVerticalPageIndicator` | [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt) | -| `HeadphoneIcon`, `Stepper`, `StepperLevelIndicator`, `rangeSemantics` | [StepperSample](references/material3/StepperSample.kt.md.txt) | -| `HorizontalPageIndicator`, `VerticalPageIndicator` | [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt) | -| `Icon` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [ButtonSample](references/material3/ButtonSample.kt.md.txt), [CardSample](references/material3/CardSample.kt.md.txt), [CheckboxButtonSample](references/material3/CheckboxButtonSample.kt.md.txt), [ConfirmationDialogSample](references/material3/ConfirmationDialogSample.kt.md.txt), [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [DatePickerSample](references/material3/DatePickerSample.kt.md.txt), [EdgeButtonSample](references/material3/EdgeButtonSample.kt.md.txt), [IconButtonSample](references/material3/IconButtonSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [PlaceholderSample](references/material3/PlaceholderSample.kt.md.txt), [ProgressIndicatorSample](references/material3/ProgressIndicatorSample.kt.md.txt), [RadioButtonSample](references/material3/RadioButtonSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [SwitchButtonSample](references/material3/SwitchButtonSample.kt.md.txt), [TimePickerSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/TimePickerSample.kt) | -| `IconButton` | [IconButtonSample](references/material3/IconButtonSample.kt.md.txt), [LevelIndicatorSample](references/material3/LevelIndicatorSample.kt.md.txt), [ProgressIndicatorSample](references/material3/ProgressIndicatorSample.kt.md.txt) | -| `IconButtonDefaults` | [IconButtonSample](references/material3/IconButtonSample.kt.md.txt), [ProgressIndicatorSample](references/material3/ProgressIndicatorSample.kt.md.txt) | -| `IconToggleButton`, `IconToggleButtonDefaults`, `WifiOffIcon`, `WifiOnIcon` | [IconToggleButtonSample](references/material3/IconToggleButtonSample.kt.md.txt) | -| `LevelIndicator` | [LevelIndicatorSample](references/material3/LevelIndicatorSample.kt.md.txt) | -| `LinearProgressIndicator` | [LinearProgressIndicatorSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt) | -| `ListHeader` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ListHeaderDefaults` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ListSubHeader` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt) | -| `MaterialTheme` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [ButtonSample](references/material3/ButtonSample.kt.md.txt), [CardSample](references/material3/CardSample.kt.md.txt), [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [LinearProgressIndicatorSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [ProgressIndicatorSample](references/material3/ProgressIndicatorSample.kt.md.txt), [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt) | -| `OpenOnPhoneDialog`, `OpenOnPhoneDialogDefaults`, `openOnPhoneDialogCurvedText` | [OpenOnPhoneDialogSample](references/material3/OpenOnPhoneDialogSample.kt.md.txt) | -| `OutlinedCard` | [CardSample](references/material3/CardSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `PagerScaffoldDefaults` | [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt) | -| `Picker` | [PickerSample](references/material3/PickerSample.kt.md.txt) | -| `PickerGroup` | [PickerGroupSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/PickerGroupSample.kt) | -| `RadioButton`, `SplitRadioButton` | [RadioButtonSample](references/material3/RadioButtonSample.kt.md.txt) | -| `ResponsiveTransformationSpec`, `TransformationVariableSpec` | [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt) | -| `RevealValue`, `SwipeToReveal`, `SwipeToRevealDefaults`, `rememberRevealState` | [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt) | -| `ScreenScaffold` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `ScreenScaffoldDefaults`, `ScrollIndicator` | [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt) | -| `ScreenStage`, `scrollAway` | [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt) | -| `Slider`, `SliderDefaults` | [SliderSample](references/material3/SliderSample.kt.md.txt) | -| `SplitCheckboxButton` | [CheckboxButtonSample](references/material3/CheckboxButtonSample.kt.md.txt) | -| `SplitSwitchButton` | [SwitchButtonSample](references/material3/SwitchButtonSample.kt.md.txt) | -| `StepperDefaults`, `VolumeDownIcon`, `VolumeUpIcon` | [LevelIndicatorSample](references/material3/LevelIndicatorSample.kt.md.txt), [StepperSample](references/material3/StepperSample.kt.md.txt) | -| `SurfaceTransformation` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `SwipeToDismissBox` | [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt) | -| `SwitchButton` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [SwitchButtonSample](references/material3/SwitchButtonSample.kt.md.txt) | -| `Text` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [AnimatedTextSample](references/material3/AnimatedTextSample.kt.md.txt), [ButtonGroupSample](references/material3/ButtonGroupSample.kt.md.txt), [ButtonSample](references/material3/ButtonSample.kt.md.txt), [CardSample](references/material3/CardSample.kt.md.txt), [CheckboxButtonSample](references/material3/CheckboxButtonSample.kt.md.txt), [ConfirmationDialogSample](references/material3/ConfirmationDialogSample.kt.md.txt), [DatePickerSample](references/material3/DatePickerSample.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [EdgeButtonSample](references/material3/EdgeButtonSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [OpenOnPhoneDialogSample](references/material3/OpenOnPhoneDialogSample.kt.md.txt), [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [PickerGroupSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/PickerGroupSample.kt), [PickerSample](references/material3/PickerSample.kt.md.txt), [PlaceholderSample](references/material3/PlaceholderSample.kt.md.txt), [RadioButtonSample](references/material3/RadioButtonSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [StepperSample](references/material3/StepperSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [SwitchButtonSample](references/material3/SwitchButtonSample.kt.md.txt), [TextButtonSample](references/material3/TextButtonSample.kt.md.txt), [TextToggleButtonSample](references/material3/TextToggleButtonSample.kt.md.txt), [TimePickerSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/TimePickerSample.kt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `TextButton` | [TextButtonSample](references/material3/TextButtonSample.kt.md.txt) | -| `TextButtonDefaults` | [TextButtonSample](references/material3/TextButtonSample.kt.md.txt), [TextToggleButtonSample](references/material3/TextToggleButtonSample.kt.md.txt) | -| `TextToggleButton`, `TextToggleButtonDefaults`, `touchTargetAwareSize` | [TextToggleButtonSample](references/material3/TextToggleButtonSample.kt.md.txt) | -| `TimePicker`, `TimePickerType` | [TimePickerSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/TimePickerSample.kt) | -| `TimeText` | [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | -| `TimeTextDefaults`, `timeTextCurvedText` | [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | -| `TitleCard` | [CardSample](references/material3/CardSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt) | -| `TransformationSpec` | [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt) | -| `curvedText` | [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | -| `firstVisibleItemLayoutItemInfo`, `layoutItemInfoOf`, `rememberTransformingLazyColumnFirstLayoutItemProvider` | [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `placeholder`, `placeholderShimmer`, `rememberPlaceholderState` | [PlaceholderSample](references/material3/PlaceholderSample.kt.md.txt) | -| `rememberPickerState` | [PickerGroupSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/PickerGroupSample.kt), [PickerSample](references/material3/PickerSample.kt.md.txt) | -| `rememberTransformationSpec`, `transformedHeight` | [AlertDialogSample](references/material3/AlertDialogSample.kt.md.txt), [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `timeTextSeparator` | [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | - -#### Foundation components in `androidx.wear.compose.foundation.*` - -| Component / Symbol | Reference Samples | -|---|---| -| `AmbientMode`, `AmbientTickEffect`, `LocalAmbientModeManager`, `rememberAmbientModeManager` | [AmbientModeSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/foundation/AmbientModeSample.kt) | -| `AutoCenteringParams`, `ScalingLazyColumnDefaults`, `ScalingLazyListAnchorType` | [ScalingLazyColumnSample](references/foundation/ScalingLazyColumnSample.kt.md.txt) | -| `BasicSwipeToDismissBox` | [SwipeToDismissBoxSample](references/foundation/SwipeToDismissBoxSample.kt.md.txt) | -| `CurvedAlignment`, `CurvedTextStyle`, `angularGradientBackground`, `angularSize`, `basicCurvedText`, `clearAndSetSemantics`, `curvedColumn`, `padding`, `radialGradientBackground`, `radialSize`, `semantics`, `size` | [CurvedWorldSample](references/foundation/CurvedWorldSample.kt.md.txt) | -| `CurvedDirection`, `CurvedLayout`, `angularSizeDp`, `background`, `curvedBox`, `curvedComposable`, `curvedRow` | [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [CurvedWorldSample](references/foundation/CurvedWorldSample.kt.md.txt) | -| `CurvedModifier` | [CurvedTextSamples](references/material3/CurvedTextSamples.kt.md.txt), [CurvedWorldSample](references/foundation/CurvedWorldSample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | -| `ExperimentalWearFoundationApi`, `RevealValue`, `SwipeToReveal`, `rememberRevealState` | [SwipeToRevealSample](references/foundation/SwipeToRevealSample.kt.md.txt) | -| `HorizontalPager`, `VerticalPager`, `rememberPagerState` | [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [PageIndicatorSample](references/material3/PageIndicatorSample.kt.md.txt), [PagerSamples](references/foundation/PagerSamples.kt.md.txt), [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt) | -| `ItemEdge`, `TransformingLazyColumnDefaults` | [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt) | -| `PagerDefaults` | [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt) | -| `RotaryScrollableDefaults` | [PagerScaffoldSample](references/material3/PagerScaffoldSample.kt.md.txt), [RotarySamples](references/foundation/RotarySamples.kt.md.txt), [ScalingLazyColumnSample](references/foundation/ScalingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt) | -| `RotarySnapLayoutInfoProvider`, `rotaryScrollable` | [RotarySamples](references/foundation/RotarySamples.kt.md.txt) | -| `ScalingLazyColumn` | [ExpandableSample](references/foundation/ExpandableSample.kt.md.txt), [HierarchicalFocusSample](references/foundation/HierarchicalFocusSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [ScalingLazyColumnSample](references/foundation/ScalingLazyColumnSample.kt.md.txt), [SwipeToRevealSample](references/foundation/SwipeToRevealSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt) | -| `ScrollInfoProvider` | [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt) | -| `SwipeToDismissValue`, `edgeSwipeToDismiss`, `rememberSwipeToDismissBoxState` | [SwipeToDismissBoxSample](references/foundation/SwipeToDismissBoxSample.kt.md.txt), [SwipeToDismissBoxSample](references/material3/SwipeToDismissBoxSample.kt.md.txt) | -| `TransformingLazyColumn` | [DynamicColorSchemeSample](references/material3/DynamicColorSchemeSample.kt.md.txt), [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `TransformingLazyColumnFirstLayoutItemProvider` | [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `TransformingLazyColumnItemScrollProgress` | [TransformationSpecSample](references/material3/TransformationSpecSample.kt.md.txt) | -| `expandableButton`, `expandableItems` | [ExpandableSample](references/foundation/ExpandableSample.kt.md.txt) | -| `expandableItem`, `rememberExpandableState` | [ExpandableSample](references/foundation/ExpandableSample.kt.md.txt), [SwipeToRevealSample](references/foundation/SwipeToRevealSample.kt.md.txt) | -| `hierarchicalFocusGroup` | [HierarchicalFocusSample](references/foundation/HierarchicalFocusSample.kt.md.txt) | -| `items` | [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt) | -| `itemsIndexed` | [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `rememberScalingLazyListState` | [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [ScalingLazyColumnSample](references/foundation/ScalingLazyColumnSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt) | -| `rememberTransformingLazyColumnState` | [ListHeaderSample](references/material3/ListHeaderSample.kt.md.txt), [OneHandedGestureSamples](references/material3/OneHandedGestureSamples.kt.md.txt), [ScaffoldSample](references/material3/ScaffoldSample.kt.md.txt), [ScrollAwaySample](references/material3/ScrollAwaySample.kt.md.txt), [ScrollIndicatorSample](references/material3/ScrollIndicatorSample.kt.md.txt), [SurfaceTransformationSample](references/material3/SurfaceTransformationSample.kt.md.txt), [SwipeToRevealSample](https://developer.android.com/agents/skills/wear/wear-compose-m3/references/material3/SwipeToRevealSample.kt), [TransformingLazyColumnNotificationsSample](references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt), [TransformingLazyColumnSample](references/foundation/TransformingLazyColumnSample.kt.md.txt), [TransformingLazyColumnSample](references/material3/TransformingLazyColumnSample.kt.md.txt) | -| `requestFocusOnHierarchyActive` | [HierarchicalFocusSample](references/foundation/HierarchicalFocusSample.kt.md.txt), [RotarySamples](references/foundation/RotarySamples.kt.md.txt) | -| `weight` | [CurvedWorldSample](references/foundation/CurvedWorldSample.kt.md.txt), [TimeTextSample](references/material3/TimeTextSample.kt.md.txt) | +Wear Compose includes individual component samples for each and every component, +within the `--samples-sources.jar` file. Gradle automatically +downloads these JAR files along with the main library JAR when using any of +`compose-material3`, `compose-foundation` or `compose-navigation3`. + +Use the canonical component samples whenever adding or adjusting a Wear Compose +Material3 component. + +STRICT COMPLIANCE: Extraction is NOT optional. You are FORBIDDEN from +implementing any code until samples are extracted and read. Bypassing this step +with alternative search tools or by assuming library documentation is sufficient +is a protocol breach. You MUST verify the local cache by reading a sample file +before proceeding. + +Exception: You don't need to extract samples if the request is strictly related +to tooling (for example, adding @WearPreviewDevices), updating text/colors, or +basic refactoring that doesn't involve adding new Wear Compose components. + +#### Step 1: Prepare + +1. Check the `build.gradle.kts` or `libs.versions.toml` to ensure the Wear Compose version matches `{VERSION}`. +2. Ensure that the necessary dependencies are downloaded by doing a Gradle sync. + +#### Step 2: Check the local cache + +1. Define the cache directory path: `/tmp/wear-compose-samples/{VERSION}/`. Do NOT choose your own different location. +2. Check if this directory exists and contains subdirectories with `.kt` files. + - **IF YES (cache hit):** Proceed to **Step 4**. + - **IF NO (cache miss):** Proceed to **Step 3**. + +#### Step 3: Network-based sample retrieval + +Google Maven publishes a `-samples-sources.jar` alongside every library release. +Download and extract it directly without needing a local Gradle sync or cache +lookup. + +1. Determine the `{VERSION}` of `androidx.wear.compose:compose-material3` from `build.gradle.kts` or `libs.versions.toml`. +2. Define `{ARTIFACT}` as the items in the list `["material3", "foundation"]`. Also include `navigation3` if `androidx.wear.compose.navigation3` is used. +3. For each `{ARTIFACT}`, run the following commands to download and extract + the samples: + + # Download the samples-sources JAR + curl -sSL "https://dl.google.com/dl/android/maven2/androidx/wear/compose/compose-{ARTIFACT}/{VERSION}/compose-{ARTIFACT}-{VERSION}-samples-sources.jar" -o {ARTIFACT}-{VERSION}-samples.jar + + # Extract and flatten into the cache directory + unzip -q -o {ARTIFACT}-{VERSION}-samples.jar -d /tmp/wear-compose-samples/{VERSION}/{ARTIFACT}/ + + # Clean up + rm {ARTIFACT}-{VERSION}-samples.jar + +4. If this works, proceed directly to step 4. + +#### Step 4: Read samples and implement + +1. Read the relevant `.kt` sample files. +2. Use these official, version-matched samples as the source of truth for: + - Required parameters and slot names. + - Default styling and typography tokens. + - Interactive behaviors (for example: `onClick`, `onLongClick`). + - Component nesting (for example: `AppScaffold` -\> `ScreenScaffold`). ### Capability 4: Component guidance diff --git a/wear/wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md b/wear/wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md index 9ff43dc..9e1498d 100644 --- a/wear/wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md +++ b/wear/wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md @@ -27,19 +27,19 @@ M3 has a separate package and version to M2.5: ### M3 - implementation("androidx.wear.compose:compose-material3:1.7.0-alpha07") + implementation("androidx.wear.compose:compose-material3:1.7.0-alpha06") See the latest M3 versions on the [Wear Compose Material 3 releases page](https://developer.android.com/jetpack/androidx/releases/wear-compose-m3). -Wear Compose Foundation library version 1.7.0-alpha07 introduced +Wear Compose Foundation library version 1.7.0-alpha06 introduced some new components that are designed to work with Material 3 components. Similarly, `SwipeDismissableNavHost` from Wear Compose Navigation library has an updated animation when running on Wear OS 6 (API level 36) or higher. When updating to Wear Compose Material 3 version, we suggest to also update the Wear Compose Foundation and Navigation libraries: - implementation("androidx.wear.compose:compose-foundation:1.7.0-alpha07") - implementation("androidx.wear.compose:compose-navigation:1.7.0-alpha07") + implementation("androidx.wear.compose:compose-foundation:1.7.0-alpha06") + implementation("androidx.wear.compose:compose-navigation:1.7.0-alpha06") ## Theme @@ -329,7 +329,7 @@ Here is a full list of all the Material 3 components: And finally a list of some relevant components from Wear Compose Foundation library: -| Wear Compose Foundation 1.7.0-alpha07 | | +| Wear Compose Foundation 1.7.0-alpha06 | | |---|---| | [androidx.wear.compose.foundation.hierarchicalFocusGroup](https://developer.android.com/reference/kotlin/androidx/wear/compose/foundation/package-summary#(androidx.compose.ui.Modifier).hierarchicalFocusGroup(kotlin.Boolean)) | Used to annotate composables in an application, to keep track of the active part of the composition and coordinate focus. | | [androidx.wear.compose.foundation.pager.HorizontalPager](https://developer.android.com/reference/kotlin/androidx/wear/compose/foundation/pager/package-summary#HorizontalPager(androidx.wear.compose.foundation.pager.PagerState,androidx.compose.ui.Modifier,androidx.compose.foundation.layout.PaddingValues,kotlin.Int,androidx.compose.foundation.gestures.TargetedFlingBehavior,kotlin.Boolean,androidx.wear.compose.foundation.GestureInclusion,kotlin.Boolean,kotlin.Function1,androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior,kotlin.Function2)) | A horizontally scrolling pager, built on the Compose Foundation components with Wear-specific enhancements to improve performance and adherence to Wear OS guidelines. | diff --git a/wear/wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt deleted file mode 100644 index e19960b..0000000 --- a/wear/wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt +++ /dev/null @@ -1,430 +0,0 @@ -``` -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.traversalIndex -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.wear.compose.foundation.CurvedAlignment -import androidx.wear.compose.foundation.CurvedDirection -import androidx.wear.compose.foundation.CurvedLayout -import androidx.wear.compose.foundation.CurvedModifier -import androidx.wear.compose.foundation.CurvedTextStyle -import androidx.wear.compose.foundation.angularGradientBackground -import androidx.wear.compose.foundation.angularSize -import androidx.wear.compose.foundation.angularSizeDp -import androidx.wear.compose.foundation.background -import androidx.wear.compose.foundation.basicCurvedText -import androidx.wear.compose.foundation.clearAndSetSemantics -import androidx.wear.compose.foundation.curvedBox -import androidx.wear.compose.foundation.curvedColumn -import androidx.wear.compose.foundation.curvedComposable -import androidx.wear.compose.foundation.curvedRow -import androidx.wear.compose.foundation.padding -import androidx.wear.compose.foundation.radialGradientBackground -import androidx.wear.compose.foundation.radialSize -import androidx.wear.compose.foundation.semantics -import androidx.wear.compose.foundation.size -import androidx.wear.compose.foundation.weight -import androidx.wear.compose.material.Text - -@Sampled -@Composable -fun SimpleCurvedWorld() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - curvedComposable { - BasicText( - "Simple", - Modifier.background(Color.White).padding(2.dp), - TextStyle(color = Color.Black, fontSize = 16.sp), - ) - } - curvedComposable { Box(modifier = Modifier.size(20.dp).background(Color.Gray)) } - curvedComposable { - BasicText( - "CurvedWorld", - Modifier.background(Color.White).padding(2.dp), - TextStyle(color = Color.Black, fontSize = 16.sp), - ) - } - } -} - -@Sampled -@Composable -fun CurvedRowAndColumn() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - curvedComposable { Box(modifier = Modifier.size(20.dp).background(Color.Red)) } - curvedColumn(angularAlignment = CurvedAlignment.Angular.End) { - repeat(3) { - curvedRow { - curvedComposable { - BasicText( - "Row #$it", - Modifier.background(Color.White).padding(2.dp), - TextStyle(color = Color.Black, fontSize = 14.sp), - ) - } - curvedComposable { - Box(modifier = Modifier.size(10.dp).background(Color.Green)) - } - curvedComposable { - BasicText( - "More", - Modifier.background(Color.Yellow).padding(2.dp), - TextStyle(color = Color.Black, fontSize = 14.sp), - ) - } - } - } - } - curvedComposable { Box(modifier = Modifier.size(20.dp).background(Color.Red)) } - } -} - -@Sampled -@Composable -fun CurvedAndNormalText() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText( - "Curved Text", - CurvedModifier.padding(10.dp), - style = { - CurvedTextStyle(fontSize = 16.sp, color = Color.Black, background = Color.White) - }, - ) - curvedComposable { Box(modifier = Modifier.size(20.dp).background(Color.Gray)) } - curvedComposable { - BasicText( - "Normal Text", - Modifier.padding(5.dp), - TextStyle(fontSize = 16.sp, color = Color.Black, background = Color.White), - ) - } - } -} - -@Sampled -@Composable -fun CurvedFixedSize() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText( - "45 deg", - style = { CurvedTextStyle(fontSize = 16.sp, color = Color.Black) }, - modifier = - CurvedModifier.background(Color.White).size(sweepDegrees = 45f, thickness = 40.dp), - ) - basicCurvedText( - "40 dp", - style = { CurvedTextStyle(fontSize = 16.sp, color = Color.Black) }, - modifier = - CurvedModifier.background(Color.Yellow).radialSize(40.dp).angularSizeDp(40.dp), - ) - } -} - -@Sampled -@Composable -fun CurvedBackground() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText( - "Radial", - style = { CurvedTextStyle(fontSize = 16.sp, color = Color.Black) }, - modifier = - CurvedModifier.radialGradientBackground(0f to Color.White, 1f to Color.Black) - .padding(5.dp), - ) - basicCurvedText( - "Angular", - style = { CurvedTextStyle(fontSize = 16.sp, color = Color.Black) }, - modifier = - CurvedModifier.angularGradientBackground(0f to Color.White, 1f to Color.Black) - .padding(5.dp), - ) - } -} - -@Sampled -@Composable -fun CurvedWeight() { - CurvedLayout(modifier = Modifier.fillMaxSize().background(Color.White)) { - // Evenly spread A, B & C in a 90 degree angle. - curvedRow(modifier = CurvedModifier.angularSize(90f)) { - basicCurvedText("A") - curvedRow(modifier = CurvedModifier.weight(1f)) {} - basicCurvedText("B") - curvedRow(modifier = CurvedModifier.weight(1f)) {} - basicCurvedText("C") - } - } -} - -@Sampled -@Composable -fun CurvedBottomLayout() { - CurvedLayout( - modifier = Modifier.fillMaxSize(), - anchor = 90f, - angularDirection = CurvedDirection.Angular.Reversed, - ) { - basicCurvedText( - "Bottom", - style = { - CurvedTextStyle(fontSize = 16.sp, color = Color.Black, background = Color.White) - }, - ) - curvedComposable { Spacer(modifier = Modifier.size(5.dp)) } - basicCurvedText( - "text", - style = { - CurvedTextStyle(fontSize = 16.sp, color = Color.Black, background = Color.White) - }, - ) - } -} - -@Sampled -@Composable -fun CurvedBoxSample() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - curvedBox( - modifier = CurvedModifier.background(Color.Red), - radialAlignment = CurvedAlignment.Radial.Inner, - angularAlignment = CurvedAlignment.Angular.End, - ) { - curvedComposable { - Box(modifier = Modifier.width(40.dp).height(80.dp).background(Color.Green)) - } - curvedComposable { - Box(modifier = Modifier.size(30.dp).clip(CircleShape).background(Color.White)) - } - } - } -} - -@Sampled -@Composable -fun CurvedLetterSpacingSample() { - val style = - CurvedTextStyle( - letterSpacing = 0.6.sp, - letterSpacingCounterClockwise = 1.4.sp, - color = Color.White, - ) - Box { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText("Clockwise", style = style) - } - CurvedLayout( - modifier = Modifier.fillMaxSize(), - angularDirection = CurvedDirection.Angular.CounterClockwise, - anchor = 90f, - ) { - basicCurvedText("Counter Clockwise", style = style) - } - } -} - -@Sampled -@Composable -fun CurvedWarpingSample() { - val style = - CurvedTextStyle( - letterSpacing = 0.6.sp, - letterSpacingCounterClockwise = 1.4.sp, - color = Color.White, - ) - Box { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText( - "No Warping", - style = style.copy(warpOffset = CurvedTextStyle.WarpOffset.None), - ) - } - CurvedLayout( - modifier = Modifier.fillMaxSize(), - angularDirection = CurvedDirection.Angular.CounterClockwise, - anchor = 90f, - ) { - basicCurvedText( - "Standard Warping", - style.copy(warpOffset = CurvedTextStyle.WarpOffset.HalfOpticalHeight), - ) - } - } -} - -@Sampled -@Composable -fun CurvedSemanticsSample() { - val style = - CurvedTextStyle( - letterSpacing = 0.6.sp, - letterSpacingCounterClockwise = 1.4.sp, - color = Color.White, - ) - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText( - "2 - Left", - style, - CurvedModifier.semantics { - contentDescription = "Left" - traversalIndex = 2f - }, - ) - curvedComposable { Box(Modifier.padding(5.dp).background(Color.Red).size(5.dp)) } - basicCurvedText( - "3 - Right", - style, - CurvedModifier.semantics { - contentDescription = "Right" - traversalIndex = 3f - }, - ) - } - Row { - Text("Text 1", Modifier.semantics { traversalIndex = 1f }) - Spacer(Modifier.size(10.dp)) - Text("Text 4", Modifier.semantics { traversalIndex = 4f }) - } - } -} - -@Sampled -@Composable -fun CurvedClearSemanticsSample() { - val style = - CurvedTextStyle( - letterSpacing = 0.6.sp, - letterSpacingCounterClockwise = 1.4.sp, - color = Color.White, - ) - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - basicCurvedText("This is not announced", style, CurvedModifier.clearAndSetSemantics {}) - } - Row { Text("This is announced", Modifier.semantics { traversalIndex = -1f }) } - } -} - -@Composable -fun CurvedFontWeight() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - (100..900 step 100).forEach { - basicCurvedText( - "W$it", - style = CurvedTextStyle(color = Color.White, fontWeight = FontWeight(it)), - modifier = CurvedModifier.padding(5.dp), - ) - } - } -} - -@Composable -fun CurvedFontHeight() { - Box( - modifier = - Modifier.aspectRatio(1f) - .fillMaxSize() - .padding(2.dp) - .border(2.dp, Color.White, CircleShape) - ) { - CurvedLayout() { - basicCurvedText("9⎪:⎪0", style = CurvedTextStyle(color = Color.Green, fontSize = 30.sp)) - } - CurvedLayout(anchor = 90f, angularDirection = CurvedDirection.Angular.CounterClockwise) { - basicCurvedText("9⎪:⎪0", style = CurvedTextStyle(color = Color.Green, fontSize = 30.sp)) - } - } -} - -@Composable -fun CurvedFonts() { - CurvedLayout(modifier = Modifier.fillMaxSize()) { - listOf( - "Serif" to FontFamily.Serif, - "SansSerif" to FontFamily.SansSerif, - "Monospace" to FontFamily.Monospace, - "Cursive" to FontFamily.Cursive, - ) - .forEach { (name, ff) -> - basicCurvedText( - "$name", - style = CurvedTextStyle(color = Color.White, fontFamily = ff), - modifier = CurvedModifier.padding(5.dp), - ) - } - } -} - -@Composable -fun OversizeComposable() { - val modBase = CurvedModifier.size(sweepDegrees = 30f, thickness = 20.dp) - CurvedLayout(modifier = Modifier.fillMaxSize()) { - curvedComposable(modifier = modBase.background(Color.Red)) {} - curvedComposable(modifier = modBase.background(Color.Green)) { - Box(Modifier.size(80.dp, 30.dp).background(Color.White)) - } - curvedComposable(modifier = modBase.background(Color.Blue)) {} - } - CurvedLayout(modifier = Modifier.fillMaxSize(), anchor = 90f) { - curvedComposable(modifier = CurvedModifier.background(Color.Green)) { - Box(Modifier.size(80.dp, 30.dp).background(Color.White)) - } - } -} - -@Composable -fun CurvedLineHeight() { - val baseStyle = CurvedTextStyle(color = Color.White, background = Color.Gray, fontSize = 16.sp) - CurvedLayout( - modifier = Modifier.fillMaxSize(), - radialAlignment = CurvedAlignment.Radial.Center, - ) { - basicCurvedText("Line Height 10.sp", style = baseStyle.copy(lineHeight = 10.sp)) - curvedBox(CurvedModifier.angularSizeDp(1.dp)) {} - basicCurvedText("Base", style = baseStyle) - curvedBox(CurvedModifier.angularSizeDp(1.dp)) {} - basicCurvedText("Line Height 24.sp", style = baseStyle.copy(lineHeight = 24.sp)) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt deleted file mode 100644 index 84fe733..0000000 --- a/wear/wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt +++ /dev/null @@ -1,99 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.expandableButton -import androidx.wear.compose.foundation.expandableItem -import androidx.wear.compose.foundation.expandableItems -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.rememberExpandableState -import androidx.wear.compose.material.Chip -import androidx.wear.compose.material.Icon -import androidx.wear.compose.material.OutlinedCompactChip -import androidx.wear.compose.material.Text - -@Sampled -@Composable -fun ExpandableWithItemsSample() { - val expandableState = rememberExpandableState() - - val sampleItem: @Composable (String) -> Unit = { label -> - Chip(label = { Text(label) }, onClick = {}, secondaryLabel = { Text("line 2 - Secondary") }) - } - - val items = List(10) { "Item $it" } - val top = items.take(3) - val rest = items.drop(3) - - ScalingLazyColumn(modifier = Modifier.fillMaxSize()) { - items(top.size) { sampleItem(top[it]) } - expandableItems(expandableState, rest.size) { sampleItem(rest[it]) } - expandableButton(expandableState) { - OutlinedCompactChip( - label = { - Text("Show More") - Spacer(Modifier.size(6.dp)) - Icon(painterResource(R.drawable.ic_expand_more_24), "Expand") - }, - onClick = { expandableState.expanded = true }, - ) - } - } -} - -@Sampled -@Composable -fun ExpandableTextSample() { - val expandableState = rememberExpandableState() - - ScalingLazyColumn(modifier = Modifier.fillMaxSize()) { - expandableItem(expandableState) { expanded -> - Text( - "Account Alert: you have made a large purchase.\n" + - "We have noticed that a large purchase was charged to " + - "your credit card account. " + - "Please contact us if you did not perform this purchase. " + - "Our Customer Service team is available 24 hours a day, " + - "7 days a week to answer your account or product support question.", - maxLines = if (expanded) 20 else 3, - modifier = Modifier.padding(horizontal = 10.dp), - ) - } - - expandableButton(expandableState) { - OutlinedCompactChip( - label = { - Text("Show More") - Spacer(Modifier.size(6.dp)) - Icon(painterResource(R.drawable.ic_expand_more_24), "Expand") - }, - onClick = { expandableState.expanded = true }, - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt deleted file mode 100644 index 1df8a80..0000000 --- a/wear/wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt +++ /dev/null @@ -1,141 +0,0 @@ -``` -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -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 -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.wear.compose.foundation.hierarchicalFocusGroup -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.requestFocusOnHierarchyActive - -@Sampled -@Composable -fun HierarchicalFocusSample() { - var selected by remember { mutableIntStateOf(0) } - - Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) { - repeat(5) { colIx -> - Box( - Modifier.hierarchicalFocusGroup(active = selected == colIx) - .weight(1f) - .clickable { selected = colIx } - .then( - if (selected == colIx) { - Modifier.border(BorderStroke(2.dp, Color.Red)) - } else { - Modifier - } - ) - ) { - // This is used a Gray background to the currently focused item, as seen by the - // focus system. - var focused by remember { mutableStateOf(false) } - - BasicText( - "$colIx", - style = - TextStyle( - color = Color.White, - fontSize = 20.sp, - textAlign = TextAlign.Center, - ), - modifier = - Modifier.fillMaxWidth() - .requestFocusOnHierarchyActive() - .onFocusChanged { focused = it.isFocused } - .focusable() - .then( - if (focused) { - Modifier.background(Color.Gray) - } else { - Modifier - } - ), - ) - } - } - } -} - -@Sampled -@Composable -fun HierarchicalFocus2Levels() { - Column(Modifier.fillMaxSize()) { - var selectedRow by remember { mutableIntStateOf(0) } - repeat(2) { rowIx -> - Row( - Modifier.weight(1f) - .fillMaxWidth() - .hierarchicalFocusGroup(active = selectedRow == rowIx) - ) { - var selectedItem by remember { mutableIntStateOf(0) } - repeat(2) { itemIx -> - Box( - Modifier.weight(1f).hierarchicalFocusGroup(active = selectedItem == itemIx) - ) { - // ScalingLazyColumn uses requestFocusOnHierarchyActive internally - ScalingLazyColumn( - Modifier.fillMaxWidth().clickable { - selectedRow = rowIx - selectedItem = itemIx - } - ) { - val prefix = (rowIx * 2 + itemIx + 'A'.code).toChar() - items(20) { - BasicText( - "$prefix $it", - style = - TextStyle( - color = Color.White, - fontSize = 20.sp, - textAlign = TextAlign.Center, - ), - ) - } - } - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/PagerSamples.kt.md.txt b/wear/wear-compose-m3/references/foundation/PagerSamples.kt.md.txt deleted file mode 100644 index bae406c..0000000 --- a/wear/wear-compose-m3/references/foundation/PagerSamples.kt.md.txt +++ /dev/null @@ -1,56 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.text.BasicText -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.wear.compose.foundation.pager.HorizontalPager -import androidx.wear.compose.foundation.pager.VerticalPager -import androidx.wear.compose.foundation.pager.rememberPagerState - -@Sampled -@Composable -fun SimpleHorizontalPagerSample() { - // Creates a horizontal pager with 10 elements - val state = rememberPagerState { 10 } - HorizontalPager(modifier = Modifier.fillMaxSize(), state = state) { page -> - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - BasicText(text = "Page $page", style = TextStyle(color = Color.White)) - } - } -} - -@Sampled -@Composable -fun SimpleVerticalPagerSample() { - // Creates a vertical pager with 10 elements - val state = rememberPagerState { 10 } - VerticalPager(modifier = Modifier.fillMaxSize(), state = state) { page -> - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - BasicText(text = "Page $page", style = TextStyle(color = Color.White)) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/RotarySamples.kt.md.txt b/wear/wear-compose-m3/references/foundation/RotarySamples.kt.md.txt deleted file mode 100644 index d15792b..0000000 --- a/wear/wear-compose-m3/references/foundation/RotarySamples.kt.md.txt +++ /dev/null @@ -1,153 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.overscroll -import androidx.compose.foundation.rememberOverscrollEffect -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.BasicText -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastSumBy -import androidx.wear.compose.foundation.requestFocusOnHierarchyActive -import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults -import androidx.wear.compose.foundation.rotary.RotarySnapLayoutInfoProvider -import androidx.wear.compose.foundation.rotary.rotaryScrollable -import androidx.wear.compose.material.Text - -@Sampled -@Composable -fun RotaryScrollSample() { - val scrollableState = rememberLazyListState() - val focusRequester = remember { FocusRequester() } - LazyColumn( - modifier = - Modifier.fillMaxSize() - .requestFocusOnHierarchyActive() - .rotaryScrollable( - behavior = RotaryScrollableDefaults.behavior(scrollableState), - focusRequester = focusRequester, - ), - horizontalAlignment = Alignment.CenterHorizontally, - state = scrollableState, - ) { - items(300) { - BasicText( - text = "item $it", - modifier = Modifier.background(Color.Gray), - style = TextStyle.Default.copy(), - ) - } - } -} - -@Sampled -@Composable -fun RotaryScrollWithOverscrollSample() { - val scrollableState = rememberScrollState() - val focusRequester = remember { FocusRequester() } - val overscrollEffect = rememberOverscrollEffect() - - val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp - - Column( - Modifier.fillMaxSize() - .requestFocusOnHierarchyActive() - .rotaryScrollable( - behavior = RotaryScrollableDefaults.behavior(scrollableState), - focusRequester = focusRequester, - overscrollEffect = overscrollEffect, - ) - .verticalScroll(scrollableState, overscrollEffect) - .overscroll(overscrollEffect), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text("Top") - Spacer(modifier = Modifier.height(screenHeightDp / 2)) - Text("Scroll this list up and down with rotary input", textAlign = TextAlign.Center) - Spacer(modifier = Modifier.height(screenHeightDp / 2)) - Text("Bottom") - } -} - -@Sampled -@Composable -fun RotarySnapSample() { - val scrollableState = rememberLazyListState() - val focusRequester = remember { FocusRequester() } - LazyColumn( - modifier = - Modifier.fillMaxSize() - .requestFocusOnHierarchyActive() - .rotaryScrollable( - behavior = - RotaryScrollableDefaults.snapBehavior( - scrollableState, - // This sample has a custom implementation of - // RotarySnapLayoutInfoProvider which is required for snapping behavior. - // ScalingLazyColumn has it built-in, so it's not required there. - remember(scrollableState) { - object : RotarySnapLayoutInfoProvider { - - override val averageItemSize: Float - get() { - val items = scrollableState.layoutInfo.visibleItemsInfo - return (items.fastSumBy { it.size } / items.size) - .toFloat() - } - - override val currentItemIndex: Int - get() = scrollableState.firstVisibleItemIndex - - override val currentItemOffset: Float - get() = - scrollableState.firstVisibleItemScrollOffset.toFloat() - - override val totalItemCount: Int - get() = scrollableState.layoutInfo.totalItemsCount - } - }, - ), - focusRequester = focusRequester, - ), - horizontalAlignment = Alignment.CenterHorizontally, - state = scrollableState, - ) { - items(300) { - BasicText(text = "item $it", modifier = Modifier.background(Color.Gray).height(50.dp)) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt deleted file mode 100644 index 8b1c765..0000000 --- a/wear/wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt +++ /dev/null @@ -1,131 +0,0 @@ -``` -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.lazy.AutoCenteringParams -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults -import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState -import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults -import androidx.wear.compose.material.Chip -import androidx.wear.compose.material.ChipDefaults -import androidx.wear.compose.material.ListHeader -import androidx.wear.compose.material.Text -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun SimpleScalingLazyColumn() { - ScalingLazyColumn(modifier = Modifier.fillMaxWidth()) { - item { ListHeader { Text(text = "List Header") } } - items(20) { - Chip( - onClick = {}, - label = { Text("List item $it") }, - colors = ChipDefaults.secondaryChipColors(), - ) - } - } -} - -@Sampled -@Composable -fun SimpleScalingLazyColumnWithSnap() { - val state = rememberScalingLazyListState() - ScalingLazyColumn( - rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(scrollableState = state), - flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(state = state), - modifier = Modifier.fillMaxWidth(), - state = state, - ) { - item { ListHeader { Text(text = "List Header") } } - items(20) { - Chip( - onClick = {}, - label = { Text("List item $it") }, - colors = ChipDefaults.secondaryChipColors(), - ) - } - } -} - -@Sampled -@Composable -fun ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo() { - val coroutineScope = rememberCoroutineScope() - val itemSpacing = 6.dp - // Line up the gap between the items on the center-line - val scrollOffset = with(LocalDensity.current) { -(itemSpacing / 2).roundToPx() } - val state = - rememberScalingLazyListState( - initialCenterItemIndex = 1, - initialCenterItemScrollOffset = scrollOffset, - ) - - ScalingLazyColumn( - modifier = Modifier.fillMaxWidth(), - anchorType = ScalingLazyListAnchorType.ItemStart, - verticalArrangement = Arrangement.spacedBy(itemSpacing), - state = state, - autoCentering = AutoCenteringParams(itemOffset = scrollOffset), - ) { - item { ListHeader { Text(text = "List Header") } } - items(20) { - Chip( - onClick = { - coroutineScope.launch { - // Add +1 to allow for the ListHeader - state.animateScrollToItem(it + 1, scrollOffset) - } - }, - label = { Text("List item $it") }, - colors = ChipDefaults.secondaryChipColors(), - ) - } - } -} - -@Sampled -@Composable -fun SimpleScalingLazyColumnWithContentPadding() { - ScalingLazyColumn( - modifier = Modifier.fillMaxWidth(), - contentPadding = PaddingValues(top = 20.dp, bottom = 20.dp), - autoCentering = null, - ) { - item { ListHeader { Text(text = "List Header") } } - items(20) { - Chip( - onClick = {}, - label = { Text("List item $it") }, - colors = ChipDefaults.secondaryChipColors(), - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt deleted file mode 100644 index 46ecc1b..0000000 --- a/wear/wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt +++ /dev/null @@ -1,168 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -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.saveable.rememberSaveableStateHolder -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.BasicSwipeToDismissBox -import androidx.wear.compose.foundation.SwipeToDismissValue -import androidx.wear.compose.foundation.edgeSwipeToDismiss -import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState -import androidx.wear.compose.material.Icon -import androidx.wear.compose.material.MaterialTheme -import androidx.wear.compose.material.SplitToggleChip -import androidx.wear.compose.material.Text -import androidx.wear.compose.material.ToggleChipDefaults - -@Sampled -@Composable -fun SimpleSwipeToDismissBox(navigateBack: () -> Unit) { - val state = rememberSwipeToDismissBoxState() - BasicSwipeToDismissBox(state = state, onDismissed = navigateBack) { isBackground -> - if (isBackground) { - Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.secondaryVariant)) - } else { - Column( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Swipe right to dismiss", color = MaterialTheme.colors.onPrimary) - } - } - } -} - -@Sampled -@Composable -fun StatefulSwipeToDismissBox() { - // State for managing a 2-level navigation hierarchy between - // MainScreen and ItemScreen composables. - // Alternatively, use SwipeDismissableNavHost from wear.compose.navigation. - var showMainScreen by remember { mutableStateOf(true) } - val saveableStateHolder = rememberSaveableStateHolder() - - // Swipe gesture dismisses ItemScreen to return to MainScreen. - val state = rememberSwipeToDismissBoxState() - LaunchedEffect(state.currentValue) { - if (state.currentValue == SwipeToDismissValue.Dismissed) { - state.snapTo(SwipeToDismissValue.Default) - showMainScreen = !showMainScreen - } - } - - // Hierarchy is ListScreen -> ItemScreen, so we show ListScreen as the background behind - // the ItemScreen, otherwise there's no background to show. - BasicSwipeToDismissBox( - state = state, - userSwipeEnabled = !showMainScreen, - backgroundKey = if (!showMainScreen) "MainKey" else "Background", - contentKey = if (showMainScreen) "MainKey" else "ItemKey", - ) { isBackground -> - if (isBackground || showMainScreen) { - // Best practice would be to use State Hoisting and leave this composable stateless. - // Here, we want to support MainScreen being shown from different destinations - // (either in the foreground or in the background during swiping) - that can be achieved - // using SaveableStateHolder and rememberSaveable as shown below. - saveableStateHolder.SaveableStateProvider( - key = "MainKey", - content = { - // Composable that maintains its own state - // and can be shown in foreground or background. - val checked = rememberSaveable { mutableStateOf(true) } - Column( - modifier = - Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), - ) { - SplitToggleChip( - checked = checked.value, - label = { Text("Item details") }, - modifier = Modifier.height(40.dp), - onCheckedChange = { v -> checked.value = v }, - onClick = { showMainScreen = false }, - toggleControl = { - Icon( - imageVector = - ToggleChipDefaults.checkboxIcon(checked = checked.value), - contentDescription = null, - ) - }, - ) - } - }, - ) - } else { - Column( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Show details here...", color = MaterialTheme.colors.onPrimary) - Text("Swipe right to dismiss", color = MaterialTheme.colors.onPrimary) - } - } - } -} - -@Sampled -@Composable -fun EdgeSwipeForSwipeToDismiss(navigateBack: () -> Unit) { - val state = rememberSwipeToDismissBoxState() - - // When using Modifier.edgeSwipeToDismiss, it is required that the element on which the - // modifier applies exists within a SwipeToDismissBox which shares the same state. - BasicSwipeToDismissBox(state = state, onDismissed = navigateBack) { isBackground -> - val horizontalScrollState = rememberScrollState(0) - if (isBackground) { - Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.secondaryVariant)) - } else { - Box(modifier = Modifier.fillMaxSize()) { - Text( - modifier = - Modifier.align(Alignment.Center) - .edgeSwipeToDismiss(state) - .horizontalScroll(horizontalScrollState), - text = - "This text can be scrolled horizontally - to dismiss, swipe " + - "right from the left edge of the screen (called Edge Swiping)", - ) - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt deleted file mode 100644 index aa0e085..0000000 --- a/wear/wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt +++ /dev/null @@ -1,282 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -@file:OptIn(ExperimentalWearFoundationApi::class) -@file:Suppress("DEPRECATION") - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.MoreVert -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.graphics.graphicsLayer -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.ExperimentalWearFoundationApi -import androidx.wear.compose.foundation.RevealValue -import androidx.wear.compose.foundation.SwipeToReveal -import androidx.wear.compose.foundation.expandableItem -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.rememberExpandableState -import androidx.wear.compose.foundation.rememberRevealState -import androidx.wear.compose.material.Chip -import androidx.wear.compose.material.ChipDefaults -import androidx.wear.compose.material.Icon -import androidx.wear.compose.material.ListHeader -import androidx.wear.compose.material.Text -import kotlin.math.abs -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun SwipeToRevealSample() { - val state = rememberRevealState() - val coroutineScope = rememberCoroutineScope() - SwipeToReveal( - state = state, - primaryAction = { - Box( - modifier = - Modifier.fillMaxSize().clickable { - /* Add the primary action */ - coroutineScope.launch { state.animateTo(RevealValue.RightRevealed) } - }, - contentAlignment = Alignment.Center, - ) { - Icon(imageVector = Icons.Outlined.Delete, contentDescription = "Delete") - } - }, - undoAction = { - Chip( - modifier = Modifier.fillMaxWidth(), - onClick = { - /* Add the undo action */ - coroutineScope.launch { state.animateTo(RevealValue.Covered) } - }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Undo") }, - ) - }, - ) { - Chip( - modifier = - Modifier.fillMaxWidth().semantics { - // Use custom actions to make the primary and secondary actions accessible - customActions = - listOf( - CustomAccessibilityAction("Delete") { - /* Add the primary action click handler */ - true - } - ) - }, - onClick = { /* the click action associated with chip */ }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Swipe Me") }, - ) - } -} - -@Sampled -@Composable -fun SwipeToRevealWithDelayedText() { - val state = rememberRevealState() - val coroutineScope = rememberCoroutineScope() - SwipeToReveal( - state = state, - primaryAction = { - Row( - modifier = - Modifier.fillMaxSize().clickable { - /* Add the primary action */ - coroutineScope.launch { state.animateTo(RevealValue.RightRevealed) } - }, - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Icon(imageVector = Icons.Outlined.Delete, contentDescription = "Delete") - if (abs(state.offset) > state.revealThreshold) { - // Delay the text appearance so that it has enough space to be displayed - val textAlpha = - animateFloatAsState( - targetValue = 1f, - animationSpec = tween(durationMillis = 250, delayMillis = 250), - label = "PrimaryActionTextAlpha", - ) - Box(modifier = Modifier.graphicsLayer { alpha = textAlpha.value }) { - Spacer(Modifier.size(5.dp)) - Text("Clear") - } - } - } - }, - undoAction = { - Chip( - modifier = Modifier.fillMaxWidth(), - onClick = { - /* Add the undo action */ - coroutineScope.launch { state.animateTo(RevealValue.Covered) } - }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Undo") }, - ) - }, - ) { - Chip( - modifier = - Modifier.fillMaxWidth().semantics { - // Use custom actions to make the primary and secondary actions accessible - customActions = - listOf( - CustomAccessibilityAction("Delete") { - /* Add the primary action click handler */ - true - } - ) - }, - onClick = { /* the click action associated with chip */ }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Swipe Me") }, - ) - } -} - -/** - * A sample on how to use Swipe To Reveal within a list of items, preferably [ScalingLazyColumn]. - */ -@Sampled -@Composable -fun SwipeToRevealWithExpandables() { - // Shape of actions should match with the overlay content. For example, Chips - // should use RoundedCornerShape(CornerSize(percent = 50)), Cards should use - // RoundedCornerShape with appropriate radius, based on the theme. - val actionShape = RoundedCornerShape(corner = CornerSize(percent = 50)) - val itemCount = 10 - val coroutineScope = rememberCoroutineScope() - val expandableStates = List(itemCount) { rememberExpandableState(initiallyExpanded = true) } - ScalingLazyColumn(modifier = Modifier.fillMaxSize()) { - item { ListHeader { Text("Scaling Lazy Column") } } - repeat(itemCount) { current -> - expandableItem(state = expandableStates[current]) { isExpanded -> - val revealState = rememberRevealState() - if (isExpanded) { - SwipeToReveal( - state = revealState, - primaryAction = { - Box( - modifier = - Modifier.fillMaxSize() - .background(Color.Red, actionShape) - .clickable { - coroutineScope.launch { - revealState.animateTo(RevealValue.RightRevealed) - } - }, - contentAlignment = Alignment.Center, - ) { - Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = "Delete", - ) - } - }, - secondaryAction = { - Box( - modifier = - Modifier.fillMaxSize() - .background(Color.Gray, actionShape) - .clickable { /* trigger the optional action */ }, - contentAlignment = Alignment.Center, - ) { - Icon( - imageVector = Icons.Outlined.MoreVert, - contentDescription = "More Options", - ) - } - }, - undoAction = { - Chip( - modifier = Modifier.fillMaxWidth(), - onClick = { - coroutineScope.launch { - revealState.animateTo(RevealValue.Covered) - } - }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Undo") }, - ) - }, - onFullSwipe = { - coroutineScope.launch { - delay(1000) - expandableStates[current].expanded = false - } - }, - ) { - Chip( - modifier = - Modifier.fillMaxWidth().semantics { - // Use custom actions to make the primary and secondary actions - // accessible - customActions = - listOf( - CustomAccessibilityAction("Delete") { - /* Add the primary action click handler */ - coroutineScope.launch { - revealState.animateTo(RevealValue.RightRevealed) - } - true - }, - CustomAccessibilityAction("More Options") { - /* Add the secondary action click handler */ - true - }, - ) - }, - onClick = { /* the click action associated with chip */ }, - colors = ChipDefaults.secondaryChipColors(), - label = { Text(text = "Swipe Me") }, - ) - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt b/wear/wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt deleted file mode 100644 index 87e92be..0000000 --- a/wear/wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt +++ /dev/null @@ -1,335 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.foundation.samples - -import androidx.annotation.Sampled -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow -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.layout.LocalPinnableContainer -import androidx.compose.ui.layout.PinnableContainer -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastFirstOrNull -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.TransformingLazyColumnDefaults -import androidx.wear.compose.foundation.lazy.TransformingLazyColumnFirstLayoutItemProvider -import androidx.wear.compose.foundation.lazy.TransformingLazyColumnFirstLayoutItemProvider.ItemEdge -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults -import androidx.wear.compose.material.Text -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.CardDefaults -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.TitleCard -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight -import kotlin.math.abs -import kotlinx.coroutines.launch - -@Sampled -@Preview -@Composable -fun SimpleTransformingLazyColumnSample() { - val transformationSpec = rememberTransformationSpec() - TransformingLazyColumn(contentPadding = PaddingValues(20.dp)) { - items(count = 10) { index -> - Button( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text(text = "Item $index") - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnWithSnapSample() { - val transformationSpec = rememberTransformationSpec() - val state = rememberTransformingLazyColumnState() - TransformingLazyColumn( - rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(scrollableState = state), - flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(state = state), - modifier = Modifier.fillMaxWidth(), - state = state, - contentPadding = PaddingValues(20.dp), - ) { - items(count = 20) { index -> - Button( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text(text = "Item $index") - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnAnimateItemSample() { - val state = rememberTransformingLazyColumnState() - - var list by remember { mutableStateOf(listOf("1", "2", "3")) } - - var next by remember { mutableIntStateOf(4) } - - Box(Modifier.fillMaxSize()) { - TransformingLazyColumn( - state = state, - contentPadding = PaddingValues(5.dp), - modifier = Modifier.background(Color.Black).fillMaxSize(), - ) { - items(list.size, key = { list[it] }) { - Text( - "Item ${list[it]}", - Modifier.animateItem().clickable { - list = list.filter { elem -> elem != list[it] } - }, - ) - } - } - Text( - "+", - Modifier.align(Alignment.CenterStart).padding(horizontal = 5.dp).clickable { - if (list.size < 25) list = list + "${next++}" - }, - ) - Text( - "S", - Modifier.align(Alignment.CenterEnd).padding(horizontal = 5.dp).clickable { - list = list.shuffled() - }, - ) - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnScrollToItemSample() { - val state = - rememberTransformingLazyColumnState( - // Customize initial scroll position of the TransformingLazyColumn. - initialAnchorItemIndex = 10 - ) - val coroutineScope = rememberCoroutineScope() - - TransformingLazyColumn( - modifier = Modifier.background(Color.Black), - state = state, - contentPadding = PaddingValues(vertical = 20.dp), - ) { - items(count = 20) { - Text( - "Item $it", - modifier = - Modifier.drawBehind { - val isCentered = - it == state.anchorItemIndex && - abs(state.anchorItemScrollOffset) < size.height - drawRect(if (isCentered) Color.Green else Color.DarkGray) - } - .padding(5.dp) - .clickable { coroutineScope.launch { state.scrollToItem(it) } }, - ) - } - - item { - Text( - "Scroll to top", - modifier = - Modifier.clickable { coroutineScope.launch { state.animateScrollToItem(0) } }, - ) - } - } - - LaunchedEffect(Unit) { - snapshotFlow { state.anchorItemIndex }.collect { println("Anchor item index: $it") } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnMinimumVerticalContentPaddingSample() { - val transformationSpec = rememberTransformationSpec() - - TransformingLazyColumn(contentPadding = PaddingValues(horizontal = 20.dp)) { - items(count = 20) { index -> - Button( - modifier = - Modifier.fillMaxWidth() - .transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text(text = "Item $index") - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnFirstLayoutItemProviderSample() { - val state = rememberTransformingLazyColumnState() - val transformationSpec = rememberTransformationSpec() - var expandedItemIndex by remember { mutableIntStateOf(-1) } - - // This sample demonstrates how to use the provider API to control the direction of content - // shifting. By default, TransformingLazyColumn uses the center item as the layout reference. - // This means that if an item above the center expands, it pushes content upwards; - // if below, it pushes downwards. - // - // Here, we fix the Bottom/End edge of the clicked item regardless of its position on screen, - // so that when its animated content appears, the card predictably expands *upwards* every time. - val upwardExpandingItemProvider = - remember(state) { - TransformingLazyColumnFirstLayoutItemProvider { centerItem -> - val item = expandedItemIndex - - // Yield to the standard layout behavior during active scrolls. - // This avoids custom layout overhead and ensures the [TransformingLazyColumn] - // tracks the user's scroll gesture using its default center layout reference. - if (item == -1 || state.isScrollInProgress) { - return@TransformingLazyColumnFirstLayoutItemProvider centerItem - } - - // Look up the item's offset from state.layoutInfo (which holds the details - // from the previous measure pass) to maintain its visual position in the current - // pass. - state.layoutInfo.visibleItems - .fastFirstOrNull { visibleItem -> visibleItem.index == item } - ?.let { visibleItem -> - TransformingLazyColumnFirstLayoutItemProvider.ItemInfo( - key = visibleItem.key, - index = visibleItem.index, - // Pin the bottom edge of the item - itemEdge = ItemEdge.End, - // Calculate the exact bottom offset from the previous pass - offset = visibleItem.offset + visibleItem.transformedHeight, - ) - } ?: centerItem - } - } - - TransformingLazyColumn( - state = state, - contentPadding = PaddingValues(horizontal = 20.dp), - firstLayoutItemProvider = upwardExpandingItemProvider, - ) { - items(count = 10, key = { it }) { cardIndex -> - val isExpanded = expandedItemIndex == cardIndex - TitleCard( - onClick = { expandedItemIndex = cardIndex }, - modifier = - Modifier.minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ) - .fillMaxWidth() - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - title = { Text("Card $cardIndex") }, - subtitle = { - AnimatedVisibility(isExpanded) { Text("Expanded content is available here") } - }, - content = { Text("Tap to expand") }, - ) - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnPinnableContainerSample() { - val state = rememberTransformingLazyColumnState() - val transformationSpec = rememberTransformationSpec() - - TransformingLazyColumn(state = state, contentPadding = PaddingValues(horizontal = 20.dp)) { - items(count = 20) { index -> - var isPinned by remember { mutableStateOf(false) } - var pinHandle by remember { mutableStateOf(null) } - val pinnableContainer = LocalPinnableContainer.current - - // This state will be reset if the item is scrolled out and not pinned - var counter by remember { mutableIntStateOf(0) } - - TitleCard( - onClick = { counter++ }, - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - title = { Text("Item $index") }, - subtitle = { Text("Count: $counter (Tap card to +)") }, - content = { - // This button toggles the pin state - Button( - onClick = { - if (isPinned) { - pinHandle?.release().also { pinHandle = null } - isPinned = false - } else { - pinHandle = pinnableContainer?.pin() - isPinned = true - } - }, - colors = - if (isPinned) { - ButtonDefaults.buttonColors() - } else { - ButtonDefaults.filledTonalButtonColors() - }, - ) { - Text(if (isPinned) "Unpin" else "Pin Off-screen") - } - }, - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt b/wear/wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt deleted file mode 100644 index 79036c1..0000000 --- a/wear/wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt +++ /dev/null @@ -1,377 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.AccountCircle -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.graphicsLayer -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.AlertDialog -import androidx.wear.compose.material3.AlertDialogDefaults -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.SwitchButton -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Sampled -@Composable -@Preview -fun AlertDialogWithConfirmAndDismissSample() { - var showDialog by remember { mutableStateOf(false) } - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - icon = { - Icon( - Icons.Rounded.AccountCircle, - modifier = Modifier.size(32.dp), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("Enable Battery Saver Mode?") }, - text = { Text("Your battery is low. Turn on battery saver.") }, - confirmButton = { - AlertDialogDefaults.ConfirmButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) - }, - dismissButton = { - AlertDialogDefaults.DismissButton( - onClick = { - // Perform dismiss action here - showDialog = false - } - ) - }, - ) { - item { Text(text = "You can configure battery saver mode in the setting menu.") } - item { Button(onClick = {}) { Text(text = "Go to settings") } } - } -} - -@Preview -@Sampled -@Composable -fun AlertDialogWithConfirmAndDismissTransformingContentSample() { - var showDialog by remember { mutableStateOf(false) } - val transformationSpec = rememberTransformationSpec() - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - icon = { - Icon( - Icons.Rounded.AccountCircle, - modifier = Modifier.size(32.dp), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("Enable Battery Saver Mode?") }, - text = { Text("Your battery is low. Turn on battery saver.") }, - transformationSpec = transformationSpec, - confirmButton = { - AlertDialogDefaults.ConfirmButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) - }, - dismissButton = { - AlertDialogDefaults.DismissButton( - onClick = { - // Perform dismiss action here - showDialog = false - } - ) - }, - ) { - item { - Text( - modifier = - Modifier.transformedHeight(this, transformationSpec).graphicsLayer { - with(SurfaceTransformation(transformationSpec)) { - applyContentTransformation() - applyContainerTransformation() - } - }, - text = "You can configure battery saver mode in the setting menu.", - ) - } - item { - Button( - modifier = Modifier.transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text(text = "Go to settings") - } - } - } -} - -@Preview -@Sampled -@Composable -fun AlertDialogWithEdgeButtonSample() { - var showDialog by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - icon = { - Icon( - Icons.Rounded.AccountCircle, - modifier = Modifier.size(32.dp), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("Mobile network is not currently available") }, - text = { Text("Please try again later or check your network settings.") }, - edgeButton = { - AlertDialogDefaults.EdgeButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) - }, - ) -} - -@Preview -@Sampled -@Composable -fun AlertDialogWithEdgeButtonTransformingContentSample() { - var showDialog by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - icon = { - Icon( - Icons.Rounded.AccountCircle, - modifier = Modifier.size(32.dp), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("Mobile network is not currently available") }, - text = { Text("Please try again later or check your network settings.") }, - transformationSpec = rememberTransformationSpec(), - edgeButton = { - AlertDialogDefaults.EdgeButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) - }, - ) -} - -@Preview -@Sampled -@Composable -fun AlertDialogWithContentGroupsSample() { - var showDialog by remember { mutableStateOf(false) } - var weatherEnabled by remember { mutableStateOf(false) } - var calendarEnabled by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - title = { Text("Share your location") }, - text = { Text(" The following apps have asked you to share your location") }, - edgeButton = { - AlertDialogDefaults.EdgeButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) { - Text("Share once") - } - }, - ) { - item { - SwitchButton( - modifier = Modifier.fillMaxWidth(), - checked = weatherEnabled, - onCheckedChange = { weatherEnabled = it }, - label = { Text("Weather") }, - ) - } - item { - SwitchButton( - modifier = Modifier.fillMaxWidth(), - checked = calendarEnabled, - onCheckedChange = { calendarEnabled = it }, - label = { Text("Calendar") }, - ) - } - item { AlertDialogDefaults.GroupSeparator() } - item { - FilledTonalButton( - modifier = Modifier.fillMaxWidth(), - onClick = {}, - label = { Text(modifier = Modifier.fillMaxWidth(), text = "Never share") }, - ) - } - item { - FilledTonalButton( - modifier = Modifier.fillMaxWidth(), - onClick = {}, - label = { Text(modifier = Modifier.fillMaxWidth(), text = "Share always") }, - ) - } - } -} - -@Preview -@Sampled -@Composable -fun AlertDialogWithContentGroupsTransformingContentSample() { - var showDialog by remember { mutableStateOf(false) } - var weatherEnabled by remember { mutableStateOf(false) } - var calendarEnabled by remember { mutableStateOf(false) } - val transformationSpec = rememberTransformationSpec() - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showDialog = true }, - label = { Text("Show Dialog") }, - ) - } - AlertDialog( - visible = showDialog, - onDismissRequest = { showDialog = false }, - title = { Text("Share your location") }, - text = { Text(" The following apps have asked you to share your location") }, - transformationSpec = transformationSpec, - edgeButton = { - AlertDialogDefaults.EdgeButton( - onClick = { - // Perform confirm action here - showDialog = false - } - ) { - Text("Share once") - } - }, - ) { - item { - SwitchButton( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - checked = weatherEnabled, - onCheckedChange = { weatherEnabled = it }, - label = { Text("Weather") }, - transformation = SurfaceTransformation(transformationSpec), - ) - } - item { - SwitchButton( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - checked = calendarEnabled, - onCheckedChange = { calendarEnabled = it }, - label = { Text("Calendar") }, - transformation = SurfaceTransformation(transformationSpec), - ) - } - item { AlertDialogDefaults.GroupSeparator() } - item { - FilledTonalButton( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - onClick = {}, - label = { Text(modifier = Modifier.fillMaxWidth(), text = "Never share") }, - transformation = SurfaceTransformation(transformationSpec), - ) - } - item { - FilledTonalButton( - modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec), - onClick = {}, - label = { Text(modifier = Modifier.fillMaxWidth(), text = "Share always") }, - transformation = SurfaceTransformation(transformationSpec), - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt b/wear/wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt deleted file mode 100644 index 4cd8d4a..0000000 --- a/wear/wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt +++ /dev/null @@ -1,165 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontVariation -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.wear.compose.material3.AnimatedText -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.rememberAnimatedTextFontRegistry -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun AnimatedTextSample() { - val scope = rememberCoroutineScope() - val animatable = remember { Animatable(0f) } - val animate = { - scope.launch { - // Animate from 0 to 1 and then back to 0. - animatable.animateTo(1f) - animatable.animateTo(0f) - } - } - val animatedTextFontRegistry = - rememberAnimatedTextFontRegistry( - // Variation axes at the start of the animation, width 10, weight 200 - startFontVariationSettings = - FontVariation.Settings(FontVariation.width(10f), FontVariation.weight(200)), - // Variation axes at the end of the animation, width 100, weight 500 - endFontVariationSettings = - FontVariation.Settings(FontVariation.width(100f), FontVariation.weight(500)), - startFontSize = 30.sp, - endFontSize = 40.sp, - ) - AnimatedText( - text = "Hello!", - fontRegistry = animatedTextFontRegistry, - // Content alignment anchors the animation at the vertical center, expanding horizontally - contentAlignment = Alignment.CenterStart, - progressFraction = { animatable.value }, - modifier = Modifier.clickable(onClick = { animate() }), - ) - LaunchedEffect(Unit) { animate() } -} - -@Sampled -@Composable -fun AnimatedTextSampleButtonResponse() { - val scope = rememberCoroutineScope() - val animatedTextFontRegistry = - rememberAnimatedTextFontRegistry( - // Variation axes at the start of the animation, width 10, weight 200 - startFontVariationSettings = - FontVariation.Settings(FontVariation.width(10f), FontVariation.weight(200)), - // Variation axes at the end of the animation, width 100, weight 500 - endFontVariationSettings = - FontVariation.Settings(FontVariation.width(100f), FontVariation.weight(500)), - startFontSize = 30.sp, - endFontSize = 30.sp, - ) - val number = remember { mutableIntStateOf(0) } - val textAnimatable = remember { Animatable(0f) } - Row(verticalAlignment = Alignment.CenterVertically) { - Button( - modifier = Modifier.padding(horizontal = 16.dp), - onClick = { - number.value -= 1 - scope.launch { - textAnimatable.animateTo(1f) - textAnimatable.animateTo(0f) - } - }, - label = { - Text(modifier = Modifier.semantics { contentDescription = "Decrease" }, text = "-") - }, - ) - AnimatedText( - text = "${number.value}", - fontRegistry = animatedTextFontRegistry, - progressFraction = { textAnimatable.value }, - ) - Button( - modifier = Modifier.padding(horizontal = 16.dp), - onClick = { - number.value += 1 - scope.launch { - textAnimatable.animateTo(1f) - textAnimatable.animateTo(0f) - } - }, - label = { - Text(modifier = Modifier.semantics { contentDescription = "Increase" }, text = "+") - }, - ) - } -} - -@Sampled -@Composable -fun AnimatedTextSampleSharedFontRegistry() { - val animatedTextFontRegistry = - rememberAnimatedTextFontRegistry( - // Variation axes at the start of the animation, width 50, weight 300 - startFontVariationSettings = - FontVariation.Settings(FontVariation.width(50f), FontVariation.weight(300)), - // Variation axes at the end of the animation are the same as the start axes - endFontVariationSettings = - FontVariation.Settings(FontVariation.width(50f), FontVariation.weight(300)), - startFontSize = 15.sp, - endFontSize = 25.sp, - ) - val firstAnimatable = remember { Animatable(0f) } - val secondAnimatable = remember { Animatable(0f) } - Column(horizontalAlignment = Alignment.CenterHorizontally) { - AnimatedText( - text = "Top Text", - fontRegistry = animatedTextFontRegistry, - progressFraction = { firstAnimatable.value }, - ) - AnimatedText( - text = "Bottom Text", - fontRegistry = animatedTextFontRegistry, - progressFraction = { secondAnimatable.value }, - ) - } - LaunchedEffect(Unit) { - firstAnimatable.animateTo(1f) - firstAnimatable.animateTo(0f) - secondAnimatable.animateTo(1f) - secondAnimatable.animateTo(0f) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt deleted file mode 100644 index b29826a..0000000 --- a/wear/wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt +++ /dev/null @@ -1,91 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonGroup -import androidx.wear.compose.material3.Text - -@Sampled -@Composable -fun ButtonGroupSample() { - val interactionSource1 = remember { MutableInteractionSource() } - val interactionSource2 = remember { MutableInteractionSource() } - - Box(contentAlignment = Alignment.Center) { - ButtonGroup(Modifier.fillMaxWidth()) { - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource1), - interactionSource = interactionSource1, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Text("L") } - } - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource2), - interactionSource = interactionSource2, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Text("R") } - } - } - } -} - -@Sampled -@Composable -fun ButtonGroupThreeButtonsSample() { - val interactionSource1 = remember { MutableInteractionSource() } - val interactionSource2 = remember { MutableInteractionSource() } - val interactionSource3 = remember { MutableInteractionSource() } - - Box(contentAlignment = Alignment.Center) { - ButtonGroup(Modifier.fillMaxWidth()) { - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource1), - interactionSource = interactionSource1, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Text("A") } - } - Button( - onClick = {}, - modifier = Modifier.weight(1.5f).animateWidth(interactionSource2), - interactionSource = interactionSource2, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Text("B") } - } - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource3), - interactionSource = interactionSource3, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Text("C") } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ButtonSample.kt.md.txt deleted file mode 100644 index 5a3c305..0000000 --- a/wear/wear-compose-m3/references/material3/ButtonSample.kt.md.txt +++ /dev/null @@ -1,496 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material.icons.filled.KeyboardArrowUp -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -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 -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.onClick -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.sp -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.ChildButton -import androidx.wear.compose.material3.CompactButton -import androidx.wear.compose.material3.CompactButtonDefaults -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.OutlinedButton -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureAction -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicator -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicatorState -import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture -import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun SimpleButtonSample(modifier: Modifier = Modifier) { - Button(onClick = { /* Do something */ }, label = { Text("Simple Button") }, modifier = modifier) -} - -@Sampled -@Composable -fun ButtonSample() { - Button( - onClick = { /* Do something */ }, - label = { Text("Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun ButtonLargeIconSample(enabled: Boolean = true) { - // When customising the icon size, it is recommended to also specify - // the associated content padding - Button( - onClick = { /* Do something */ }, - enabled = enabled, - label = { Text("Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.LargeIconSize), - ) - }, - contentPadding = ButtonDefaults.ButtonWithLargeIconContentPadding, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun ButtonExtraLargeIconSample(enabled: Boolean = true) { - // When customising the icon size, it is recommended to also specify - // the associated content padding - Button( - onClick = { /* Do something */ }, - enabled = enabled, - label = { Text("Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.ExtraLargeIconSize), - ) - }, - contentPadding = ButtonDefaults.ButtonWithExtraLargeIconContentPadding, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun ButtonWithImageSample(enabled: Boolean = true) { - Button( - onClick = { /* Do something */ }, - containerPainter = - ButtonDefaults.containerPainter( - image = painterResource(id = R.drawable.backgroundimage) - ), - enabled = enabled, - label = { Text("Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun SimpleFilledTonalButtonSample() { - FilledTonalButton( - onClick = { /* Do something */ }, - label = { Text("Filled Tonal Button") }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun FilledTonalButtonSample() { - FilledTonalButton( - onClick = { /* Do something */ }, - label = { Text("Filled Tonal Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun SimpleFilledVariantButtonSample() { - Button( - onClick = { /* Do something */ }, - colors = ButtonDefaults.filledVariantButtonColors(), - label = { Text("Filled Variant Button") }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun FilledVariantButtonSample() { - Button( - onClick = { /* Do something */ }, - colors = ButtonDefaults.filledVariantButtonColors(), - label = { Text("Filled Variant Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun SimpleOutlinedButtonSample() { - OutlinedButton( - onClick = { /* Do something */ }, - label = { Text("Outlined Button") }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun OutlinedButtonSample() { - OutlinedButton( - onClick = { /* Do something */ }, - label = { Text("Outlined Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun SimpleChildButtonSample() { - ChildButton( - onClick = { /* Do something */ }, - label = { - Text("Child Button", textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun ChildButtonSample() { - ChildButton( - onClick = { /* Do something */ }, - label = { Text("Child Button") }, - secondaryLabel = { Text("Secondary label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Sampled -@Composable -fun CompactButtonSample(modifier: Modifier = Modifier) { - CompactButton( - onClick = { /* Do something */ }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(CompactButtonDefaults.ExtraSmallIconSize), - ) - }, - modifier = modifier, - label = { Text("Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis) }, - ) -} - -@Sampled -@Composable -fun CompactButtonWithContentSample(modifier: Modifier = Modifier) { - CompactButton( - onClick = { /* Do something */ }, - modifier = modifier, - content = { - Box( - Modifier.size(CompactButtonDefaults.ExtraSmallIconSize) - .clip(CircleShape) - .background(Color.Green) - ) - Spacer(Modifier.width(ButtonDefaults.IconSpacing)) - Text( - "Custom content", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.Bold, - fontStyle = FontStyle.Italic, - letterSpacing = 0.5.sp, - ) - }, - ) -} - -@Sampled -@Composable -fun CompactButtonWithOnLongClickSample( - onClickHandler: () -> Unit, - onLongClickHandler: () -> Unit, - modifier: Modifier = Modifier, -) { - CompactButton( - onClick = onClickHandler, - onLongClick = onLongClickHandler, - onLongClickLabel = "Long click", - label = { Text("Long clickable") }, - modifier = - modifier.semantics { - // Also override the 'click label' to say 'Double tap to press' instead of - // the usual 'Double tap to activate'. - onClick("press") { false } - }, - ) -} - -@Sampled -@Composable -fun FilledTonalCompactButtonSample(modifier: Modifier = Modifier) { - CompactButton( - onClick = { /* Do something */ }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(CompactButtonDefaults.ExtraSmallIconSize), - ) - }, - colors = ButtonDefaults.filledTonalButtonColors(), - modifier = modifier, - label = { - Text("Filled Tonal Compact Button", maxLines = 1, overflow = TextOverflow.Ellipsis) - }, - ) -} - -@Sampled -@Composable -fun OutlinedCompactButtonSample(modifier: Modifier = Modifier) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - var expanded by remember { mutableStateOf(false) } - if (expanded) { - Text("A multiline string showing two lines") - } else { - Text("One line text") - } - Spacer(Modifier.height(ButtonDefaults.IconSpacing)) - CompactButton( - onClick = { expanded = !expanded }, - colors = ButtonDefaults.outlinedButtonColors(), - border = ButtonDefaults.outlinedButtonBorder(enabled = true), - modifier = modifier, - content = { - if (expanded) { - Text("Show Less", maxLines = 1, overflow = TextOverflow.Ellipsis) - } else { - Text("Show More", maxLines = 1, overflow = TextOverflow.Ellipsis) - } - Spacer(Modifier.width(ButtonDefaults.IconSpacing)) - if (expanded) { - Icon( - Icons.Filled.KeyboardArrowUp, - contentDescription = "Collapse", - modifier = Modifier.size(CompactButtonDefaults.ExtraSmallIconSize), - ) - } else { - Icon( - Icons.Filled.KeyboardArrowDown, - contentDescription = "Expand", - modifier = Modifier.size(CompactButtonDefaults.ExtraSmallIconSize), - ) - } - }, - ) - } -} - -@Sampled -@Composable -fun ButtonContentWithOneHandedGestureSample() { - var label by remember { mutableStateOf("Filled Button") } - val onClick = remember { { label = "Gestured" } } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "click", - interactionSource = interactionSource, - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - onGesture = onClick, - ), - content = { - OneHandedGestureClickIndicator( - gestureConfiguration = gestureConfig, - state = indicatorState, - gestureIndicatorTint = MaterialTheme.colorScheme.onPrimary, - ) { - ButtonDefaults.Content( - secondaryLabel = { Text("Secondary Label") }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - colors = ButtonDefaults.buttonColors(), - label = { Text(label) }, - ) - } - }, - ) - } -} - -@Sampled -@Composable -fun CompactButtonContentWithOneHandedGestureSample() { - var label by remember { mutableStateOf("Compact Button") } - val onClick = remember { { label = "Gestured" } } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CompactButton( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "click", - interactionSource = interactionSource, - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - onGesture = onClick, - ), - content = { - OneHandedGestureClickIndicator( - gestureConfiguration = gestureConfig, - state = indicatorState, - gestureIndicatorTint = MaterialTheme.colorScheme.onPrimary, - ) { - CompactButtonDefaults.Content( - icon = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(CompactButtonDefaults.ExtraSmallIconSize), - ) - }, - colors = ButtonDefaults.buttonColors(), - label = { Text(label) }, - ) - } - }, - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/CardSample.kt.md.txt b/wear/wear-compose-m3/references/material3/CardSample.kt.md.txt deleted file mode 100644 index bcf5d47..0000000 --- a/wear/wear-compose-m3/references/material3/CardSample.kt.md.txt +++ /dev/null @@ -1,470 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight -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.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -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.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.AppCard -import androidx.wear.compose.material3.Card -import androidx.wear.compose.material3.CardDefaults -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.OutlinedCard -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TitleCard -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureAction -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicator -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicatorState -import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture -import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration -import kotlinx.coroutines.launch - -@Preview -@Sampled -@Composable -fun CardSample() { - Card(onClick = { /* Do something */ }) { Text("Card") } -} - -@Sampled -@Composable -fun NonClickableCardSample() { - Card { Text("Non Clickable Card") } -} - -@Sampled -@Composable -fun CardWithOnLongClickSample(onLongClickHandler: () -> Unit) { - Card( - onClick = { /* Do something */ }, - onLongClick = onLongClickHandler, - onLongClickLabel = "Long click", - ) { - Text("Card with long click") - } -} - -@Sampled -@Composable -fun AppCardSample() { - AppCard( - onClick = { /* Do something */ }, - appName = { Text("App name") }, - title = { Text("Card title") }, - time = { Text("Now") }, - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun NonClickableAppCardSample() { - AppCard( - appName = { Text("App name") }, - title = { Text("Card title") }, - time = { Text("Now") }, - ) { - Text("Non clickable card content") - } -} - -@Sampled -@Composable -fun AppCardWithIconSample() { - AppCard( - onClick = { /* Do something */ }, - appName = { Text("App name") }, - appImage = { - Icon( - painter = painterResource(id = android.R.drawable.star_big_off), - contentDescription = "Star icon", - modifier = - Modifier.size(CardDefaults.AppImageSize) - .wrapContentSize(align = Alignment.Center), - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("Card title") }, - time = { Text("Now") }, - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun AppCardWithImageSample() { - val configuration = LocalConfiguration.current - // Add padding to the end of the image in order to maintain the correct proportions - // between the image and the card. - val imageEndPaddingDp = (0.15f * configuration.screenWidthDp).dp - AppCard( - onClick = { /* Do something */ }, - appName = { Text("App name") }, - appImage = { - Icon( - painter = painterResource(id = android.R.drawable.star_big_off), - contentDescription = "Star icon", - modifier = - Modifier.size(CardDefaults.AppImageSize) - .wrapContentSize(align = Alignment.Center), - tint = MaterialTheme.colorScheme.primary, - ) - }, - title = { Text("With image") }, - time = { Text("Now") }, - ) { - Spacer(modifier = Modifier.height(4.dp)) - Row(modifier = Modifier.fillMaxWidth()) { - Image( - modifier = - Modifier.weight(1f).aspectRatio(16f / 9f).clip(RoundedCornerShape(16.dp)), - painter = painterResource(id = R.drawable.card_content_image), - contentScale = ContentScale.Crop, - contentDescription = null, - ) - Spacer(modifier = Modifier.width(imageEndPaddingDp)) - } - } -} - -@Sampled -@Composable -fun TitleCardSample() { - TitleCard( - onClick = { /* Do something */ }, - title = { Text("Title card") }, - time = { Text("Now") }, - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun NonClickableTitleCardSample() { - TitleCard(title = { Text("Title card") }, time = { Text("Now") }) { - Text("Non clickable Card content") - } -} - -@Sampled -@Composable -fun TitleCardWithSubtitleAndTimeSample() { - TitleCard( - onClick = { /* Do something */ }, - time = { Text("Now") }, - title = { Text("Title card") }, - subtitle = { Text("Subtitle") }, - ) -} - -@Preview -@Sampled -@Composable -fun TitleCardWithMultipleImagesSample() { - TitleCard( - onClick = { /* Do something */ }, - title = { Text("Title card") }, - time = { Text("Now") }, - modifier = Modifier.semantics { contentDescription = "Background image" }, - ) { - Spacer(Modifier.height(4.dp)) - Row(modifier = Modifier.fillMaxWidth()) { - Image( - modifier = - Modifier.weight(2f) - .height(68.dp) - .align(Alignment.CenterVertically) - .clip(RoundedCornerShape(16.dp)), - painter = painterResource(id = R.drawable.card_content_image), - contentScale = ContentScale.Crop, - contentDescription = null, - ) - Spacer(Modifier.width(4.dp)) - Image( - modifier = - Modifier.weight(1f) - .height(68.dp) - .align(Alignment.CenterVertically) - .clip(RoundedCornerShape(16.dp)), - painter = painterResource(id = R.drawable.card_content_image), - contentScale = ContentScale.Crop, - contentDescription = null, - ) - } - } -} - -@Sampled -@Composable -fun TitleCardWithImageWithTimeAndTitleSample() { - TitleCard( - onClick = { /* Do something */ }, - containerPainter = - CardDefaults.containerPainter(image = painterResource(id = R.drawable.backgroundimage)), - title = { Text("Card title") }, - subtitle = { Text("Subtitle") }, - time = { Text("Now") }, - contentPadding = CardDefaults.CardWithContainerPainterContentPadding, - modifier = Modifier.semantics { contentDescription = "Background image" }, - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun NonClickableTitleCardWithImageWithTimeAndTitleSample() { - TitleCard( - containerPainter = - CardDefaults.containerPainter(image = painterResource(id = R.drawable.backgroundimage)), - title = { Text("Card title") }, - subtitle = { Text("Subtitle") }, - time = { Text("Now") }, - contentPadding = CardDefaults.CardWithContainerPainterContentPadding, - modifier = Modifier.semantics { contentDescription = "Background image" }, - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun OutlinedCardSample() { - OutlinedCard(onClick = { /* Do something */ }) { Text("Outlined card") } -} - -@Sampled -@Composable -fun NonClickableOutlinedCardSample() { - OutlinedCard { Text("Non-clickable outlined card") } -} - -@Sampled -@Composable -fun ImageCardSample() { - Card( - onClick = { /* Do something */ }, - containerPainter = - CardDefaults.containerPainter(image = painterResource(id = R.drawable.backgroundimage)), - ) { - Text("Image card") - } -} - -@Sampled -@Composable -fun NonClickableImageCardSample() { - Card( - containerPainter = - CardDefaults.containerPainter(image = painterResource(id = R.drawable.backgroundimage)) - ) { - Text("Non clickable image card") - } -} - -@Sampled -@Composable -fun OutlinedAppCardSample() { - AppCard( - onClick = { /* Do something */ }, - appName = { Text("App name") }, - appImage = { - Icon( - Icons.Filled.Favorite, - contentDescription = "Favorite icon", - modifier = Modifier.size(CardDefaults.AppImageSize), - ) - }, - title = { Text("App card") }, - time = { Text("Now") }, - colors = CardDefaults.outlinedCardColors(), - border = CardDefaults.outlinedCardBorder(), - ) { - Text("Card content") - } -} - -@Sampled -@Composable -fun OutlinedTitleCardSample() { - TitleCard( - onClick = { /* Do something */ }, - title = { Text("Title card") }, - time = { Text("Now") }, - colors = CardDefaults.outlinedCardColors(), - border = CardDefaults.outlinedCardBorder(), - ) { - Text("Card content") - } -} - -@Sampled -@Preview -@Composable -fun CardFillContentSample() { - Card( - onClick = { /* Do something */ }, - // Constrains the card to fill background up to the intrinsic height. - modifier = Modifier.height(IntrinsicSize.Min), - ) { - Text( - "Filled Content", - color = MaterialTheme.colorScheme.onPrimary, - modifier = - Modifier.fillMaxHeight() - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.primary) - .wrapContentSize(Alignment.Center), - ) - } -} - -@Sampled -@Composable -fun AppCardContentWithOneHandedGestureSample() { - var label by remember { mutableStateOf("App Card") } - val onClick = remember { { label = "Gestured" } } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Card( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.padding(horizontal = 12.dp) - .fillMaxWidth() - .oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "click", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - interactionSource = interactionSource, - onGesture = onClick, - ), - ) { - OneHandedGestureClickIndicator( - gestureConfiguration = gestureConfig, - state = indicatorState, - gestureIndicatorTint = MaterialTheme.colorScheme.onSurface, - ) { - CardDefaults.AppCardContent( - appName = { Text("App Name") }, - title = { Text(label) }, - appImage = { - Icon( - painter = painterResource(R.drawable.ic_favorite_rounded), - contentDescription = "Favorite icon", - modifier = Modifier.size(CardDefaults.AppImageSize), - ) - }, - time = { Text("now") }, - ) { - Text("Card body") - } - } - } - } -} - -@Sampled -@Composable -fun TitleCardContentWithOneHandedGestureSample() { - var label by remember { mutableStateOf("Title Card") } - val onClick = remember { { label = "Gestured" } } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Card( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.padding(horizontal = 12.dp) - .fillMaxWidth() - .oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "click", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - interactionSource = interactionSource, - onGesture = onClick, - ), - ) { - OneHandedGestureClickIndicator( - gestureConfiguration = gestureConfig, - state = indicatorState, - gestureIndicatorTint = MaterialTheme.colorScheme.onSurface, - ) { - CardDefaults.TitleCardContent( - title = { Text(label) }, - time = { Text("now") }, - subtitle = { Text("Subtitle") }, - ) { - Text("Card body") - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt deleted file mode 100644 index 3ae73bd..0000000 --- a/wear/wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt +++ /dev/null @@ -1,69 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -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.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.material3.CheckboxButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.SplitCheckboxButton -import androidx.wear.compose.material3.Text - -@Sampled -@Preview -@Composable -fun CheckboxButtonSample() { - var checked by remember { mutableStateOf(true) } - CheckboxButton( - label = { Text("Checkbox Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - secondaryLabel = { - Text("With secondary label", maxLines = 2, overflow = TextOverflow.Ellipsis) - }, - checked = checked, - onCheckedChange = { checked = it }, - icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite icon") }, - enabled = true, - ) -} - -@Sampled -@Preview -@Composable -fun SplitCheckboxButtonSample() { - var checked by remember { mutableStateOf(true) } - SplitCheckboxButton( - label = { Text("Split Checkbox Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - checked = checked, - onCheckedChange = { checked = it }, - toggleContentDescription = "Split Checkbox Button Sample", - onContainerClick = { - /* Do something */ - }, - containerClickLabel = "click", - enabled = true, - ) -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt deleted file mode 100644 index 1f31892..0000000 --- a/wear/wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt +++ /dev/null @@ -1,162 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Send -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.wear.compose.material3.ConfirmationDialog -import androidx.wear.compose.material3.ConfirmationDialogDefaults -import androidx.wear.compose.material3.FailureConfirmationDialog -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.SuccessConfirmationDialog -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.confirmationDialogCurvedText -import androidx.wear.compose.material3.samples.icons.FavoriteIcon - -@Sampled -@Composable -fun ConfirmationDialogSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Show Confirmation") }, - ) - } - - // Has an icon and a short curved text content, which will be displayed along the bottom edge of - // the screen. - val curvedTextStyle = ConfirmationDialogDefaults.curvedTextStyle - ConfirmationDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - curvedText = { confirmationDialogCurvedText("Confirmed", curvedTextStyle) }, - ) { - FavoriteIcon(ConfirmationDialogDefaults.IconSize) - } -} - -@Sampled -@Composable -fun LongTextConfirmationDialogSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Show Confirmation") }, - ) - } - - // Has an icon and a text content. Text will be displayed in the center of the screen below the - // icon. - ConfirmationDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - text = { Text(text = "Your message has been sent") }, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Send, - contentDescription = null, - modifier = Modifier.size(ConfirmationDialogDefaults.SmallIconSize), - ) - } -} - -@Sampled -@Composable -fun FailureConfirmationDialogSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Show Confirmation") }, - ) - } - - val text = "Failure" - val style = ConfirmationDialogDefaults.curvedTextStyle - FailureConfirmationDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - curvedText = { confirmationDialogCurvedText(text, style) }, - ) -} - -@Sampled -@Composable -fun FailureConfirmationDialogWithGenericFailureIconSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Show Confirmation") }, - ) - } - - val text = "Failure" - val style = ConfirmationDialogDefaults.curvedTextStyle - FailureConfirmationDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - curvedText = { confirmationDialogCurvedText(text, style) }, - content = { ConfirmationDialogDefaults.GenericFailureIcon() }, - ) -} - -@Sampled -@Composable -fun SuccessConfirmationDialogSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Show Confirmation") }, - ) - } - - val text = "Success" - val style = ConfirmationDialogDefaults.curvedTextStyle - SuccessConfirmationDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - curvedText = { confirmationDialogCurvedText(text, style) }, - ) -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt b/wear/wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt deleted file mode 100644 index 4e1a9ae..0000000 --- a/wear/wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt +++ /dev/null @@ -1,78 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Warning -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.StrokeCap.Companion.Round -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.CurvedDirection -import androidx.wear.compose.foundation.CurvedLayout -import androidx.wear.compose.foundation.CurvedModifier -import androidx.wear.compose.foundation.angularSizeDp -import androidx.wear.compose.foundation.background -import androidx.wear.compose.foundation.curvedBox -import androidx.wear.compose.foundation.curvedComposable -import androidx.wear.compose.foundation.curvedRow -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.CurvedTextDefaults -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.curvedText - -@Sampled -@Composable -fun CurvedTextTop() { - val backgroundColor = MaterialTheme.colorScheme.onPrimary - val customColor = MaterialTheme.colorScheme.tertiaryDim - CurvedLayout { - curvedRow(CurvedModifier.background(backgroundColor, Round)) { - curvedText("Calling", color = customColor) - curvedBox(CurvedModifier.angularSizeDp(5.dp)) {} - curvedText("Camilia Garcia") - } - } -} - -@Sampled -@Composable -fun CurvedTextBottom() { - val backgroundColor = MaterialTheme.colorScheme.onPrimary - CurvedLayout(anchor = 90f, angularDirection = CurvedDirection.Angular.Reversed) { - curvedRow(CurvedModifier.background(backgroundColor, Round)) { - curvedComposable { - Icon( - Icons.Filled.Warning, - contentDescription = "Warning", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - } - curvedText( - "Network lost", - maxSweepAngle = CurvedTextDefaults.StaticContentMaxSweepAngle, - overflow = TextOverflow.Ellipsis, - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/DatePickerSample.kt.md.txt b/wear/wear-compose-m3/references/material3/DatePickerSample.kt.md.txt deleted file mode 100644 index 261e5d7..0000000 --- a/wear/wear-compose-m3/references/material3/DatePickerSample.kt.md.txt +++ /dev/null @@ -1,131 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -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.platform.LocalConfiguration -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.DatePicker -import androidx.wear.compose.material3.DatePickerType -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.Text -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle - -@Sampled -@Composable -fun DatePickerSample() { - var showDatePicker by remember { mutableStateOf(true) } - var datePickerDate by remember { mutableStateOf(LocalDate.now()) } - val formatter = - DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(LocalConfiguration.current.locales[0]) - if (showDatePicker) { - DatePicker( - initialDate = datePickerDate, // Initialize with last picked date on reopen - onDatePicked = { - datePickerDate = it - showDatePicker = false - }, - ) - } else { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = { showDatePicker = true }, - label = { Text("Selected Date") }, - secondaryLabel = { Text(datePickerDate.format(formatter)) }, - icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") }, - ) - } - } -} - -@Sampled -@Composable -fun DatePickerYearMonthDaySample() { - var showDatePicker by remember { mutableStateOf(true) } - var datePickerDate by remember { mutableStateOf(LocalDate.now()) } - - val formatter = - DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(LocalConfiguration.current.locales[0]) - - if (showDatePicker) { - DatePicker( - initialDate = datePickerDate, // Initialize with last picked date on reopen - onDatePicked = { - datePickerDate = it - showDatePicker = false - }, - datePickerType = DatePickerType.YearMonthDay, - ) - } else { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = { showDatePicker = true }, - label = { Text("Selected Date") }, - secondaryLabel = { Text(datePickerDate.format(formatter)) }, - icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") }, - ) - } - } -} - -@Sampled -@Composable -fun DatePickerFutureOnlySample() { - val currentDate = LocalDate.now() - var showDatePicker by remember { mutableStateOf(true) } - var datePickerDate by remember { mutableStateOf(LocalDate.now()) } - val formatter = - DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(LocalConfiguration.current.locales[0]) - if (showDatePicker) { - DatePicker( - initialDate = datePickerDate, // Initialize with last picked date on reopen - onDatePicked = { - datePickerDate = it - showDatePicker = false - }, - datePickerType = DatePickerType.YearMonthDay, - minValidDate = currentDate, - ) - } else { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = { showDatePicker = true }, - label = { Text("Selected Date") }, - secondaryLabel = { Text(datePickerDate.format(formatter)) }, - icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") }, - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt b/wear/wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt deleted file mode 100644 index 45d4651..0000000 --- a/wear/wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt +++ /dev/null @@ -1,74 +0,0 @@ -``` -/* - * Copyright 2026 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 - * - * 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. - */ -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.ColorScheme -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.dynamicColorScheme -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Sampled -@Composable -fun DynamicColorSchemeSample() { - val dynamicColorScheme = dynamicColorScheme(LocalContext.current) - val transformationSpec = rememberTransformationSpec() - - // Fallback to the default color scheme if dynamic colors are unavailable - MaterialTheme(colorScheme = dynamicColorScheme ?: ColorScheme()) { - val hasDynamicColors = dynamicColorScheme != null - TransformingLazyColumn( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp), - contentPadding = PaddingValues(20.dp), - ) { - if (!hasDynamicColors) { - item { Text("Dynamic color is not available.") } - } - items(5) { index -> - // The button's defaults will pick up the dynamic primary color from the - // MaterialTheme - Button( - label = { Text("Primary Button ${index + 1}") }, - modifier = - Modifier.fillMaxWidth() - .transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ), - onClick = {}, - transformation = SurfaceTransformation(transformationSpec), - ) - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt deleted file mode 100644 index 4a01bc8..0000000 --- a/wear/wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt +++ /dev/null @@ -1,63 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.EdgeButton -import androidx.wear.compose.material3.EdgeButtonSize -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.Text - -@Sampled -@Composable -fun EdgeButtonSample() { - Column( - Modifier.fillMaxSize().padding(horizontal = 20.dp).padding(top = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.Center) { - Text( - "EdgeButton hugs the bottom of the curved screen", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - ) - } - EdgeButton(onClick = { /* Do something */ }, buttonSize = EdgeButtonSize.Medium) { - Icon( - Icons.Filled.Check, - contentDescription = "Check icon", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt b/wear/wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt deleted file mode 100644 index 4605ac0..0000000 --- a/wear/wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt +++ /dev/null @@ -1,60 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -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 -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.FadingExpandingLabel - -@Sampled -@Composable -fun FadingExpandingLabelButtonSample() { - var text by remember { mutableStateOf("Line of Text One.") } - var lines by remember { mutableIntStateOf(1) } - - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = { - lines = lines % 3 + 1 - text = - (1..lines).joinToString(separator = "\n") { - when (it) { - 1 -> "Line of Text One." - 2 -> "Line of Text Two." - else -> "Line of Text Three." - } - } - }, - modifier = Modifier.fillMaxWidth(), - label = { FadingExpandingLabel(text = text, textAlign = TextAlign.Left) }, - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/IconButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/IconButtonSample.kt.md.txt deleted file mode 100644 index 7ecc104..0000000 --- a/wear/wear-compose-m3/references/material3/IconButtonSample.kt.md.txt +++ /dev/null @@ -1,125 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.Image -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.wear.compose.material3.FilledIconButton -import androidx.wear.compose.material3.FilledTonalIconButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.IconButton -import androidx.wear.compose.material3.IconButtonColors -import androidx.wear.compose.material3.IconButtonDefaults -import androidx.wear.compose.material3.IconButtonShapes -import androidx.wear.compose.material3.OutlinedIconButton - -@Composable -@Sampled -fun IconButtonSample() { - IconButton(onClick = { /* Do something */ }) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun FilledIconButtonSample() { - FilledIconButton(onClick = { /* Do something */ }) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun FilledVariantIconButtonSample() { - FilledIconButton( - onClick = { /* Do something */ }, - colors = IconButtonDefaults.filledVariantIconButtonColors(), - ) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun FilledTonalIconButtonSample() { - FilledTonalIconButton(onClick = { /* Do something */ }) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun OutlinedIconButtonSample() { - OutlinedIconButton(onClick = { /* Do something */ }) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Sampled -@Composable -fun IconButtonWithOnLongClickSample(onLongClick: () -> Unit) { - IconButton( - onClick = { /* Do something for onClick*/ }, - onLongClick = onLongClick, - onLongClickLabel = "Long click", - ) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun IconButtonWithCornerAnimationSample( - colors: IconButtonColors = IconButtonDefaults.filledIconButtonColors() -) { - FilledIconButton( - onClick = { /* Do something */ }, - shapes = IconButtonDefaults.animatedShapes(), - colors = colors, - ) { - Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon") - } -} - -@Composable -@Sampled -fun IconButtonWithImageSample( - painter: Painter = painterResource(R.drawable.card_content_image), - enabled: Boolean = true, - shapes: IconButtonShapes = IconButtonDefaults.shapes(), -) { - IconButton(onClick = { /* Do something */ }, shapes = shapes, enabled = enabled) { - Image( - painter = painter, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - if (enabled) Modifier else Modifier.alpha(IconButtonDefaults.DisabledImageOpacity), - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt deleted file mode 100644 index 23c16da..0000000 --- a/wear/wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt +++ /dev/null @@ -1,116 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.width -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.unit.dp -import androidx.wear.compose.material3.IconToggleButton -import androidx.wear.compose.material3.IconToggleButtonDefaults -import androidx.wear.compose.material3.samples.icons.WifiOffIcon -import androidx.wear.compose.material3.samples.icons.WifiOnIcon - -@Sampled -@Composable -fun IconToggleButtonSample() { - var firstChecked by remember { mutableStateOf(true) } - var secondChecked by remember { mutableStateOf(false) } - - Row( - modifier = Modifier.fillMaxSize(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - IconToggleButton( - checked = firstChecked, - onCheckedChange = { firstChecked = !firstChecked }, - shapes = IconToggleButtonDefaults.animatedShapes(), - ) { - if (firstChecked) { - WifiOnIcon() - } else { - WifiOffIcon() - } - } - - Spacer(modifier = Modifier.width(5.dp)) - - IconToggleButton( - checked = secondChecked, - onCheckedChange = { secondChecked = !secondChecked }, - shapes = IconToggleButtonDefaults.animatedShapes(), - ) { - if (secondChecked) { - WifiOnIcon() - } else { - WifiOffIcon() - } - } - } -} - -@Sampled -@Composable -fun IconToggleButtonVariantSample() { - var firstChecked by remember { mutableStateOf(true) } - var secondChecked by remember { mutableStateOf(false) } - - Row( - modifier = Modifier.fillMaxSize(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - IconToggleButton( - checked = firstChecked, - onCheckedChange = { firstChecked = !firstChecked }, - shapes = IconToggleButtonDefaults.variantAnimatedShapes(), - ) { - if (firstChecked) { - WifiOnIcon() - } else { - WifiOffIcon() - } - } - - Spacer(modifier = Modifier.width(5.dp)) - - IconToggleButton( - checked = secondChecked, - onCheckedChange = { secondChecked = !secondChecked }, - shapes = IconToggleButtonDefaults.variantAnimatedShapes(), - ) { - if (secondChecked) { - WifiOnIcon() - } else { - WifiOffIcon() - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt b/wear/wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt deleted file mode 100644 index 7f02be2..0000000 --- a/wear/wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt +++ /dev/null @@ -1,68 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.IconButton -import androidx.wear.compose.material3.LevelIndicator -import androidx.wear.compose.material3.StepperDefaults -import androidx.wear.compose.material3.samples.icons.VolumeDownIcon -import androidx.wear.compose.material3.samples.icons.VolumeUpIcon - -@Sampled -@Composable -fun LevelIndicatorSample() { - var value by remember { mutableFloatStateOf(0.5f) } - Box(modifier = Modifier.fillMaxSize()) { - Column( - verticalArrangement = Arrangement.SpaceEvenly, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize(), - ) { - IconButton( - modifier = Modifier.padding(horizontal = 16.dp), - enabled = value < 1f, - onClick = { value += 0.1f }, - ) { - VolumeUpIcon(StepperDefaults.IconSize) - } - IconButton( - modifier = Modifier.padding(horizontal = 16.dp), - enabled = value > 0f, - onClick = { value -= 0.1f }, - ) { - VolumeDownIcon(StepperDefaults.IconSize) - } - } - LevelIndicator(value = { value }, modifier = Modifier.align(Alignment.CenterStart)) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt deleted file mode 100644 index 8256973..0000000 --- a/wear/wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt +++ /dev/null @@ -1,177 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.ListHeader -import androidx.wear.compose.material3.ListHeaderDefaults -import androidx.wear.compose.material3.ListSubHeader -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Sampled -@Preview -@Composable -fun ListHeaderSample() { - val transformationSpec = rememberTransformationSpec() - val scrollState = rememberTransformingLazyColumnState() - - AppScaffold { - ScreenScaffold(scrollState = scrollState) { contentPadding -> - TransformingLazyColumn( - state = scrollState, - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - contentPadding = contentPadding, - ) { - item { - ListHeader( - modifier = - Modifier.minimumVerticalContentPadding( - ListHeaderDefaults.minimumTopListContentPadding, - ListHeaderDefaults.minimumBottomListContentPadding, - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Settings") - } - } - item { - ListSubHeader( - modifier = - Modifier.minimumVerticalContentPadding( - ListHeaderDefaults.minimumTopListContentPadding, - ListHeaderDefaults.minimumBottomListContentPadding, - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - icon = { - Icon( - painter = painterResource(R.drawable.ic_connectivity), - contentDescription = "Connectivity", - ) - }, - label = { Text("Connectivity") }, - ) - } - item { - Button( - modifier = - Modifier.fillMaxWidth() - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - icon = { - Icon( - painter = painterResource(R.drawable.ic_bluetooth), - contentDescription = "Bluetooth", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - ) { - Text("Bluetooth") - } - } - item { - Button( - modifier = - Modifier.fillMaxWidth() - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - icon = { - Icon( - painter = painterResource(R.drawable.ic_wifi), - contentDescription = "Wifi", - modifier = Modifier.size(ButtonDefaults.IconSize), - ) - }, - ) { - Text("Wifi") - } - } - item { - ListSubHeader( - modifier = - Modifier.minimumVerticalContentPadding( - ListHeaderDefaults.minimumTopListContentPadding, - ListHeaderDefaults.minimumBottomListContentPadding, - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Display") - } - } - item { - Button( - modifier = - Modifier.fillMaxWidth() - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text("Change Watchface") - } - } - item { - Button( - modifier = - Modifier.fillMaxWidth() - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - onClick = {}, - ) { - Text("Brightness") - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt b/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt deleted file mode 100644 index 340d53a..0000000 --- a/wear/wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt +++ /dev/null @@ -1,651 +0,0 @@ -``` -/* - * Copyright 2026 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.annotation.Sampled -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.rememberOverscrollEffect -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -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 -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.AmbientMode -import androidx.wear.compose.foundation.LocalAmbientModeManager -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.foundation.pager.HorizontalPager -import androidx.wear.compose.foundation.pager.VerticalPager -import androidx.wear.compose.foundation.pager.rememberPagerState -import androidx.wear.compose.foundation.rememberAmbientModeManager -import androidx.wear.compose.material3.AnimatedPage -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.EdgeButton -import androidx.wear.compose.material3.HorizontalPagerScaffold -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SwitchButton -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.VerticalPagerScaffold -import androidx.wear.compose.material3.onehandedgesture.LocalOneHandedGestureEnabled -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureAction -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicator -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureClickIndicatorState -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureDefaults -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureHorizontalPageIndicator -import androidx.wear.compose.material3.onehandedgesture.OneHandedGesturePageIndicatorState -import androidx.wear.compose.material3.onehandedgesture.OneHandedGesturePriority -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureScrollIndicator -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureScrollIndicatorState -import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureVerticalPageIndicator -import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture -import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun OneHandedGestureButtonSample() { - var label by remember { mutableStateOf("Gesturable Button") } - val onClick = { label = "Clicked/Gestured" } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Button( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.fillMaxWidth() - .oneHandedGesture( - gestureConfiguration = gestureConfig, - interactionSource = interactionSource, - onGestureLabel = "activate the button", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - onGesture = onClick, - ), - ) { - OneHandedGestureClickIndicator(gestureConfig, indicatorState) { - Text(label, modifier = Modifier.fillMaxWidth()) - } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureButtonInAmbientSample() { - var label by remember { mutableStateOf("Gesturable Button") } - val onClick = { label = "Clicked/Gestured" } - val interactionSource = remember { MutableInteractionSource() } - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - val coroutineScope = rememberCoroutineScope() - val activityAmbientModeManager = rememberAmbientModeManager() - var showGestureIndicator by remember { mutableStateOf(true) } - - CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - val isInAmbientMode = - LocalAmbientModeManager.current?.currentAmbientMode is AmbientMode.Ambient - Button( - onClick = onClick, - interactionSource = interactionSource, - modifier = - Modifier.fillMaxWidth() - .oneHandedGesture( - gestureConfiguration = gestureConfig, - interactionSource = interactionSource, - enabledInAmbient = true, - onGestureLabel = "activate the button", - onGestureAvailable = { - // Bypass repeating the gesture indicator on every recomposition. - if (showGestureIndicator) { - coroutineScope.launch { indicatorState.showIndicator() } - showGestureIndicator = false - } - }, - onGesture = onClick, - ), - colors = - if (isInAmbientMode) ButtonDefaults.outlinedButtonColors() - else ButtonDefaults.buttonColors(), - border = - if (isInAmbientMode) ButtonDefaults.outlinedButtonBorder(enabled = true) - else null, - ) { - OneHandedGestureClickIndicator(gestureConfig, indicatorState) { - Text(label, modifier = Modifier.fillMaxWidth()) - } - } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureDisableButtonSample() { - var counter by remember { mutableIntStateOf(0) } - var enabled by remember { mutableStateOf(true) } - val interactionSource = remember { MutableInteractionSource() } - val coroutineScope = rememberCoroutineScope() - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - SwitchButton(checked = enabled, onCheckedChange = { enabled = it }) { - Text("Gestures enabled") - } - Spacer(modifier = Modifier.height(6.dp)) - CompositionLocalProvider(LocalOneHandedGestureEnabled provides enabled) { - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGestureClickIndicatorState() } - Button( - onClick = {}, - interactionSource = interactionSource, - modifier = - Modifier.oneHandedGesture( - gestureConfiguration = gestureConfig, - interactionSource = interactionSource, - onGestureLabel = "increase the counter", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - onGesture = { counter++ }, - ), - ) { - OneHandedGestureClickIndicator(gestureConfig, indicatorState) { - Text("Gestured $counter times") - } - } - } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureTransformingLazyColumnSample() { - val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current - val onClick = - remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } } - val scrollState = rememberTransformingLazyColumnState() - val coroutineScope = rememberCoroutineScope() - - val buttonInteractionSource = remember { MutableInteractionSource() } - val buttonGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Clickable, - ) - val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() } - - val scrollGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Scrollable, - ) - val scrollIndicatorState = - remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() } - - ScreenScaffold( - scrollState = scrollState, - edgeButton = { - EdgeButton( - onClick = onClick, - interactionSource = buttonInteractionSource, - modifier = - if (scrollState.canScrollForward) { - Modifier - } else { - // Apply the one-handed gesture modifier only when the container cannot - // scroll further, ensuring the EdgeButton is fully visible and interactive - Modifier.oneHandedGesture( - gestureConfiguration = buttonGestureConfig, - interactionSource = buttonInteractionSource, - onGestureLabel = "close", - onGestureAvailable = { - coroutineScope.launch { buttonIndicatorState.showIndicator() } - }, - onGesture = onClick, - ) - } then - Modifier.scrollable( - state = scrollState, - orientation = Orientation.Vertical, - reverseDirection = true, - overscrollEffect = rememberOverscrollEffect(), - ), - ) { - OneHandedGestureClickIndicator(buttonGestureConfig, buttonIndicatorState) { - Text("Close") - } - } - }, - scrollIndicator = { - OneHandedGestureScrollIndicator( - gestureConfiguration = scrollGestureConfig, - indicatorState = scrollIndicatorState, - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - ) - }, - ) { contentPadding -> - TransformingLazyColumn( - state = scrollState, - contentPadding = contentPadding, - modifier = - Modifier.fillMaxSize() - .oneHandedGesture( - gestureConfiguration = scrollGestureConfig, - onGestureLabel = "scroll", - onGestureAvailable = { - coroutineScope.launch { scrollIndicatorState.showIndicator() } - }, - onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }, - ), - ) { - items(10) { Text("Item $it") } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureScalingLazyColumnSample() { - val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current - val onClick = - remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } } - val scrollState = rememberScalingLazyListState() - val coroutineScope = rememberCoroutineScope() - - val buttonInteractionSource = remember { MutableInteractionSource() } - val buttonGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Clickable, - ) - val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() } - - val scrollGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Scrollable, - ) - val scrollIndicatorState = - remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() } - - ScreenScaffold( - scrollState = scrollState, - edgeButton = { - EdgeButton( - onClick = onClick, - interactionSource = buttonInteractionSource, - modifier = - if (scrollState.canScrollForward) { - Modifier - } else { - // Apply the one-handed gesture modifier only when the container cannot - // scroll further, ensuring the EdgeButton is fully visible and interactive - Modifier.oneHandedGesture( - gestureConfiguration = buttonGestureConfig, - interactionSource = buttonInteractionSource, - onGestureLabel = "close", - onGestureAvailable = { - coroutineScope.launch { buttonIndicatorState.showIndicator() } - }, - onGesture = onClick, - ) - } then - Modifier.scrollable( - state = scrollState, - orientation = Orientation.Vertical, - reverseDirection = true, - overscrollEffect = rememberOverscrollEffect(), - ), - ) { - OneHandedGestureClickIndicator(buttonGestureConfig, buttonIndicatorState) { - Text("Close") - } - } - }, - scrollIndicator = { - OneHandedGestureScrollIndicator( - gestureConfiguration = scrollGestureConfig, - indicatorState = scrollIndicatorState, - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - ) - }, - ) { contentPadding -> - ScalingLazyColumn( - state = scrollState, - contentPadding = contentPadding, - modifier = - Modifier.fillMaxSize() - .oneHandedGesture( - gestureConfiguration = scrollGestureConfig, - onGestureLabel = "scroll", - onGestureAvailable = { - coroutineScope.launch { scrollIndicatorState.showIndicator() } - }, - onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }, - ), - autoCentering = null, - ) { - items(10) { Text("Item $it") } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureTransformingLazyColumnScrollToNextItemSample() { - val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current - val onClick = - remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } } - val scrollState = rememberTransformingLazyColumnState() - val coroutineScope = rememberCoroutineScope() - - val buttonInteractionSource = remember { MutableInteractionSource() } - val buttonGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Clickable, - ) - val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() } - - val scrollGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Scrollable, - ) - val scrollIndicatorState = - remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() } - - ScreenScaffold( - scrollState = scrollState, - edgeButton = { - EdgeButton( - onClick = onClick, - interactionSource = buttonInteractionSource, - modifier = - if (scrollState.canScrollForward) { - Modifier - } else { - // Apply the one-handed gesture modifier only when the container cannot - // scroll further, ensuring the EdgeButton is fully visible and interactive - Modifier.oneHandedGesture( - gestureConfiguration = buttonGestureConfig, - interactionSource = buttonInteractionSource, - onGestureLabel = "close", - onGestureAvailable = { - coroutineScope.launch { buttonIndicatorState.showIndicator() } - }, - onGesture = onClick, - ) - } then - Modifier.scrollable( - state = scrollState, - orientation = Orientation.Vertical, - reverseDirection = true, - overscrollEffect = rememberOverscrollEffect(), - ), - ) { - OneHandedGestureClickIndicator(buttonGestureConfig, buttonIndicatorState) { - Text("Close") - } - } - }, - scrollIndicator = { - OneHandedGestureScrollIndicator( - gestureConfiguration = scrollGestureConfig, - indicatorState = scrollIndicatorState, - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - ) - }, - ) { contentPadding -> - TransformingLazyColumn( - state = scrollState, - contentPadding = contentPadding, - modifier = - Modifier.fillMaxSize() - .oneHandedGesture( - gestureConfiguration = scrollGestureConfig, - onGestureLabel = "scroll", - onGestureAvailable = { - coroutineScope.launch { scrollIndicatorState.showIndicator() } - }, - onGesture = { OneHandedGestureDefaults.scrollDownToNextItem(scrollState) }, - ), - ) { - items(10) { Text("Item $it") } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureScalingLazyColumnScrollToNextItemSample() { - val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current - val onClick = - remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } } - val scrollState = rememberScalingLazyListState() - val coroutineScope = rememberCoroutineScope() - - val buttonInteractionSource = remember { MutableInteractionSource() } - val buttonGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Clickable, - ) - val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() } - - val scrollGestureConfig = - rememberOneHandedGestureConfiguration( - action = OneHandedGestureAction.Primary, - priority = OneHandedGesturePriority.Scrollable, - ) - val scrollIndicatorState = - remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() } - - ScreenScaffold( - scrollState = scrollState, - edgeButton = { - EdgeButton( - onClick = onClick, - interactionSource = buttonInteractionSource, - modifier = - if (scrollState.canScrollForward) { - Modifier - } else { - // Apply the one-handed gesture modifier only when the container cannot - // scroll further, ensuring the EdgeButton is fully visible and interactive - Modifier.oneHandedGesture( - gestureConfiguration = buttonGestureConfig, - interactionSource = buttonInteractionSource, - onGestureLabel = "close", - onGestureAvailable = { - coroutineScope.launch { buttonIndicatorState.showIndicator() } - }, - onGesture = onClick, - ) - } then - Modifier.scrollable( - state = scrollState, - orientation = Orientation.Vertical, - reverseDirection = true, - overscrollEffect = rememberOverscrollEffect(), - ), - ) { - OneHandedGestureClickIndicator(buttonGestureConfig, buttonIndicatorState) { - Text("Close") - } - } - }, - scrollIndicator = { - OneHandedGestureScrollIndicator( - gestureConfiguration = scrollGestureConfig, - indicatorState = scrollIndicatorState, - scrollState = scrollState, - modifier = Modifier.align(Alignment.CenterEnd), - ) - }, - ) { contentPadding -> - ScalingLazyColumn( - state = scrollState, - contentPadding = contentPadding, - modifier = - Modifier.fillMaxSize() - .oneHandedGesture( - gestureConfiguration = scrollGestureConfig, - onGestureLabel = "scroll", - onGestureAvailable = { - coroutineScope.launch { scrollIndicatorState.showIndicator() } - }, - onGesture = { OneHandedGestureDefaults.scrollDownToNextItem(scrollState) }, - ), - autoCentering = null, - ) { - items(10) { Text("Item $it") } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureHorizontalPagerSample() { - val pagerState = rememberPagerState(pageCount = { 10 }) - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGesturePageIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - HorizontalPagerScaffold( - pagerState = pagerState, - pageIndicator = { - OneHandedGestureHorizontalPageIndicator( - gestureConfiguration = gestureConfig, - indicatorState = indicatorState, - pagerState = pagerState, - ) - }, - ) { - HorizontalPager( - state = pagerState, - modifier = - Modifier.oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "scroll to the next page", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - ) { - OneHandedGestureDefaults.scrollToNextPage(pagerState) - }, - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe left and right") - } - } - } - } - } -} - -@Sampled -@Composable -fun OneHandedGestureVerticalPagerSample() { - val pagerState = rememberPagerState(pageCount = { 10 }) - val gestureConfig = - rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary) - val indicatorState = remember { OneHandedGesturePageIndicatorState() } - val coroutineScope = rememberCoroutineScope() - - VerticalPagerScaffold( - pagerState = pagerState, - pageIndicator = { - OneHandedGestureVerticalPageIndicator( - gestureConfiguration = gestureConfig, - indicatorState = indicatorState, - pagerState = pagerState, - ) - }, - ) { - VerticalPager( - state = pagerState, - modifier = - Modifier.oneHandedGesture( - gestureConfiguration = gestureConfig, - onGestureLabel = "scroll to the next page", - onGestureAvailable = { - coroutineScope.launch { indicatorState.showIndicator() } - }, - ) { - OneHandedGestureDefaults.scrollToNextPage(pagerState) - }, - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe up and down") - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt b/wear/wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt deleted file mode 100644 index dc164fc..0000000 --- a/wear/wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt +++ /dev/null @@ -1,57 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -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.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.OpenOnPhoneDialog -import androidx.wear.compose.material3.OpenOnPhoneDialogDefaults -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.openOnPhoneDialogCurvedText - -@Sampled -@Composable -fun OpenOnPhoneDialogSample() { - var showConfirmation by remember { mutableStateOf(false) } - - Box(Modifier.fillMaxSize()) { - FilledTonalButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showConfirmation = true }, - label = { Text("Open on phone") }, - ) - } - - val text = OpenOnPhoneDialogDefaults.text - val style = OpenOnPhoneDialogDefaults.curvedTextStyle - OpenOnPhoneDialog( - visible = showConfirmation, - onDismissRequest = { showConfirmation = false }, - curvedText = { openOnPhoneDialogCurvedText(text = text, style = style) }, - ) -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt b/wear/wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt deleted file mode 100644 index 6b5828e..0000000 --- a/wear/wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt +++ /dev/null @@ -1,110 +0,0 @@ -``` -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.pager.HorizontalPager -import androidx.wear.compose.foundation.pager.VerticalPager -import androidx.wear.compose.foundation.pager.rememberPagerState -import androidx.wear.compose.material3.AnimatedPage -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.HorizontalPageIndicator -import androidx.wear.compose.material3.HorizontalPagerScaffold -import androidx.wear.compose.material3.PagerScaffoldDefaults -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.VerticalPageIndicator -import androidx.wear.compose.material3.VerticalPagerScaffold - -@Sampled -@Composable -fun HorizontalPageIndicatorWithPagerSample(navigateBack: () -> Unit) { - val pageCount = 9 - val pagerState = rememberPagerState { pageCount } - - Box { - HorizontalPagerScaffold( - pagerState = pagerState, - pageIndicator = { HorizontalPageIndicator(pagerState = pagerState) }, - ) { - HorizontalPager( - state = pagerState, - flingBehavior = - PagerScaffoldDefaults.snapWithSpringFlingBehavior(state = pagerState), - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe left and right") - if (page == 0) { - Spacer(modifier = Modifier.height(8.dp)) - Button(onClick = navigateBack) { Text("Exit") } - } - } - } - } - } - } -} - -@Sampled -@Composable -fun VerticalPageIndicatorWithPagerSample() { - val pageCount = 9 - val pagerState = rememberPagerState { pageCount } - - Box { - VerticalPagerScaffold( - pagerState = pagerState, - pageIndicator = { VerticalPageIndicator(pagerState = pagerState) }, - ) { - VerticalPager( - state = pagerState, - flingBehavior = - PagerScaffoldDefaults.snapWithSpringFlingBehavior(state = pagerState), - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe up and down") - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt b/wear/wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt deleted file mode 100644 index 1d2aced..0000000 --- a/wear/wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt +++ /dev/null @@ -1,204 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.pager.HorizontalPager -import androidx.wear.compose.foundation.pager.PagerDefaults -import androidx.wear.compose.foundation.pager.VerticalPager -import androidx.wear.compose.foundation.pager.rememberPagerState -import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults -import androidx.wear.compose.material3.AnimatedPage -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.HorizontalPagerScaffold -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.PagerScaffoldDefaults -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.VerticalPagerScaffold - -@Sampled -@Composable -fun HorizontalPagerScaffoldSample(navigateBack: () -> Unit) { - AppScaffold { - val pagerState = rememberPagerState(pageCount = { 10 }) - - HorizontalPagerScaffold(pagerState = pagerState) { - HorizontalPager( - state = pagerState, - flingBehavior = - PagerDefaults.snapFlingBehavior( - state = pagerState, - maxFlingPages = 1, - snapPositionalThreshold = PagerScaffoldDefaults.HighSnapPositionalThreshold, - snapAnimationSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), - ), - rotaryScrollableBehavior = null, - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe left and right") - if (page == 0) { - Spacer(modifier = Modifier.height(16.dp)) - Button(onClick = navigateBack) { Text("Exit") } - } - } - } - } - } - } - } -} - -@Sampled -@Composable -fun HorizontalPagerScaffoldWithLowSensitivitySample(navigateBack: () -> Unit) { - AppScaffold { - val pagerState = rememberPagerState(pageCount = { 3 }) - - HorizontalPagerScaffold(pagerState = pagerState) { - HorizontalPager( - state = pagerState, - flingBehavior = - PagerDefaults.snapFlingBehavior( - state = pagerState, - maxFlingPages = 0, - snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, - snapAnimationSpec = PagerDefaults.SnapAnimationSpec, - ), - rotaryScrollableBehavior = - RotaryScrollableDefaults.snapBehavior( - pagerState = pagerState, - snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity, - ), - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe left and right") - if (page == 0) { - Spacer(modifier = Modifier.height(16.dp)) - Button(onClick = navigateBack) { Text("Exit") } - } - } - } - } - } - } - } -} - -@Sampled -@Composable -fun VerticalPagerScaffoldSample() { - AppScaffold { - val pagerState = rememberPagerState(pageCount = { 10 }) - - VerticalPagerScaffold(pagerState = pagerState) { - VerticalPager( - state = pagerState, - flingBehavior = - PagerDefaults.snapFlingBehavior( - state = pagerState, - maxFlingPages = 1, - snapPositionalThreshold = PagerScaffoldDefaults.HighSnapPositionalThreshold, - snapAnimationSpec = MaterialTheme.motionScheme.defaultSpatialSpec(), - ), - rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(pagerState), - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe up and down") - } - } - } - } - } - } -} - -@Sampled -@Composable -fun VerticalPagerScaffoldWithLowSensitivitySample() { - AppScaffold { - val pagerState = rememberPagerState(pageCount = { 3 }) - - VerticalPagerScaffold(pagerState = pagerState) { - VerticalPager( - state = pagerState, - flingBehavior = - PagerDefaults.snapFlingBehavior( - state = pagerState, - maxFlingPages = 0, - snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, - snapAnimationSpec = PagerDefaults.SnapAnimationSpec, - ), - rotaryScrollableBehavior = - RotaryScrollableDefaults.snapBehavior( - pagerState = pagerState, - snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity, - ), - ) { page -> - AnimatedPage(pageIndex = page, pagerState = pagerState) { - ScreenScaffold { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = "Page #$page") - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Swipe up and down") - } - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/PickerSample.kt.md.txt b/wear/wear-compose-m3/references/material3/PickerSample.kt.md.txt deleted file mode 100644 index 530b02f..0000000 --- a/wear/wear-compose-m3/references/material3/PickerSample.kt.md.txt +++ /dev/null @@ -1,111 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.Picker -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.rememberPickerState -import kotlinx.coroutines.launch - -@Sampled -@Composable -fun SimplePicker() { - val items = listOf("One", "Two", "Three", "Four", "Five") - val state = rememberPickerState(items.size) - // We forward scroll gestures from the whole screen to the Picker which makes this sample - // accessible for 2-finger vertical scrolling. - Box( - modifier = - Modifier.fillMaxSize() - .scrollable( - state = state, - orientation = Orientation.Vertical, - reverseDirection = true, - ), - contentAlignment = Alignment.Center, - ) { - val selectedLabel by remember { - derivedStateOf { "Selected: ${items[state.selectedOptionIndex]}" } - } - Text( - modifier = Modifier.align(Alignment.TopCenter).padding(top = 10.dp), - text = selectedLabel, - ) - Picker( - modifier = Modifier.size(100.dp, 100.dp), - state = state, - contentDescription = { "${state.selectedOptionIndex + 1}" }, - ) { - Text(items[it]) - } - } -} - -@Sampled -@Composable -fun PickerScrollToOption() { - val coroutineScope = rememberCoroutineScope() - val state = rememberPickerState(initialNumberOfOptions = 10) - Picker( - state = state, - verticalSpacing = 4.dp, - contentDescription = { "${state.selectedOptionIndex + 1}" }, - ) { - Button( - onClick = { coroutineScope.launch { state.scrollToOption(it) } }, - label = { Text("$it") }, - ) - } -} - -@Sampled -@Composable -fun PickerAnimateScrollToOption() { - val coroutineScope = rememberCoroutineScope() - val state = rememberPickerState(initialNumberOfOptions = 10) - - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Picker( - state = state, - verticalSpacing = 4.dp, - contentDescription = { "${state.selectedOptionIndex + 1}" }, - ) { - Button( - onClick = { coroutineScope.launch { state.animateScrollToOption(it) } }, - label = { Text("$it") }, - ) - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt b/wear/wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt deleted file mode 100644 index 943bca1..0000000 --- a/wear/wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt +++ /dev/null @@ -1,182 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -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.graphics.vector.ImageVector -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.placeholder -import androidx.wear.compose.material3.placeholderShimmer -import androidx.wear.compose.material3.rememberPlaceholderState -import kotlinx.coroutines.delay - -/** - * This sample applies placeholders directly over the content that is waiting to be loaded. This - * approach is suitable for situations where the developer has an approximate knowledge of how big - * the content is going to be and it doesn't have cached data that can be shown. - */ -@Sampled -@Composable -fun ButtonWithIconAndLabelAndPlaceholders() { - var labelText by remember { mutableStateOf("") } - var imageVector: ImageVector? by remember { mutableStateOf(null) } - val buttonPlaceholderState = - rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null) - - FilledTonalButton( - onClick = { /* Do something */ }, - enabled = true, - modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState), - label = { - Text( - text = labelText, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth().placeholder(buttonPlaceholderState), - ) - }, - icon = { - Box( - modifier = - Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState) - ) { - if (imageVector != null) { - Icon( - imageVector = imageVector!!, - contentDescription = "Heart", - modifier = - Modifier.wrapContentSize(align = Alignment.Center) - .size(ButtonDefaults.IconSize) - .fillMaxSize(), - ) - } - } - }, - ) - // Simulate content loading completing in stages - LaunchedEffect(Unit) { - delay(2000) - imageVector = Icons.Filled.Favorite - delay(1000) - labelText = "A label" - } -} - -/** - * This sample doesn't use placeholders for the label as there is some cached data that can be shown - * while loading. - */ -@Sampled -@Composable -fun ButtonWithIconAndLabelCachedData() { - var labelText by remember { mutableStateOf("Cached Data") } - var imageVector: ImageVector? by remember { mutableStateOf(null) } - val buttonPlaceholderState = - rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null) - - // Put placeholderShimmer in the container and placeholder in the elements of the content that - // have no cached data. - FilledTonalButton( - onClick = { /* Do something */ }, - enabled = true, - modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState), - label = { - Text( - text = labelText, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(), - ) - }, - icon = { - Box( - modifier = - Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState) - ) { - if (imageVector != null) { - Icon( - imageVector = imageVector!!, - contentDescription = "Heart", - modifier = - Modifier.wrapContentSize(align = Alignment.Center) - .size(ButtonDefaults.IconSize) - .fillMaxSize(), - ) - } - } - }, - ) - // Simulate content loading completing in stages - LaunchedEffect(Unit) { - delay(2000) - imageVector = Icons.Filled.Favorite - delay(1000) - labelText = "A label" - } -} - -/** - * This sample applies a placeholder and placeholderShimmer directly over a single composable. - * - * Note that the modifier ordering is important, the placeholderShimmer must be before the - * placeholder in the modifier chain - otherwise the shimmer will be drawn underneath the - * placeholder and will not be visible. - */ -@Sampled -@Composable -fun TextPlaceholder() { - var labelText by remember { mutableStateOf("") } - val placeholderState = rememberPlaceholderState(isVisible = labelText.isEmpty()) - - Text( - text = labelText, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - modifier = - Modifier.width(90.dp).placeholderShimmer(placeholderState).placeholder(placeholderState), - ) - - // Simulate content loading - LaunchedEffect(Unit) { - delay(3000) - labelText = "A label" - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt deleted file mode 100644 index 97332f1..0000000 --- a/wear/wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt +++ /dev/null @@ -1,263 +0,0 @@ -``` -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.PlayArrow -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.drawBehind -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.ArcProgressIndicator -import androidx.wear.compose.material3.ArcProgressIndicatorDefaults -import androidx.wear.compose.material3.CircularProgressIndicator -import androidx.wear.compose.material3.CircularProgressIndicatorDefaults -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.IconButton -import androidx.wear.compose.material3.IconButtonDefaults -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.ProgressIndicatorDefaults -import androidx.wear.compose.material3.SegmentedCircularProgressIndicator -import androidx.wear.compose.material3.drawCircularProgressIndicator - -@Sampled -@Composable -fun FullScreenProgressIndicatorSample() { - Box( - modifier = - Modifier.background(MaterialTheme.colorScheme.background) - .padding(CircularProgressIndicatorDefaults.FullScreenPadding) - .fillMaxSize() - ) { - CircularProgressIndicator(progress = { 0.25f }, startAngle = 120f, endAngle = 60f) - } -} - -@Sampled -@Composable -fun MediaButtonProgressIndicatorSample() { - var isPlaying by remember { mutableStateOf(false) } - val buttonPadding = 4.dp - val progressStrokeWidth = 4.dp - val progress = 0.75f - - Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) { - // The CircularProgressIndicator should be around the IconButton, with an extra gap between - // then of 'buttonPadding'. We multiply by 2 because the size includes progressStrokeWidth - // at top and bottom and the buttonPadding at top and bottom. - CircularProgressIndicator( - modifier = - Modifier.align(Alignment.Center) - .size( - IconButtonDefaults.DefaultButtonSize + - progressStrokeWidth * 2 + - buttonPadding * 2 - ), - progress = { progress }, - strokeWidth = progressStrokeWidth, - ) - - IconButton( - modifier = - Modifier.align(Alignment.Center) - .semantics { - // Set custom progress semantics for accessibility. - contentDescription = - String.format( - "Play/pause button, track progress: %.0f%%", - progress * 100, - ) - } - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceContainerLow), - onClick = { isPlaying = !isPlaying }, - ) { - Icon( - imageVector = if (isPlaying) Icons.Filled.Close else Icons.Filled.PlayArrow, - contentDescription = null, - ) - } - } -} - -@Sampled -@Composable -fun OverflowProgressIndicatorSample() { - Box( - modifier = - Modifier.background(MaterialTheme.colorScheme.background) - .padding(CircularProgressIndicatorDefaults.FullScreenPadding) - .fillMaxSize() - ) { - CircularProgressIndicator( - // Overflow value of 120% - progress = { 1.2f }, - allowProgressOverflow = true, - startAngle = 120f, - endAngle = 60f, - ) - } -} - -@Sampled -@Composable -fun SmallValuesProgressIndicatorSample() { - Box { - CircularProgressIndicator( - // Small progress values like 2% will be rounded up to at least the stroke width. - progress = { 0.02f }, - modifier = - Modifier.fillMaxSize().padding(CircularProgressIndicatorDefaults.FullScreenPadding), - startAngle = 120f, - endAngle = 60f, - strokeWidth = 10.dp, - colors = - ProgressIndicatorDefaults.colors( - indicatorColor = Color.Green, - trackColor = Color.White, - ), - ) - } -} - -@Sampled -@Composable -fun CircularProgressIndicatorCustomAnimationSample() { - val animatedProgress = remember { Animatable(0f) } - val colors = - ProgressIndicatorDefaults.colors(indicatorColor = Color.Green, trackColor = Color.White) - - LaunchedEffect(Unit) { - animatedProgress.animateTo(1f, tween(durationMillis = 1024, easing = LinearEasing)) - animatedProgress.animateTo(0f, tween(durationMillis = 1024, easing = LinearEasing)) - } - - Box( - modifier = - Modifier.background(MaterialTheme.colorScheme.background) - .padding(CircularProgressIndicatorDefaults.FullScreenPadding) - .fillMaxSize() - ) { - // Draw the circular progress indicator with custom animation - Spacer( - Modifier.fillMaxSize().focusable().drawBehind { - drawCircularProgressIndicator( - progress = animatedProgress.value, - strokeWidth = 10.dp, - colors = colors, - startAngle = 120f, - endAngle = 60f, - ) - } - ) - } -} - -@Sampled -@Composable -fun IndeterminateProgressIndicatorSample() { - Box(modifier = Modifier.fillMaxSize()) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) - } -} - -@Sampled -@Composable -fun IndeterminateProgressArcSample() { - Box(modifier = Modifier.fillMaxSize()) { - ArcProgressIndicator( - modifier = - Modifier.align(Alignment.Center) - .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter) - ) - } -} - -@Sampled -@Composable -fun SegmentedProgressIndicatorSample() { - Box( - modifier = - Modifier.background(MaterialTheme.colorScheme.background) - .padding(CircularProgressIndicatorDefaults.FullScreenPadding) - .fillMaxSize() - ) { - SegmentedCircularProgressIndicator(segmentCount = 5, progress = { 0.5f }) - } -} - -@Sampled -@Composable -fun SegmentedProgressIndicatorBinarySample() { - Box( - modifier = - Modifier.background(MaterialTheme.colorScheme.background) - .padding(CircularProgressIndicatorDefaults.FullScreenPadding) - .fillMaxSize() - ) { - SegmentedCircularProgressIndicator(segmentCount = 5, segmentValue = { it % 2 != 0 }) - } -} - -@Sampled -@Composable -fun SmallSegmentedProgressIndicatorSample() { - Box(modifier = Modifier.fillMaxSize()) { - SegmentedCircularProgressIndicator( - segmentCount = 6, - progress = { 0.75f }, - modifier = Modifier.align(Alignment.Center).size(80.dp), - ) - } -} - -@Sampled -@Composable -fun SmallSegmentedProgressIndicatorBinarySample() { - Box(modifier = Modifier.fillMaxSize()) { - SegmentedCircularProgressIndicator( - segmentCount = 8, - segmentValue = { it % 2 != 0 }, - modifier = Modifier.align(Alignment.Center).size(80.dp), - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt deleted file mode 100644 index 3c1b941..0000000 --- a/wear/wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt +++ /dev/null @@ -1,109 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -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.selection.selectableGroup -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.RadioButton -import androidx.wear.compose.material3.SplitRadioButton -import androidx.wear.compose.material3.Text - -@Sampled -@Preview -@Composable -fun RadioButtonSample() { - Column(modifier = Modifier.selectableGroup().fillMaxSize()) { - var selectedButton by remember { mutableStateOf(0) } - // RadioButton uses the Radio selection control by default. - RadioButton( - label = { Text("Radio button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - secondaryLabel = { - Text("With secondary label", maxLines = 2, overflow = TextOverflow.Ellipsis) - }, - selected = selectedButton == 0, - onSelect = { selectedButton = 0 }, - icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite icon") }, - enabled = true, - ) - Spacer(modifier = Modifier.height(4.dp)) - RadioButton( - label = { Text("Radio button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - secondaryLabel = { - Text("With secondary label", maxLines = 3, overflow = TextOverflow.Ellipsis) - }, - selected = selectedButton == 1, - onSelect = { selectedButton = 1 }, - icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite icon") }, - enabled = true, - ) - } -} - -@Sampled -@Preview -@Composable -fun SplitRadioButtonSample() { - Column(modifier = Modifier.selectableGroup().padding(horizontal = 10.dp)) { - var selectedButton by remember { mutableStateOf(0) } - // SplitRadioButton uses the Radio selection control by default. - SplitRadioButton( - label = { Text("First Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - selected = selectedButton == 0, - onSelectionClick = { selectedButton = 0 }, - selectionContentDescription = "First", - onContainerClick = { - /* Do something */ - }, - containerClickLabel = "click", - modifier = Modifier.fillMaxWidth(), - enabled = true, - ) - Spacer(modifier = Modifier.height(4.dp)) - SplitRadioButton( - label = { Text("Second Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - selected = selectedButton == 1, - onSelectionClick = { selectedButton = 1 }, - selectionContentDescription = "Second", - onContainerClick = { - /* Do something */ - }, - containerClickLabel = "click", - modifier = Modifier.fillMaxWidth(), - enabled = true, - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt deleted file mode 100644 index 9620d69..0000000 --- a/wear/wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt +++ /dev/null @@ -1,146 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.rememberOverscrollEffect -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.EdgeButton -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Preview -@Sampled -@Composable -fun ScaffoldSample() { - // Declare just one [AppScaffold] per app such as in the activity. - // [AppScaffold] allows static screen elements (i.e. [TimeText]) to remain visible - // during in-app transitions such as swipe-to-dismiss. - AppScaffold { - val transformationSpec = rememberTransformationSpec() - - // Define the navigation hierarchy within the AppScaffold, - // such as using SwipeDismissableNavHost. - // For this sample, we will define a single screen inline. - val listState = rememberTransformingLazyColumnState() - - // By default, ScreenScaffold will handle transitions showing/hiding ScrollIndicator, - // showing/hiding/scrolling away TimeText and optionally hosting the EdgeButton. - ScreenScaffold(scrollState = listState) { contentPadding -> - TransformingLazyColumn( - state = listState, - contentPadding = contentPadding, - modifier = Modifier.fillMaxSize(), - ) { - items(10) { - Button( - onClick = {}, - label = { Text("Item ${it + 1}") }, - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .fillMaxWidth(), - ) - } - } - } - } -} - -@Preview -@Sampled -@Composable -fun ScaffoldWithTLCEdgeButtonSample() { - // Declare just one [AppScaffold] per app such as in the activity. - // [AppScaffold] allows static screen elements (i.e. [TimeText]) to remain visible - // during in-app transitions such as swipe-to-dismiss. - AppScaffold(modifier = Modifier.background(Color.Black)) { - val transformationSpec = rememberTransformationSpec() - - // Define the navigation hierarchy within the AppScaffold, - // such as using SwipeDismissableNavHost. - // For this sample, we will define a single screen inline. - val listState = rememberTransformingLazyColumnState() - // By default, ScreenScaffold will handle transitions showing/hiding ScrollIndicator, - // showing/hiding/scrolling away TimeText and optionally hosting the EdgeButton. - ScreenScaffold( - scrollState = listState, - // Define custom spacing between [EdgeButton] and [TransformingLazyColumn]. - edgeButtonSpacing = 15.dp, - edgeButton = { - EdgeButton( - onClick = {}, - modifier = - // In case user starts scrolling from the EdgeButton. - Modifier.scrollable( - listState, - orientation = Orientation.Vertical, - reverseDirection = true, - // An overscroll effect should be applied to the EdgeButton for proper - // scrolling behavior. - overscrollEffect = rememberOverscrollEffect(), - ), - ) { - Text("Clear All") - } - }, - ) { contentPadding -> - TransformingLazyColumn( - state = listState, - modifier = Modifier.fillMaxSize(), - // Bottom spacing is derived from [ScreenScaffold.edgeButtonSpacing]. - contentPadding = contentPadding, - ) { - items(4) { - Button( - onClick = {}, - label = { Text("Item ${it + 1}") }, - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .fillMaxWidth(), - ) - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt b/wear/wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt deleted file mode 100644 index 7205759..0000000 --- a/wear/wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt +++ /dev/null @@ -1,97 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.ScrollInfoProvider -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.ListHeader -import androidx.wear.compose.material3.ListHeaderDefaults -import androidx.wear.compose.material3.ScreenStage -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TimeText -import androidx.wear.compose.material3.curvedText -import androidx.wear.compose.material3.scrollAway -import androidx.wear.compose.material3.timeTextSeparator - -@Sampled -@Composable -fun ScrollAwaySample() { - val state = rememberTransformingLazyColumnState() - - Box(modifier = Modifier.fillMaxSize()) { - TransformingLazyColumn(state = state, modifier = Modifier.fillMaxSize()) { - item { - ListHeader( - modifier = - Modifier.minimumVerticalContentPadding( - ListHeaderDefaults.minimumTopListContentPadding, - ListHeaderDefaults.minimumBottomListContentPadding, - ) - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = "TLC", - textAlign = TextAlign.Center, - ) - } - } - items(50) { - FilledTonalButton( - modifier = - Modifier.fillMaxWidth() - .padding(horizontal = 36.dp) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ), - onClick = {}, - label = { Text("Item ${it + 1}") }, - ) - } - } - TimeText( - // In practice, it is recommended to use the [AppScaffold] and [ScreenScaffold], - // so that the Material3 scroll away behavior is provided by default, rather than using - // [Modifier.scrollAway] directly. - modifier = - Modifier.scrollAway( - scrollInfoProvider = ScrollInfoProvider(state), - screenStage = { - if (state.isScrollInProgress) ScreenStage.Scrolling else ScreenStage.Idle - }, - ), - content = { time -> - curvedText("List") - timeTextSeparator() - curvedText(time) - }, - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt b/wear/wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt deleted file mode 100644 index 95f8ab8..0000000 --- a/wear/wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt +++ /dev/null @@ -1,66 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.ScreenScaffoldDefaults -import androidx.wear.compose.material3.ScrollIndicator -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TimeText - -@Sampled -@Preview -@Composable -fun ScrollIndicatorWithTLCSample() { - val scrollState = rememberTransformingLazyColumnState() - Box(modifier = Modifier.fillMaxSize()) { - TransformingLazyColumn( - modifier = Modifier.background(Color.Black), - state = scrollState, - contentPadding = ScreenScaffoldDefaults.contentPadding, - ) { - items(15) { - Button( - onClick = {}, - label = { Text("Button $it") }, - modifier = - Modifier.minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .fillMaxWidth(), - ) - } - } - ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = scrollState) - TimeText() - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/SliderSample.kt.md.txt b/wear/wear-compose-m3/references/material3/SliderSample.kt.md.txt deleted file mode 100644 index 25bb251..0000000 --- a/wear/wear-compose-m3/references/material3/SliderSample.kt.md.txt +++ /dev/null @@ -1,86 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -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.wear.compose.material3.Slider -import androidx.wear.compose.material3.SliderDefaults - -@Sampled -@Composable -fun SliderSample() { - var value by remember { mutableStateOf(4.5f) } - Slider( - value = value, - onValueChange = { value = it }, - valueRange = 3f..6f, - steps = 5, - segmented = false, - ) -} - -@Sampled -@Composable -fun ChangedSliderSample() { - val initialValue = 4.5f - var value by remember { mutableStateOf(5.0f) } - Slider( - value = value, - onValueChange = { value = it }, - valueRange = 3f..6f, - steps = 5, - segmented = false, - colors = - if (value == initialValue) { - SliderDefaults.sliderColors() - } else { - SliderDefaults.variantSliderColors() - }, - ) -} - -@Sampled -@Composable -fun SliderSegmentedSample() { - var value by remember { mutableStateOf(2f) } - Slider( - value = value, - onValueChange = { value = it }, - valueRange = 1f..4f, - steps = 2, - segmented = true, - ) -} - -@Sampled -@Composable -fun SliderWithIntegerSample() { - var value by remember { mutableStateOf(4) } - Slider( - value = value, - onValueChange = { value = it }, - valueProgression = 0..10, - segmented = false, - ) -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/StepperSample.kt.md.txt b/wear/wear-compose-m3/references/material3/StepperSample.kt.md.txt deleted file mode 100644 index ea7c164..0000000 --- a/wear/wear-compose-m3/references/material3/StepperSample.kt.md.txt +++ /dev/null @@ -1,148 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.Stepper -import androidx.wear.compose.material3.StepperDefaults -import androidx.wear.compose.material3.StepperLevelIndicator -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.rangeSemantics -import androidx.wear.compose.material3.samples.icons.HeadphoneIcon -import androidx.wear.compose.material3.samples.icons.VolumeDownIcon -import androidx.wear.compose.material3.samples.icons.VolumeUpIcon - -@Sampled -@Composable -fun StepperSample() { - var value by remember { mutableFloatStateOf(2f) } - val valueRange = remember { 0f..4f } - Box(modifier = Modifier.fillMaxSize()) { - Stepper( - value = value, - onValueChange = { value = it }, - valueRange = valueRange, - steps = 7, - decreaseIcon = { VolumeDownIcon(StepperDefaults.IconSize) }, - increaseIcon = { VolumeUpIcon(StepperDefaults.IconSize) }, - ) { - Text(String.format("Value: %.1f".format(value))) - } - StepperLevelIndicator( - value = { value }, - valueRange = valueRange, - modifier = Modifier.align(Alignment.CenterStart), - ) - } -} - -@Sampled -@Composable -fun StepperWithIntegerSample() { - var value by remember { mutableIntStateOf(3) } - val valueProgression = remember { 0..10 } - Box(modifier = Modifier.fillMaxSize()) { - Stepper( - value = value, - onValueChange = { value = it }, - valueProgression = valueProgression, - decreaseIcon = { VolumeDownIcon(StepperDefaults.IconSize) }, - increaseIcon = { VolumeUpIcon(StepperDefaults.IconSize) }, - ) { - Text(String.format("Value: %d".format(value))) - } - StepperLevelIndicator( - value = { value }, - valueProgression = valueProgression, - modifier = Modifier.align(Alignment.CenterStart), - ) - } -} - -@Sampled -@Composable -fun StepperWithRangeSemanticsSample() { - var value by remember { mutableFloatStateOf(2f) } - val valueRange = remember { 0f..4f } - val onValueChange = { i: Float -> value = i } - val steps = 7 - Box(modifier = Modifier.fillMaxSize()) { - Stepper( - value = value, - onValueChange = onValueChange, - valueRange = valueRange, - modifier = Modifier.rangeSemantics(value, true, onValueChange, valueRange, steps), - steps = steps, - decreaseIcon = { VolumeDownIcon(StepperDefaults.IconSize) }, - increaseIcon = { VolumeUpIcon(StepperDefaults.IconSize) }, - ) { - Text("Value: $value") - } - StepperLevelIndicator( - value = { value }, - valueRange = valueRange, - modifier = Modifier.align(Alignment.CenterStart), - ) - } -} - -@Sampled -@Composable -fun StepperWithButtonSample() { - var value by remember { mutableFloatStateOf(2f) } - val valueRange = remember { 0f..4f } - Box(modifier = Modifier.fillMaxSize()) { - Stepper( - value = value, - onValueChange = { value = it }, - valueRange = valueRange, - increaseIcon = { VolumeUpIcon(StepperDefaults.IconSize) }, - decreaseIcon = { VolumeDownIcon(StepperDefaults.IconSize) }, - steps = 7, - ) { - Text(String.format("Value: %.1f".format(value))) - Button( - onClick = {}, - modifier = Modifier.width(150.dp), - label = { Text(text = "This watch", modifier = Modifier.fillMaxWidth()) }, - secondaryLabel = { Text(text = "Headphones", modifier = Modifier.fillMaxWidth()) }, - icon = { HeadphoneIcon(24.dp) }, - ) - } - StepperLevelIndicator( - value = { value }, - valueRange = valueRange, - modifier = Modifier.align(Alignment.CenterStart), - ) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt b/wear/wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt deleted file mode 100644 index 6b3b7fa..0000000 --- a/wear/wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt +++ /dev/null @@ -1,165 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.paint -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.CardDefaults -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TitleCard -import androidx.wear.compose.material3.lazy.ResponsiveTransformationSpec -import androidx.wear.compose.material3.lazy.TransformationVariableSpec -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Sampled -@Preview -@Composable -fun SurfaceTransformationOnCustomComponent() { - @Composable - fun MyCardComponent( - title: String, - body: String, - transformation: SurfaceTransformation, - modifier: Modifier = Modifier, - ) { - Column( - modifier = - modifier - .fillMaxWidth() - .paint( - transformation.createContainerPainter( - ColorPainter(color = Color.Gray), - shape = RoundedCornerShape(16.dp), - ) - ) - .graphicsLayer { with(transformation) { applyContainerTransformation() } } - .padding(horizontal = 16.dp, vertical = 8.dp) - ) { - Text(title) - Text(body) - } - } - - val transformationSpec = rememberTransformationSpec() - - TransformingLazyColumn { - items(count = 100) { - MyCardComponent( - "Message #$it", - "This is a body", - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ), - ) - } - } -} - -@Sampled -@Preview -@Composable -fun SurfaceTransformationButtonSample() { - val transformationSpec = - rememberTransformationSpec( - ResponsiveTransformationSpec.smallScreen( - contentAlpha = - TransformationVariableSpec( - 0f, - transformationZoneEnterFraction = 0.4f, - transformationZoneExitFraction = 0.8f, - ), - containerAlpha = TransformationVariableSpec(0.3f), - ) - ) - - TransformingLazyColumn { - items(count = 100) { - Button( - onClick = {}, - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ), - ) { - Text("Button #$it") - } - } - } -} - -@Sampled -@Preview -@Composable -fun SurfaceTransformationCardSample() { - val transformationSpec = rememberTransformationSpec() - var expandedIndex by remember { mutableIntStateOf(-1) } - val state = rememberTransformingLazyColumnState() - - AppScaffold { - ScreenScaffold(state) { contentPadding -> - TransformingLazyColumn(state = state, contentPadding = contentPadding) { - items(count = 100) { - TitleCard( - onClick = { expandedIndex = if (expandedIndex == it) -1 else it }, - title = { Text("Card #$it") }, - subtitle = { Text("Subtitle #$it") }, - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ), - ) { - if (it == expandedIndex) { - Text("Expanded content #$it") - } - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt b/wear/wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt deleted file mode 100644 index 75c908d..0000000 --- a/wear/wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt +++ /dev/null @@ -1,171 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -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.saveable.rememberSaveableStateHolder -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.SwipeToDismissValue -import androidx.wear.compose.foundation.edgeSwipeToDismiss -import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState -import androidx.wear.compose.material3.CheckboxButton -import androidx.wear.compose.material3.FilledTonalButton -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.SwipeToDismissBox -import androidx.wear.compose.material3.Text - -@Sampled -@Composable -fun SimpleSwipeToDismissBox(navigateBack: () -> Unit) { - SwipeToDismissBox(onDismissed = navigateBack) { isBackground -> - if (isBackground) { - Box( - modifier = - Modifier.fillMaxSize().background(MaterialTheme.colorScheme.secondaryContainer) - ) - } else { - Column( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primary), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Swipe right to dismiss", color = MaterialTheme.colorScheme.onPrimary) - } - } - } -} - -@Sampled -@Composable -fun StatefulSwipeToDismissBox() { - // State for managing a 2-level navigation hierarchy between - // MainScreen and ItemScreen composables. - // Alternatively, use SwipeDismissableNavHost from wear.compose.navigation. - var showMainScreen by remember { mutableStateOf(true) } - val saveableStateHolder = rememberSaveableStateHolder() - - // Swipe gesture dismisses ItemScreen to return to MainScreen. - val state = rememberSwipeToDismissBoxState() - LaunchedEffect(state.currentValue) { - if (state.currentValue == SwipeToDismissValue.Dismissed) { - state.snapTo(SwipeToDismissValue.Default) - showMainScreen = !showMainScreen - } - } - - // Hierarchy is ListScreen -> ItemScreen, so we show ListScreen as the background behind - // the ItemScreen, otherwise there's no background to show. - SwipeToDismissBox( - state = state, - userSwipeEnabled = !showMainScreen, - backgroundKey = if (!showMainScreen) "MainKey" else "Background", - contentKey = if (showMainScreen) "MainKey" else "ItemKey", - ) { isBackground -> - if (isBackground || showMainScreen) { - // Best practice would be to use State Hoisting and leave this composable stateless. - // Here, we want to support MainScreen being shown from different destinations - // (either in the foreground or in the background during swiping) - that can be achieved - // using SaveableStateHolder and rememberSaveable as shown below. - saveableStateHolder.SaveableStateProvider( - key = "MainKey", - content = { - // Composable that maintains its own state - // and can be shown in foreground or background. - val checked = rememberSaveable { mutableStateOf(true) } - Column( - modifier = - Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), - ) { - FilledTonalButton( - onClick = { showMainScreen = false }, - modifier = Modifier.fillMaxWidth(), - ) { - Text("Item details") - } - CheckboxButton( - label = { Text("Checkbox", maxLines = 1) }, - checked = checked.value, - onCheckedChange = { checked.value = it }, - modifier = Modifier.fillMaxWidth(), - ) - } - }, - ) - } else { - Column( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primary), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Show details here...", color = MaterialTheme.colorScheme.onPrimary) - Text("Swipe right to dismiss", color = MaterialTheme.colorScheme.onPrimary) - } - } - } -} - -@Sampled -@Composable -fun EdgeSwipeForSwipeToDismiss(navigateBack: () -> Unit) { - val state = rememberSwipeToDismissBoxState() - - // When using Modifier.edgeSwipeToDismiss, it is required that the element on which the - // modifier applies exists within a SwipeToDismissBox which shares the same state. - SwipeToDismissBox(state = state, onDismissed = navigateBack) { isBackground -> - val horizontalScrollState = rememberScrollState(0) - if (isBackground) { - Box( - modifier = - Modifier.fillMaxSize().background(MaterialTheme.colorScheme.secondaryContainer) - ) - } else { - Box(modifier = Modifier.fillMaxSize()) { - Text( - modifier = - Modifier.align(Alignment.Center) - .edgeSwipeToDismiss(state) - .horizontalScroll(horizontalScrollState), - text = - "This text can be scrolled horizontally - to dismiss, swipe " + - "right from the left edge of the screen (called Edge Swiping)", - ) - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt deleted file mode 100644 index 9e87918..0000000 --- a/wear/wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt +++ /dev/null @@ -1,68 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -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.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.material3.Icon -import androidx.wear.compose.material3.SplitSwitchButton -import androidx.wear.compose.material3.SwitchButton -import androidx.wear.compose.material3.Text - -@Sampled -@Preview -@Composable -fun SwitchButtonSample() { - var checked by remember { mutableStateOf(true) } - SwitchButton( - label = { Text("Switch Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - secondaryLabel = { - Text("With secondary label", maxLines = 2, overflow = TextOverflow.Ellipsis) - }, - checked = checked, - onCheckedChange = { checked = it }, - icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite icon") }, - enabled = true, - ) -} - -@Sampled -@Preview -@Composable -fun SplitSwitchButtonSample() { - var checked by remember { mutableStateOf(true) } - SplitSwitchButton( - label = { Text("Split Switch Button", maxLines = 3, overflow = TextOverflow.Ellipsis) }, - checked = checked, - onCheckedChange = { checked = it }, - toggleContentDescription = "Split Switch Button Sample", - onContainerClick = { - /* Do something */ - }, - enabled = true, - ) -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TextButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TextButtonSample.kt.md.txt deleted file mode 100644 index cf507f2..0000000 --- a/wear/wear-compose-m3/references/material3/TextButtonSample.kt.md.txt +++ /dev/null @@ -1,115 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TextButton -import androidx.wear.compose.material3.TextButtonDefaults - -@Composable -@Sampled -fun TextButtonSample() { - TextButton(onClick = { /* Do something */ }) { Text(text = "ABC") } -} - -@Composable -@Sampled -fun FilledTextButtonSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.filledTextButtonColors(), - ) { - Text(text = "ABC") - } -} - -@Composable -@Sampled -fun FilledVariantTextButtonSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.filledVariantTextButtonColors(), - ) { - Text(text = "ABC") - } -} - -@Composable -@Sampled -fun LargeFilledTonalTextButtonSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.filledTonalTextButtonColors(), - modifier = Modifier.size(TextButtonDefaults.LargeButtonSize), - ) { - Text(text = "ABC", style = TextButtonDefaults.largeButtonTextStyle) - } -} - -@Composable -@Sampled -fun FilledTonalTextButtonSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.filledTonalTextButtonColors(), - ) { - Text(text = "ABC") - } -} - -@Composable -@Sampled -fun OutlinedTextButtonSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.outlinedTextButtonColors(), - border = ButtonDefaults.outlinedButtonBorder(enabled = true), - ) { - Text(text = "ABC") - } -} - -@Sampled -@Composable -fun TextButtonWithOnLongClickSample(onLongClick: () -> Unit) { - TextButton( - onClick = { /* Do something for onClick*/ }, - onLongClick = onLongClick, - onLongClickLabel = "Long click", - ) { - Text(text = "ABC") - } -} - -@Composable -@Sampled -fun TextButtonWithCornerAnimationSample() { - TextButton( - onClick = { /* Do something */ }, - colors = TextButtonDefaults.filledTextButtonColors(), - shapes = TextButtonDefaults.animatedShapes(), - ) { - Text(text = "ABC") - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt deleted file mode 100644 index 11f43da..0000000 --- a/wear/wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt +++ /dev/null @@ -1,71 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TextButtonDefaults -import androidx.wear.compose.material3.TextToggleButton -import androidx.wear.compose.material3.TextToggleButtonDefaults -import androidx.wear.compose.material3.touchTargetAwareSize - -@Sampled -@Composable -fun TextToggleButtonSample() { - var checked by remember { mutableStateOf(true) } - TextToggleButton( - checked = checked, - onCheckedChange = { checked = !checked }, - shapes = TextToggleButtonDefaults.animatedShapes(), - ) { - Text(text = if (checked) "On" else "Off") - } -} - -@Sampled -@Composable -fun TextToggleButtonVariantSample() { - var checked by remember { mutableStateOf(true) } - TextToggleButton( - checked = checked, - onCheckedChange = { checked = !checked }, - shapes = TextToggleButtonDefaults.variantAnimatedShapes(), - ) { - Text(text = if (checked) "On" else "Off") - } -} - -@Sampled -@Composable -fun LargeTextToggleButtonSample() { - var checked by remember { mutableStateOf(true) } - TextToggleButton( - checked = checked, - onCheckedChange = { checked = !checked }, - modifier = Modifier.touchTargetAwareSize(TextButtonDefaults.LargeButtonSize), - ) { - Text(text = if (checked) "On" else "Off", style = TextToggleButtonDefaults.largeTextStyle) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md.txt deleted file mode 100644 index fc15a6c..0000000 --- a/wear/wear-compose-m3/references/material3/TimeTextSample.kt.md.txt +++ /dev/null @@ -1,65 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import androidx.wear.compose.foundation.CurvedModifier -import androidx.wear.compose.foundation.weight -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.TimeText -import androidx.wear.compose.material3.TimeTextDefaults -import androidx.wear.compose.material3.curvedText -import androidx.wear.compose.material3.timeTextCurvedText -import androidx.wear.compose.material3.timeTextSeparator - -@Sampled -@Composable -fun TimeTextClockOnly() { - // TimeText displays the current time by default. - TimeText() -} - -@Sampled -@Composable -fun TimeTextWithStatus() { - val style = TimeTextDefaults.timeTextStyle() - val primaryStyle = TimeTextDefaults.timeTextStyle(color = MaterialTheme.colorScheme.primary) - TimeText { time -> - timeTextCurvedText("ETA 12:48", style = primaryStyle) - timeTextSeparator(style) - timeTextCurvedText(time) - } -} - -@Sampled -@Composable -fun TimeTextWithStatusEllipsized() { - val style = TimeTextDefaults.timeTextStyle() - TimeText { time -> - curvedText( - "Long status that should be ellipsized.", - CurvedModifier.weight(1f), - overflow = TextOverflow.Ellipsis, - ) - timeTextSeparator(style) - timeTextCurvedText(time) - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt deleted file mode 100644 index 84704cb..0000000 --- a/wear/wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt +++ /dev/null @@ -1,246 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.CompositingStrategy.Companion.ModulateAlpha -import androidx.compose.ui.graphics.GraphicsLayerScope -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.ButtonGroup -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.lazy.ResponsiveTransformationSpec -import androidx.wear.compose.material3.lazy.TransformationSpec -import androidx.wear.compose.material3.lazy.TransformationVariableSpec -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Composable -@Sampled -@Preview -fun CustomTransformationSpecSample() { - val transformationSpec = rememberTransformationSpec() - val morphingTransformationSpec = - object : TransformationSpec by transformationSpec { - override fun GraphicsLayerScope.applyContainerTransformation( - scrollProgress: TransformingLazyColumnItemScrollProgress - ) { - with(transformationSpec) { applyContainerTransformation(scrollProgress) } - rotationX = (scrollProgress.topOffsetFraction - 0.5f).coerceIn(0f..1f) * 270f - } - } - - TransformingLazyColumn( - contentPadding = PaddingValues(20.dp), - modifier = Modifier.background(Color.Black), - ) { - items(count = 100) { index -> - Button( - onClick = {}, - modifier = - Modifier.fillMaxWidth() - .transformedHeight(this, morphingTransformationSpec) - .graphicsLayer { - with(morphingTransformationSpec) { - applyContainerTransformation(scrollProgress) - } - }, - ) { - Text( - "Item $index", - modifier = - Modifier.graphicsLayer { - with(morphingTransformationSpec) { - applyContentTransformation(scrollProgress) - } - }, - ) - } - } - } -} - -@Composable -@Sampled -@Preview -fun CustomCompositingStrategyTransformationSpecSample() { - val transformationSpec = rememberTransformationSpec() - - TransformingLazyColumn( - contentPadding = PaddingValues(20.dp), - modifier = Modifier.background(Color.Black), - ) { - items(count = 100) { index -> - Button( - onClick = {}, - modifier = - Modifier.fillMaxWidth() - .transformedHeight(this, transformationSpec) - .graphicsLayer { - with(transformationSpec) { - applyContainerTransformation(scrollProgress) - } - // Using CompositingStrategy.ModulateAlpha can provide better - // performance when a container transformation involves alpha rendering, - // as it avoids an extra offscreen buffer. - // - // However, care must be taken with overlapping or transparent content - // inside the container. If the content itself uses alpha, ModulateAlpha - // can lead to multiple, incorrect alpha blending with the background - // (double-blending artifacts). - // - // To prevent this, the content's drawing layer must explicitly use - // CompositingStrategy.Offscreen to ensure internal elements are - // correctly pre-blended before the outer ModulateAlpha is applied. - compositingStrategy = ModulateAlpha - }, - ) { - Text( - text = "Item $index", - modifier = - Modifier.graphicsLayer { - // Ensure content layer uses CompositingStrategy.Offscreen when - // container uses CompositingStrategy.ModulateAlpha. - // This composition is required to guarantee correct visual blending of - // content that contains internal alpha or complex blending. - // compositingStrategy is set to CompositingStrategy.Offscreen inside - // applyContentTransformation. - with(transformationSpec) { applyContentTransformation(scrollProgress) } - }, - ) - } - } - } -} - -@Composable -@Sampled -@Preview -fun ResponsiveTransformationSpecButtonSample() { - val transformationSpec = - rememberTransformationSpec( - ResponsiveTransformationSpec.smallScreen( - // Makes the content disappear on the edges. - contentAlpha = TransformationVariableSpec(0f) - ), - ResponsiveTransformationSpec.largeScreen( - // Makes the content disappear on the edges, but a bit more aggressively. - contentAlpha = - TransformationVariableSpec(0f, transformationZoneEnterFraction = 0.2f) - ), - ) - TransformingLazyColumn( - contentPadding = PaddingValues(20.dp), - modifier = Modifier.background(Color.Black), - ) { - items(count = 100) { index -> - Button( - onClick = {}, - modifier = - Modifier.fillMaxWidth().transformedHeight(this@items, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Item $index") - } - } - } -} - -@Composable -@Sampled -@Preview -fun TransformationSpecButtonRowSample() { - // Use the spec derived from default small and large screen specs. - val transformationSpec = rememberTransformationSpec() - - TransformingLazyColumn( - contentPadding = PaddingValues(), - modifier = Modifier.background(Color.Black), - ) { - items(count = 100) { - val interactionSource1 = remember { MutableInteractionSource() } - val interactionSource2 = remember { MutableInteractionSource() } - - ButtonGroup( - modifier = - Modifier.fillMaxWidth() - .graphicsLayer { - with(transformationSpec) { - applyContainerTransformation(scrollProgress) - } - } - .transformedHeight(this, transformationSpec) - .minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - ) { - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource1), - interactionSource = interactionSource1, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - "L", - modifier = - Modifier.graphicsLayer { - with(transformationSpec) { - applyContentTransformation(scrollProgress) - } - }, - ) - } - } - Button( - onClick = {}, - modifier = Modifier.animateWidth(interactionSource2), - interactionSource = interactionSource2, - ) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - "R", - modifier = - Modifier.graphicsLayer { - with(transformationSpec) { - applyContentTransformation(scrollProgress) - } - }, - ) - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt deleted file mode 100644 index d9d2980..0000000 --- a/wear/wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt +++ /dev/null @@ -1,89 +0,0 @@ -``` -/* - * 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 - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.items -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.ListHeader -import androidx.wear.compose.material3.MaterialTheme -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.TitleCard -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.transformedHeight - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnNotificationsSample() { - val state = rememberTransformingLazyColumnState() - val transformationSpec = rememberTransformationSpec() - data class NotificationItem(val title: String, val body: String) - - val notifications = - listOf( - NotificationItem( - "☕ Coffee Break?", - "Step away from the screen and grab a pick-me-up. Step away from the screen and grab a pick-me-up.", - ), - NotificationItem("🌟 You're Awesome!", "Just a little reminder in case you forgot 😊"), - NotificationItem("👀 Did you know?", "Check out [app name]'s latest feature update."), - NotificationItem("📅 Appointment Time", "Your meeting with [name] is in 15 minutes."), - ) - AppScaffold { - ScreenScaffold(state) { contentPadding -> - TransformingLazyColumn(state = state, contentPadding = contentPadding) { - item { - ListHeader( - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec).animateItem(), - ) { - Text("Notifications") - } - } - items(notifications) { notification -> - TitleCard( - onClick = {}, - title = { - Text( - notification.title, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.labelLarge, - maxLines = 1, - ) - }, - subtitle = { Text(notification.body) }, - transformation = SurfaceTransformation(transformationSpec), - modifier = - Modifier.transformedHeight(this, transformationSpec).animateItem(), - ) - } - } - } - } -} -``` \ No newline at end of file diff --git a/wear/wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt b/wear/wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt deleted file mode 100644 index 379c818..0000000 --- a/wear/wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt +++ /dev/null @@ -1,309 +0,0 @@ -``` -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - */ - -package androidx.wear.compose.material3.samples - -import androidx.annotation.Sampled -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -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.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.TransformingLazyColumnFirstLayoutItemProvider -import androidx.wear.compose.foundation.lazy.itemsIndexed -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.AppScaffold -import androidx.wear.compose.material3.Button -import androidx.wear.compose.material3.ButtonDefaults -import androidx.wear.compose.material3.Card -import androidx.wear.compose.material3.CardDefaults -import androidx.wear.compose.material3.CompactButton -import androidx.wear.compose.material3.CompactButtonDefaults -import androidx.wear.compose.material3.EdgeButton -import androidx.wear.compose.material3.ListHeader -import androidx.wear.compose.material3.ListHeaderDefaults -import androidx.wear.compose.material3.OutlinedCard -import androidx.wear.compose.material3.ScreenScaffold -import androidx.wear.compose.material3.SurfaceTransformation -import androidx.wear.compose.material3.Text -import androidx.wear.compose.material3.lazy.firstVisibleItemLayoutItemInfo -import androidx.wear.compose.material3.lazy.layoutItemInfoOf -import androidx.wear.compose.material3.lazy.rememberTransformationSpec -import androidx.wear.compose.material3.lazy.rememberTransformingLazyColumnFirstLayoutItemProvider -import androidx.wear.compose.material3.lazy.transformedHeight -import kotlinx.coroutines.delay - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnButtonsSample() { - val state = rememberTransformingLazyColumnState() - val transformationSpec = rememberTransformationSpec() - AppScaffold { - ScreenScaffold(state) { contentPadding -> - TransformingLazyColumn(state = state, contentPadding = contentPadding) { - items(count = 50) { - Button( - onClick = {}, - modifier = - Modifier.minimumVerticalContentPadding( - ButtonDefaults.minimumVerticalListContentPadding - ) - .fillMaxWidth() - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Item $it") - } - } - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnMinimumVerticalContentPaddingSample() { - val transformationSpec = rememberTransformationSpec() - val state = rememberTransformingLazyColumnState() - var elements by remember { mutableStateOf(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) } - var nextElement by remember { mutableIntStateOf(10) } - - fun addElementAfter(index: Int) { - elements = - elements.subList(0, index + 1) + - listOf(nextElement++) + - elements.subList(index + 1, elements.count()) - } - - fun removeElementAt(index: Int) { - elements = elements.subList(0, index) + elements.subList(index + 1, elements.count()) - } - - AppScaffold { - ScreenScaffold( - state, - edgeButton = { - EdgeButton(onClick = { elements = elements.shuffled() }) { Text("Shuffle") } - }, - ) { contentPadding -> - TransformingLazyColumn(state = state, contentPadding = contentPadding) { - itemsIndexed(elements, key = { _, key -> key }) { index, key -> - if (key % 3 == 0) { - CompactButton( - onClick = { removeElementAt(index) }, - modifier = - Modifier.minimumVerticalContentPadding( - CompactButtonDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec) - .animateItem(), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("CompactButton $key") - } - } else if (key % 4 == 0) { - ListHeader( - modifier = - Modifier.minimumVerticalContentPadding( - ListHeaderDefaults.minimumTopListContentPadding, - ListHeaderDefaults.minimumBottomListContentPadding, - ) - .transformedHeight(this, transformationSpec), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("ListHeader heading") - } - } else { - Card( - onClick = {}, - modifier = - Modifier.fillMaxWidth() - .minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec) - .animateItem(), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Card $key") - Row { - Spacer(modifier = Modifier.weight(1f)) - CompactButton( - onClick = { removeElementAt(index) }, - enabled = elements.count() > 1, - ) { - Text("-") - } - CompactButton(onClick = { addElementAfter(index) }) { Text("+") } - } - } - } - } - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnFirstLayoutItemProviderSample() { - val state = rememberTransformingLazyColumnState() - val transformationSpec = rememberTransformationSpec() - - // This sample demonstrates how to use rememberTransformingLazyColumnFirstLayoutItemProvider - // to control the expansion direction of a dynamically resizing item. By using the voice input - // card's Bottom/End edge as the layout reference, the card predictably expands upwards. - // The constant key safely falls back to default layout behavior when scrolled off-screen. - val firstLayoutItemProvider = rememberTransformingLazyColumnFirstLayoutItemProvider { - state.layoutItemInfoOf( - itemKey = "voice_input", - itemEdge = TransformingLazyColumnFirstLayoutItemProvider.ItemEdge.End, - ) - } - - var isListening by remember { mutableStateOf(false) } - var lineCount by remember { mutableIntStateOf(0) } - // Simple loop simulating text lines arriving over time - LaunchedEffect(isListening) { - if (isListening) { - while (lineCount < 5) { - delay(300) - lineCount++ - } - isListening = false - } - } - - AppScaffold { - ScreenScaffold(state) { contentPadding -> - TransformingLazyColumn( - state = state, - contentPadding = contentPadding, - firstLayoutItemProvider = firstLayoutItemProvider, - ) { - items(count = 3, key = { "message_$it" }) { index -> - Card( - onClick = {}, - modifier = - Modifier.minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec) - .animateItem(placementSpec = null), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Previous message $index") - } - } - - item(key = "voice_input") { - val voiceText = (1..lineCount).joinToString("\n") { "Voice Input line $it" } - - OutlinedCard( - onClick = { - if (lineCount >= 5) lineCount = 0 - isListening = !isListening - }, - modifier = - Modifier.minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec) - .animateItem(placementSpec = null), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text(text = voiceText.ifEmpty { "Tap to speak" }) - } - } - } - } - } -} - -@Sampled -@Preview -@Composable -fun TransformingLazyColumnFirstVisibleItemLayoutItemProviderSample() { - val transformationSpec = rememberTransformationSpec() - val state = rememberTransformingLazyColumnState() - var elements by remember { mutableStateOf(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) } - var nextElement by remember { mutableIntStateOf(10) } - - fun addCardAfter(index: Int) { - elements = - elements.subList(0, index + 1) + - listOf(nextElement++) + - elements.subList(index + 1, elements.count()) - } - - fun removeCardAt(index: Int) { - elements = elements.subList(0, index) + elements.subList(index + 1, elements.count()) - } - - // This sample demonstrates how to use rememberTransformingLazyColumnFirstVisibleItemProvider - // to maintain a stable viewport top when items are added or removed dynamically. - // It achieves this by using the first visible item as the layout reference. - AppScaffold { - ScreenScaffold(state) { contentPadding -> - TransformingLazyColumn( - state = state, - contentPadding = contentPadding, - firstLayoutItemProvider = - rememberTransformingLazyColumnFirstLayoutItemProvider { - if (state.isScrollInProgress) null else state.firstVisibleItemLayoutItemInfo - }, - ) { - itemsIndexed(elements, key = { _, key -> key }) { index, cardKey -> - Card( - onClick = {}, - modifier = - Modifier.minimumVerticalContentPadding( - CardDefaults.minimumVerticalListContentPadding - ) - .transformedHeight(this, transformationSpec) - .animateItem(), - transformation = SurfaceTransformation(transformationSpec), - ) { - Text("Card $cardKey") - Row { - Spacer(modifier = Modifier.weight(1f)) - CompactButton( - onClick = { removeCardAt(index) }, - enabled = elements.count() > 1, - ) { - Text("-") - } - CompactButton(onClick = { addCardAfter(index) }) { Text("+") } - } - } - } - } - } - } -} -``` \ No newline at end of file diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md b/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md index c3ce954..1748d43 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/SKILL.md @@ -9,7 +9,7 @@ description: Provides guidelines for developing projected Android XR apps for di license: Complete terms in LICENSE.txt metadata: author: Google LLC - last-updated: '2026-07-30' + last-updated: '2026-07-22' keywords: - Jetpack Compose Glimmer - audio glasses @@ -250,7 +250,7 @@ feedback for interactive elements. If you are creating a Glimmer Icon component, read the: - **Developer Guidance:** [Jetpack Compose Glimmer: Icons](references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/icons.md) -- **API Source Code (Icon):** Use [references/icon-source.md](https://developer.android.com/agents/skills/xr/display-glasses-with-jetpack-compose-glimmer/references/icon-source). +- **API Source Code (Icon):** Use [references/icon-source.md](references/icon-source.md). - **API Source Code (IconButton):** Use [references/iconbutton-source.md](references/iconbutton-source.md). - **API Source Code (IconSizes):** Use [references/iconsizes-source.md](references/iconsizes-source.md). @@ -264,7 +264,7 @@ different types. ##### Guidelines and usage -- **ListItem Slots:** Use the `ListItem` composable for rows. It provides predefined slots. Use [references/listitem-source.md](https://developer.android.com/agents/skills/xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source) for reference. +- **ListItem Slots:** Use the `ListItem` composable for rows. It provides predefined slots. Use [references/listitem-source.md](references/listitem-source.md) for reference. - **Visual Consistency:** When building lists of similar items, always use a consistent background color (typically `GlimmerTheme.colors.surface`) and corner radius (standard 36.dp) for every item. Don't vary these unless you are visually grouping different *types* of content. - **Integrated Title Chips:** Glimmer Lists support integrated title chips. **IF** you need a section header within a list: **THEN** enable the integrated title chip rather than adding a standalone `TitleChip` to maintain spatial consistency. - **Vertical Arrangement:** ALWAYS use `verticalArrangement = @@ -276,7 +276,7 @@ different types. If you are creating a Glimmer List component, read the: - **API Source Code (List):** Use [references/list-source.md](references/list-source.md). -- **API Source Code (ListItem):** Use [references/listitem-source.md](https://developer.android.com/agents/skills/xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source). +- **API Source Code (ListItem):** Use [references/listitem-source.md](references/listitem-source.md). - **API Source Code (GlimmerLazyListState):** Use [references/liststate-source.md](references/liststate-source.md). #### Stacks diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md index 3dcc770..162b00c 100644 --- a/xr/display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md @@ -1,3 +1,6 @@ +
+ + Applicable XR devices This guidance helps you build experiences for these types of XR devices. [Learn about XR device types →](https://developer.android.com/develop/xr/devices) ![](https://developer.android.com/static/images/develop/xr/ai-glasses-icon.svg) Audio \& Display Glasses [](https://developer.android.com/develop/xr/devices#audio-display) [Learn about XR device types →](https://developer.android.com/develop/xr/devices) @@ -26,31 +29,11 @@ functionality.
-## Before requesting glasses permissions - -Review the [permission principles and workflows](https://developer.android.com/training/permissions/requesting#principles) to make sure you provide the -best experience for your users, like [checking whether the user has already -granted the runtime permissions](https://developer.android.com/training/permissions/requesting#already-granted) your app requires and [whether your app -should show a rationale](https://developer.android.com/training/permissions/requesting#explain) to the user before requesting glasses-specific -permissions. - -## Permission scenarios - -There are different scenarios you might encounter when requesting [dangerous -runtime permissions](https://developer.android.com/guide/topics/permissions/requesting#normal-dangerous) on audio and display glasses: - -- Request runtime permissions from a projected activity -- Request runtime permissions from a phone activity - -See the following sections in this guide for details about each scenario. +## Register the permissions launcher -### Request and handle runtime permissions from a projected activity - -To request permissions for audio glasses and display glasses, first use the -[`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher) with the -[`ProjectedPermissionsResultContract`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsResultContract#ProjectedPermissionsResultContract()) method to register the permissions -launcher. When the user has acted on the permission request, the callback -receives a map of permission names to their granted status. +To request permissions for audio glasses and display glasses, first you use the +[`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher) with the [`ProjectedPermissionsResultContract`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsResultContract#ProjectedPermissionsResultContract()) +method to register the permissions launcher. ```kotlin @@ -69,18 +52,15 @@ private val requestPermissionLauncher: ActivityResultLauncher -#### Key points about the code +### Key points about the code +- The code creates an [`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher) using the [`ProjectedPermissionsResultContract`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsResultContract#ProjectedPermissionsResultContract()) method. The callback receives a map of permission names to their granted status. - You need to specify which permissions your app requires, such as [`Manifest.permission.CAMERA`](https://developer.android.com/reference/kotlin/android/Manifest.permission#camera) or [`Manifest.permission.RECORD_AUDIO`](https://developer.android.com/reference/kotlin/android/Manifest.permission#record_audio). -- Your app should handle both granted and denied results gracefully in the launcher's callback. -To trigger the permission request flow, pass a list of -[`ProjectedPermissionsRequestParams`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsRequestParams) to your registered permission -launcher's `launch` method. The `ProjectedPermissionsRequestParams` object -bundles the requested manifest permissions together with a custom `rationale` -string. The `rationale` string must clearly and concisely explain why the app -requires access to the glasses' hardware features (such as the camera or -microphone). +## Create the request function + +Next, you'll create a function that uses your app's permissions launcher to +request the permissions from the user at runtime. ```kotlin @@ -95,17 +75,72 @@ private fun requestHardwarePermissions() {
-#### Key points about the code +### Key points about the code +- The `requestHardwarePermissions` function builds a [`ProjectedPermissionsRequestParams`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsRequestParams) object. This object bundles the list of permissions your app needs and the user-facing rationale. Make the rationale clear and concise to explain why your app needs these permissions. - Calling `launch` on the launcher triggers the [permission request user flow](https://developer.android.com/develop/xr/jetpack-xr-sdk/request-hardware-permissions#permissions-user-flow). +- Your app should handle both granted and denied results gracefully in the launcher's callback. + +## Create the permissions check function + +Next, you'll create a function that can check whether the user has granted +permissions to your app. + + +```kotlin +private fun hasCameraPermission(): Boolean { + return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == + PackageManager.PERMISSION_GRANTED +} +``` + +
+ +## Add the permission request logic + +And lastly, create the logic that uses these functions to check for and request +the permissions at runtime. + + +```kotlin +if (hasCameraPermission()) { + initializeGlassesFeatures() +} else { + requestHardwarePermissions() +} +``` + +
+ +### Key points about the code + +- If the user has already granted your app the required permissions, the `initializeGlassesFeatures` function is called to initialize your app's experience. This function is defined as [part of your app's activity for AI + glasses](https://developer.android.com/develop/xr/jetpack-xr-sdk/glasses/first-activity#create-activity). -#### Understand the permission request user flow +## Understand the permission request user flow When you launch a permission request using the [`ProjectedPermissionsResultContract`](https://developer.android.com/reference/kotlin/androidx/xr/projected/permissions/ProjectedPermissionsResultContract#ProjectedPermissionsResultContract()) method, the system initiates a coordinated user flow across both the glasses and the phone. +
+ +> [!IMPORTANT] +> **Important:** You should call the `ProjectedPermissionsResultContract` method from an [`Activity`](https://developer.android.com/reference/kotlin/android/app/Activity) displayed on the glasses. Don't use the standard Android permission APIs (such as [`requestPermissions`](https://developer.android.com/reference/kotlin/androidx/core/app/ActivityCompat#requestPermissions(android.app.Activity,%20java.lang.String%5B%5D,%20int)) with [`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher)) in code running on the glasses. Doing so attempts to launch a non-interactable permission dialog on the glasses, breaking the user flow. +> +>
+> +> If your app already has an `Activity` displayed on the phone, you should use +> [`Activity#requestPermissions(permissions, requestCode, deviceId)`](https://developer.android.com/reference/kotlin/android/app/Activity#requestpermissions_1), where +> the `deviceId` comes from calling the [`getDeviceId`](https://developer.android.com/reference/kotlin/android/content/Context#getdeviceid) method on the +> [`Context`](https://developer.android.com/reference/kotlin/android/content/Context) returned by calling +> [`ProjectedContext.createProjectedDeviceContext`](https://developer.android.com/reference/kotlin/androidx/xr/projected/ProjectedContext#createProjectedDeviceContext(android.content.Context)). +> +>
+> +
+ During the permissions user flow, here is what your app and the user can expect: 1. **On the glasses** : An activity appears on the **projected device @@ -139,75 +174,5 @@ During the permissions user flow, here is what your app and the user can expect: 4. **Receiving the result** : After the user makes their final choice, the activities on both the phone and glasses are dismissed. Your - [`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher) callback is then invoked with a map - containing the granted status for each requested permission. - -### Request runtime permissions in a phone activity - -If your app is running in a phone activity but requires permissions for audio or -display glasses---for example, to let a user switch a video stream to the glasses' -camera for a first-person point of view---request the permissions using the -device-specific [`Activity#requestPermissions(permissions, requestCode, -deviceId)`](https://developer.android.com/reference/kotlin/android/app/Activity#requestpermissions_1) method. - -To target audio and display glasses, obtain the appropriate device ID by calling -[`getDeviceId`](https://developer.android.com/reference/kotlin/android/content/Context#getdeviceid) on a projected device context. Pass this ID when requesting -permissions from your phone activity, as shown in the following example: - - -```kotlin -// Request the projected permission from phone activity -requestPermissions( - arrayOf(Manifest.permission.CAMERA), - // REQUEST_CODE_GLASSES_CAMERA is a developer-defined constant - REQUEST_CODE_GLASSES_CAMERA, - projectedDeviceId -) -``` - -
- -#### Handle the permission results - -Once the user responds to the permission dialog on the phone, their decision is -delivered to your app by invoking the onRequestPermissionsResult callback. - -To handle the response, override [`onRequestPermissionsResult`](https://developer.android.com/reference/kotlin/androidx/core/app/ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,java.lang.String%5B%5D,int%5B%5D)) within the -`Activity` instance that initiated the permission request. - -The following code snippet shows how to handle the callback and verify whether -the user granted the requested permissions: - - -```kotlin -private companion object { - // REQUEST_CODE_GLASSES_CAMERA is a developer-defined constant. - const val REQUEST_CODE_GLASSES_CAMERA = 1001 -} - -@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) -override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, - deviceId: Int -) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults, deviceId) - - // Handle the result of the permission request - if (requestCode == REQUEST_CODE_GLASSES_CAMERA && deviceId == projectedDeviceId) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Proceed with glasses camera features - } else { - // Handle glasses permission denied - } - } -} -``` - -
- -##### Key points about the code - -- Use the `deviceId` overload with the `onRequestPermissionsResult` callback to ensure the permission status is correctly mapped to the specific context, such as the audio or display glasses rather than the host phone. -- Your app should handle both granted and denied results gracefully. \ No newline at end of file + [`ActivityResultLauncher`](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher) callback is then invoked with a map containing + the granted status for each requested permission. \ No newline at end of file diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/icon-source.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/icon-source.md new file mode 100644 index 0000000..1b8ad16 --- /dev/null +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/icon-source.md @@ -0,0 +1,389 @@ +When creating a Glimmer Icon component, refer to the following source code in +`Icon.kt`: + + +```kotlin +/* + * 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 + * + * 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. + */ + +package androidx.xr.glimmer + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.structuralEqualityPolicy +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.CacheDrawModifierNode +import androidx.compose.ui.draw.paint +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorProducer +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.toolingGraphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateDraw +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Dp + +/** + * An icon component that draws [imageVector], with a default size of [LocalIconSize], and applies a + * content color tint. Icon is an opinionated component designed to be used with single-color icons + * so that they can be tinted correctly for the component they are placed in. The recommended icon + * sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly + * using [size], and components can use [LocalIconSize] to set the preferred size for any Icons + * inside the component. The content color used to tint this icon is provided by [surface]. To set a + * custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons + * that should not be tinted, use the overload and set [Color.Unspecified] as the tint color. For + * generic images that should not be tinted, and should not use the provided icon size, use the + * generic [androidx.compose.foundation.Image] instead. + * + * @param imageVector [ImageVector] to draw inside this icon + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon( + imageVector: ImageVector, + contentDescription: String?, + modifier: Modifier = Modifier, +) { + Icon( + painter = rememberVectorPainter(imageVector), + contentDescription = contentDescription, + modifier = modifier, + ) +} + +/** + * An icon component that draws [imageVector], with a default size of [LocalIconSize], and applies a + * tint of [tint]. Icon is an opinionated component designed to be used with single-color icons so + * that they can be tinted correctly for the component they are placed in. The recommended icon + * sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly + * using [size], and components can use [LocalIconSize] to set the preferred size for any Icons + * inside the component. Use the other overload of Icon without a [tint] parameter to apply the + * recommended content color provided by a [surface]. For multicolored icons and icons that should + * not be tinted, set [tint] to [Color.Unspecified]. For generic images that should not be tinted, + * and should not use the provided icon size, use the generic [androidx.compose.foundation.Image] + * instead. + * + * @param imageVector [ImageVector] to draw inside this icon + * @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no tint + * is applied. + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon( + imageVector: ImageVector, + tint: Color, + contentDescription: String?, + modifier: Modifier = Modifier, +) { + Icon( + painter = rememberVectorPainter(imageVector), + tint = + if (tint.isSpecified) { + { tint } + } else null, + contentDescription = contentDescription, + modifier = modifier, + ) +} + +/** + * An icon component that draws [bitmap], with a default size of [LocalIconSize], and applies a + * content color tint. Icon is an opinionated component designed to be used with single-color icons + * so that they can be tinted correctly for the component they are placed in. The recommended icon + * sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly + * using [size], and components can use [LocalIconSize] to set the preferred size for any Icons + * inside the component. The content color used to tint this icon is provided by [surface]. To set a + * custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons + * that should not be tinted, use the overload and set [Color.Unspecified] as the tint color. For + * generic images that should not be tinted, and should not use the provided icon size, use the + * generic [androidx.compose.foundation.Image] instead. + * + * @param bitmap [ImageBitmap] to draw inside this icon + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon(bitmap: ImageBitmap, contentDescription: String?, modifier: Modifier = Modifier) { + val painter = remember(bitmap) { BitmapPainter(bitmap) } + Icon(painter = painter, contentDescription = contentDescription, modifier = modifier) +} + +/** + * An icon component that draws [bitmap], with a default size of [LocalIconSize], and applies a tint + * of [tint]. Icon is an opinionated component designed to be used with single-color icons so that + * they can be tinted correctly for the component they are placed in. The recommended icon sizes can + * be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly using [size], + * and components can use [LocalIconSize] to set the preferred size for any Icons inside the + * component. Use the other overload of Icon without a [tint] parameter to apply the recommended + * content color provided by a [surface]. For multicolored icons and icons that should not be + * tinted, set [tint] to [Color.Unspecified]. For generic images that should not be tinted, and + * should not use the provided icon size, use the generic [androidx.compose.foundation.Image] + * instead. + * + * @param bitmap [ImageBitmap] to draw inside this icon + * @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no tint is + * applied. + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon( + bitmap: ImageBitmap, + tint: Color, + contentDescription: String?, + modifier: Modifier = Modifier, +) { + val painter = remember(bitmap) { BitmapPainter(bitmap) } + Icon( + painter = painter, + tint = + if (tint.isSpecified) { + { tint } + } else null, + contentDescription = contentDescription, + modifier = modifier, + ) +} + +/** + * An icon component that draws [painter], with a default size of [LocalIconSize], and applies a + * content color tint. Icon is an opinionated component designed to be used with single-color icons + * so that they can be tinted correctly for the component they are placed in. The recommended icon + * sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly + * using [size], and components can use [LocalIconSize] to set the preferred size for any Icons + * inside the component. The content color used to tint this icon is provided by [surface]. To set a + * custom tint color, use the Icon overload with a tint parameter. For multicolored icons and icons + * that should not be tinted, use the overload and set `null` for the tint parameter. For generic + * images that should not be tinted, and should not use the provided icon size, use the generic + * [androidx.compose.foundation.Image] instead. + * + * @param painter [Painter] to draw inside this icon + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon(painter: Painter, contentDescription: String?, modifier: Modifier = Modifier) { + Icon( + painter = painter, + useContentColor = true, + tint = null, + contentDescription = contentDescription, + modifier = modifier, + ) +} + +/** + * An icon component that draws [painter], with a default size of [LocalIconSize], and applies a + * tint of [tint]. Icon is an opinionated component designed to be used with single-color icons so + * that they can be tinted correctly for the component they are placed in. The recommended icon + * sizes can be retrieved using [GlimmerTheme.Companion.iconSizes]. A size can be set explicitly + * using [size], and components can use [LocalIconSize] to set the preferred size for any Icons + * inside the component. Use the other overload of Icon without a [tint] parameter to apply the + * recommended content color provided by a [surface]. For multicolored icons and icons that should + * not be tinted, set [tint] to `null`. For generic images that should not be tinted, and should not + * use the provided icon size, use the generic [androidx.compose.foundation.Image] instead. + * + * @param painter [Painter] to draw inside this icon + * @param tint tint to be applied to [painter]. If null, then no tint is applied. + * @param contentDescription text used by accessibility services to describe what this icon + * represents. This should always be provided unless this icon is used for decorative purposes, + * and does not represent a meaningful action that a user can take. This text should be localized, + * such as by using [androidx.compose.ui.res.stringResource] or similar + * @param modifier the [Modifier] to be applied to this icon + * @see LocalIconSize + * @see GlimmerTheme.Companion.iconSizes + */ +@Composable +public fun Icon( + painter: Painter, + tint: ColorProducer?, + contentDescription: String?, + modifier: Modifier = Modifier, +) { + Icon( + painter = painter, + useContentColor = false, + tint = tint, + contentDescription = contentDescription, + modifier = modifier, + ) +} + +/** + * CompositionLocal containing the preferred size of an icon. This value will be used by [Icon] by + * default - it can be overridden with a [size] modifier. + */ +public val LocalIconSize: ProvidableCompositionLocal = + compositionLocalOf(structuralEqualityPolicy()) { GlimmerTheme._iconSizes.medium } + +@Composable +private fun Icon( + painter: Painter, + useContentColor: Boolean, + tint: ColorProducer?, + contentDescription: String?, + modifier: Modifier, +) { + val colorFilter = + if (useContentColor || tint != null) { + IconColorFilterElement( + useContentColor = useContentColor, + tint = tint, + painter = painter, + ) + } else { + Modifier + } + val semantics = + if (contentDescription != null) { + Modifier.semantics { + this.contentDescription = contentDescription + this.role = Role.Image + } + } else { + Modifier + } + Box( + modifier + .toolingGraphicsLayer() + .size(LocalIconSize.current) + .then(colorFilter) + .paint(painter, contentScale = ContentScale.Fit) + .then(semantics) + ) +} + +@Suppress("ModifierNodeInspectableProperties") +private class IconColorFilterElement( + private val useContentColor: Boolean, + private val tint: ColorProducer?, + private val painter: Painter, +) : ModifierNodeElement() { + override fun create() = IconColorFilterNode(useContentColor, tint, painter) + + override fun update(node: IconColorFilterNode) { + node.update(useContentColor, tint, painter) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is IconColorFilterElement) return false + + if (useContentColor != other.useContentColor) return false + if (tint != other.tint) return false + if (painter != other.painter) return false + + return true + } + + override fun hashCode(): Int { + var result = useContentColor.hashCode() + result = 31 * result + (tint?.hashCode() ?: 0) + result = 31 * result + painter.hashCode() + return result + } +} + +private class IconColorFilterNode( + private var useContentColor: Boolean, + private var tint: ColorProducer?, + private var painter: Painter, +) : DelegatingNode() { + val cacheDrawNode = + delegate( + CacheDrawModifierNode { + val layer = obtainGraphicsLayer() + layer.apply { record { drawContent() } } + var cachedTintColor = Color.Unspecified + var cachedColorFilter: ColorFilter? = null + onDrawWithContent { + val tintColor = + if (useContentColor) { + currentContentColor() + } else { + tint?.invoke() ?: Color.Unspecified + } + if (cachedTintColor != tintColor) { + cachedTintColor = tintColor + cachedColorFilter = + if (tintColor.isSpecified) ColorFilter.tint(tintColor) else null + layer.colorFilter = cachedColorFilter + } + drawLayer(graphicsLayer = layer) + } + } + ) + + fun update(useContentColor: Boolean, tint: ColorProducer?, painter: Painter) { + val painterChanged = this.painter != painter + if (this.useContentColor != useContentColor || this.tint != tint || painterChanged) { + this.useContentColor = useContentColor + this.tint = tint + this.painter = painter + if (painterChanged) { + cacheDrawNode.invalidateDrawCache() + } else { + cacheDrawNode.invalidateDraw() + } + } + } +} +``` + +
\ No newline at end of file diff --git a/xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md b/xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md new file mode 100644 index 0000000..0f199a2 --- /dev/null +++ b/xr/display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md @@ -0,0 +1,281 @@ +When creating a Glimmer Icon Sizes component, refer to the following source +code in `ListItem.kt` for list items: + + +```kotlin +/* + * 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 + * + * 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. + */ + +package androidx.xr.glimmer + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp + +/** + * ListItem is a component used to represent a single item in a + * [androidx.xr.glimmer.list.GlimmerLazyColumn]. A ListItem has a primary label [content], and may + * also have any combination of [supportingLabel], [leadingIcon], and [trailingIcon]. The supporting + * label is displayed below the primary label and can be used to provide additional information. A + * ListItem fills the maximum width available by default. + * + * This ListItem is focusable - see the other [ListItem] overload for a clickable ListItem. + * + * A simple ListItem with just a primary label: + * + * @sample androidx.xr.glimmer.samples.ListItemSample + * + * A ListItem with a primary and supporting label: + * + * @sample androidx.xr.glimmer.samples.ListItemWithSupportingLabelSample + * + * A ListItem with a primary label, a supporting label, and a leading icon: + * + * @sample androidx.xr.glimmer.samples.ListItemWithSupportingLabelAndLeadingIconSample + * @param modifier the [Modifier] to be applied to this list item + * @param supportingLabel optional supporting label to be placed underneath the primary label + * [content] + * @param leadingIcon optional leading icon to be placed before the primary label [content]. This is + * typically an [Icon] tinted with [contentColor] by default. + * @param trailingIcon optional trailing icon to be placed after the primary label [content]. This + * is typically an [Icon] tinted with [contentColor] by default. + * @param shape the [Shape] used to clip this list item, and also used to draw the background and + * border + * @param color background color of this list item + * @param contentColor content color used by components inside [content], [supportingLabel], + * [leadingIcon], and [trailingIcon]. + * @param border the border to draw around this list item + * @param contentPadding the spacing values to apply internally between the container and the + * content + * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and + * emitting Interactions for this list item. You can use this to change the list item's appearance + * or preview the list item in different states. Note that if `null` is provided, interactions + * will still happen internally. + * @param content the main content / primary label to display inside this list item + */ +@Composable +public fun ListItem( + modifier: Modifier = Modifier, + supportingLabel: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + shape: Shape = GlimmerTheme.shapes.medium, + color: Color = GlimmerTheme.colors.surface, + contentColor: Color = calculateContentColor(color), + border: BorderStroke? = SurfaceDefaults.border(), + contentPadding: PaddingValues = ListItemDefaults.contentPadding, + interactionSource: MutableInteractionSource? = null, + content: @Composable () -> Unit, +) { + ListItemImpl( + modifier = modifier, + onClick = null, + supportingLabel = supportingLabel, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + shape = shape, + color = color, + contentColor = contentColor, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content, + ) +} + +/** + * ListItem is a component used to represent a single item in a + * [androidx.xr.glimmer.list.GlimmerLazyColumn]. A ListItem has a primary label [content], and may + * also have any combination of [supportingLabel], [leadingIcon], and [trailingIcon]. The supporting + * label is displayed below the primary label and can be used to provide additional information. A + * ListItem fills the maximum width available by default. + * + * This ListItem is focusable and clickable - see the other [ListItem] overload for a ListItem that + * is only focusable. + * + * A simple clickable ListItem with just a primary label: + * + * @sample androidx.xr.glimmer.samples.ClickableListItemSample + * + * A clickable ListItem with a primary and supporting label: + * + * @sample androidx.xr.glimmer.samples.ClickableListItemWithSupportingLabelSample + * + * A clickable ListItem with a primary label, a supporting label, and a leading icon: + * + * @sample androidx.xr.glimmer.samples.ClickableListItemWithSupportingLabelAndLeadingIconSample + * @param onClick called when this list item is clicked + * @param modifier the [Modifier] to be applied to this list item + * @param supportingLabel optional supporting label to be placed underneath the primary label + * [content] + * @param leadingIcon optional leading icon to be placed before the primary label [content]. This is + * typically an [Icon] tinted with [contentColor] by default. + * @param trailingIcon optional trailing icon to be placed after the primary label [content]. This + * is typically an [Icon] tinted with [contentColor] by default. + * @param shape the [Shape] used to clip this list item, and also used to draw the background and + * border + * @param color background color of this list item + * @param contentColor content color used by components inside [content], [supportingLabel], + * [leadingIcon], and [trailingIcon]. + * @param border the border to draw around this list item + * @param contentPadding the spacing values to apply internally between the container and the + * content + * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and + * emitting Interactions for this list item. You can use this to change the list item's appearance + * or preview the list item in different states. Note that if `null` is provided, interactions + * will still happen internally. + * @param content the main content / primary label to display inside this list item + */ +@Composable +public fun ListItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, + supportingLabel: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + shape: Shape = GlimmerTheme.shapes.medium, + color: Color = GlimmerTheme.colors.surface, + contentColor: Color = calculateContentColor(color), + border: BorderStroke? = SurfaceDefaults.border(), + contentPadding: PaddingValues = ListItemDefaults.contentPadding, + interactionSource: MutableInteractionSource? = null, + content: @Composable () -> Unit, +) { + ListItemImpl( + modifier = modifier, + onClick = onClick, + supportingLabel = supportingLabel, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + shape = shape, + color = color, + contentColor = contentColor, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content, + ) +} + +@Composable +private fun ListItemImpl( + modifier: Modifier, + onClick: (() -> Unit)?, + supportingLabel: @Composable (() -> Unit)?, + leadingIcon: @Composable (() -> Unit)?, + trailingIcon: @Composable (() -> Unit)?, + shape: Shape, + color: Color, + contentColor: Color, + border: BorderStroke?, + contentPadding: PaddingValues, + interactionSource: MutableInteractionSource?, + content: @Composable () -> Unit, +) { + val iconSize = GlimmerTheme.iconSizes.large + val typography = GlimmerTheme.typography + val innerPadding = GlimmerTheme.componentSpacingValues.small + val internalInteractionSource = interactionSource ?: remember { MutableInteractionSource() } + + val surfaceModifier = + Modifier.surface( + shape = shape, + color = color, + contentColor = contentColor, + // TODO(b/532516157): Reenable depthEffect after b/446294492 is fixed. + depthEffect = null, + border = border, + interactionSource = internalInteractionSource, + ) + .then( + if (onClick != null) { + Modifier.clickable( + interactionSource = internalInteractionSource, + onClick = onClick, + ) + } else { + Modifier.focusable(interactionSource = internalInteractionSource) + } + ) + + Row( + modifier = + modifier + .then(surfaceModifier) + .fillMaxWidth() + .defaultMinSize(minHeight = MinimumHeight) + .padding(contentPadding), + verticalAlignment = CenterVertically, + ) { + if (leadingIcon != null) { + Box(modifier = Modifier.align(Alignment.Top), contentAlignment = Alignment.TopStart) { + CompositionLocalProvider(LocalIconSize provides iconSize, content = leadingIcon) + } + } + Column(Modifier.weight(1f).padding(horizontal = innerPadding)) { + if (supportingLabel == null) { + CompositionLocalProvider( + LocalTextStyle provides typography.bodySmall, + content = content, + ) + } else { + CompositionLocalProvider( + LocalTextStyle provides typography.titleSmall, + content = content, + ) + CompositionLocalProvider( + LocalTextStyle provides typography.bodySmall, + content = supportingLabel, + ) + } + } + if (trailingIcon != null) { + Box(modifier = Modifier.align(Alignment.Top), contentAlignment = Alignment.TopEnd) { + CompositionLocalProvider(LocalIconSize provides iconSize, content = trailingIcon) + } + } + } +} + +/** Default values used for [ListItem] */ +public object ListItemDefaults { + /** Default content padding used for a [ListItem] */ + public val contentPadding: PaddingValues + @Composable get() = PaddingValues(GlimmerTheme.componentSpacingValues.large) +} + +/** Default minimum height for a [ListItem] */ +private val MinimumHeight = 80.dp +``` + +
\ No newline at end of file