├── .editorconfig
├── .github
├── ci-gradle.properties
└── workflows
│ └── build_and_test.yml
├── .gitignore
├── .run
└── spotlessApply.run.xml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── google-services.json
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ ├── AndroidifyApplication.kt
│ │ ├── MainActivity.kt
│ │ └── navigation
│ │ ├── ListSaver.kt
│ │ ├── MainNavigation.kt
│ │ └── NavigationRoutes.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_monochrome.xml
│ └── splash_androidify.xml
│ ├── ic_launcher.png
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mythemedicon.svg
│ ├── play_graphic.png
│ ├── values-v32
│ └── themes.xml
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ ├── data_extraction_rules.xml
│ └── file_paths.xml
├── art
└── androidify_banner.webp
├── benchmark
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── com
│ └── android
│ └── developers
│ └── androidify
│ ├── Utils.kt
│ ├── baselineprofile
│ └── BaselineProfileGenerator.kt
│ └── benchmark
│ ├── PromptBenchmark.kt
│ └── StartupBenchmark.kt
├── build.gradle.kts
├── core
├── network
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ ├── RemoteConfigDataSource.kt
│ │ │ ├── di
│ │ │ └── NetworkModule.kt
│ │ │ ├── model
│ │ │ └── ImageGenerationModels.kt
│ │ │ ├── startup
│ │ │ ├── FirebaseAppCheckInitializer.kt
│ │ │ ├── FirebaseAppInitializer.kt
│ │ │ └── FirebaseRemoteConfigInitializer.kt
│ │ │ └── vertexai
│ │ │ └── FirebaseAiDataSource.kt
│ │ └── res
│ │ └── xml
│ │ ├── remote_config_defaults.xml
│ │ └── startup_initializers.xml
├── testing
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── testing
│ │ ├── AndroidifyTestRunner.kt
│ │ ├── data
│ │ ├── TestFileProvider.kt
│ │ ├── TestGeminiNanoGenerationDataSource.kt
│ │ └── TestInternetConnectivityManager.kt
│ │ ├── network
│ │ ├── TestFirebaseAiDataSource.kt
│ │ └── TestRemoteConfigDataSource.kt
│ │ ├── repository
│ │ ├── FakeImageGenerationRepository.kt
│ │ └── TestTextGenerationRepository.kt
│ │ └── util
│ │ └── MainDispatcherRule.kt
├── theme
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Motion.kt
│ │ │ ├── SharedElementsConfig.kt
│ │ │ ├── Theme.kt
│ │ │ ├── Type.kt
│ │ │ ├── components
│ │ │ ├── AnimatingGradientBrush.kt
│ │ │ ├── Backgrounds.kt
│ │ │ ├── Button.kt
│ │ │ ├── GradientAssistElevatedChip.kt
│ │ │ ├── ScaleIndication.kt
│ │ │ ├── TopAppBar.kt
│ │ │ └── VerticalMarquee.kt
│ │ │ └── transitions
│ │ │ └── ColorSplashTransition.kt
│ │ └── res
│ │ ├── drawable
│ │ ├── decorative_squiggle.xml
│ │ ├── decorative_squiggle_2.xml
│ │ ├── outline_info_24.xml
│ │ ├── photo_camera.xml
│ │ ├── rounded_arrow_back_24.xml
│ │ ├── rounded_arrow_forward_24.xml
│ │ ├── rounded_close_24.xml
│ │ ├── shape_home_bg.xml
│ │ ├── shape_result_bg.xml
│ │ ├── sharp_share_24.xml
│ │ └── squiggle.xml
│ │ ├── values-v23
│ │ └── font_certs.xml
│ │ └── values
│ │ └── strings.xml
└── util
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── android
│ └── developers
│ └── androidify
│ └── util
│ ├── AdaptivePreview.kt
│ ├── AnimationUtils.kt
│ ├── GraphicsUtils.kt
│ ├── LayoutUtils.kt
│ └── LocalFileProvider.kt
├── data
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── android
│ └── developers
│ └── androidify
│ └── data
│ ├── ConfigProvider.kt
│ ├── DataModule.kt
│ ├── Exceptions.kt
│ ├── GeminiNanoDownloader.kt
│ ├── GeminiNanoGenerationDataSource.kt
│ ├── ImageGenerationRepository.kt
│ ├── InternetConnectivityManager.kt
│ └── TextGenerationRepository.kt
├── feature
├── camera
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── camera
│ │ │ └── CameraScreenTest.kt
│ │ ├── debug
│ │ └── screenshotTest
│ │ │ └── reference
│ │ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── camera
│ │ │ └── CameraScreenScreenshotTest
│ │ │ ├── CameraScreenCannotFlipScreenshot_Cannot Flip Camera_24a71026_0.png
│ │ │ ├── CameraScreenMediumHorizontalScreenshot_Medium Horizontal_d8421eca_0.png
│ │ │ ├── CameraScreenPoseNotDetectedScreenshot_Pose Not Detected_02190099_0.png
│ │ │ ├── CameraScreenRearCamDisabledScreenshot_Rear Camera Button (Disabled)_c5370fe4_0.png
│ │ │ ├── CameraScreenRearCamEnabledScreenshot_Rear Camera Button (Enabled)_5de4fe84_0.png
│ │ │ ├── CameraScreenScreenshot_Default State_32838ce9_0.png
│ │ │ ├── CameraScreenSubcompactHorizontalScreenshot_Subcompact Horizontal_096b7a06_0.png
│ │ │ └── CameraScreenTabletopScreenshot_Tabletop Mode_046c4d4b_0.png
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── android
│ │ │ │ └── developers
│ │ │ │ └── androidify
│ │ │ │ └── camera
│ │ │ │ ├── CameraCaptureButton.kt
│ │ │ │ ├── CameraControls.kt
│ │ │ │ ├── CameraDirectionButton.kt
│ │ │ │ ├── CameraGuide.kt
│ │ │ │ ├── CameraGuideText.kt
│ │ │ │ ├── CameraLayout.kt
│ │ │ │ ├── CameraScreen.kt
│ │ │ │ ├── CameraViewModel.kt
│ │ │ │ ├── CameraViewfinder.kt
│ │ │ │ ├── CameraZoomToolbar.kt
│ │ │ │ ├── CoroutineCameraProvider.kt
│ │ │ │ ├── ImageAnalysisExt.kt
│ │ │ │ ├── RearCameraButton.kt
│ │ │ │ ├── RearCameraUseCase.kt
│ │ │ │ └── ZoomState.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── outline_cameraswitch_24.xml
│ │ │ └── outline_rear_camera.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── screenshotTest
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── camera
│ │ └── CameraScreenScreenshotTest.kt
├── creation
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── creation
│ │ │ └── CreationScreenTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── android
│ │ │ │ └── developers
│ │ │ │ └── androidify
│ │ │ │ └── creation
│ │ │ │ ├── AndroidBotColorPicker.kt
│ │ │ │ ├── CreationScreen.kt
│ │ │ │ ├── CreationViewModel.kt
│ │ │ │ └── LoadingScreen.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── choose_picture_image.xml
│ │ │ ├── gemini_24dp.xml
│ │ │ ├── pen_spark_24.xml
│ │ │ ├── rounded_check_24.xml
│ │ │ ├── rounded_draw_24.xml
│ │ │ ├── rounded_photo_24.xml
│ │ │ └── rounded_redo_24.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── kotlin
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── creation
│ │ └── CreationViewModelTest.kt
├── home
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── home
│ │ │ └── HomeScreenTest.kt
│ │ ├── debug
│ │ └── screenshotTest
│ │ │ └── reference
│ │ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── home
│ │ │ └── HomeScreenScreenshotTest
│ │ │ ├── HomeScreenScreenshot_748aa731_0.png
│ │ │ ├── HomeScreenScreenshot_Desktop preview_995fc4f1_0.png
│ │ │ ├── HomeScreenScreenshot_Foldable preview_aaa67744_0.png
│ │ │ ├── HomeScreenScreenshot_Phone preview_d57eb7c3_0.png
│ │ │ └── HomeScreenScreenshot_Tablet preview_deda3981_0.png
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── android
│ │ │ │ └── developers
│ │ │ │ └── androidify
│ │ │ │ └── home
│ │ │ │ ├── AboutScreen.kt
│ │ │ │ ├── AppInactiveScreen.kt
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ └── HomeViewModel.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── rounded_pause_24.xml
│ │ │ └── rounded_play_arrow_24.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── screenshotTest
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── home
│ │ └── HomeScreenScreenshotTest.kt
└── results
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── results
│ │ └── ResultsScreenTest.kt
│ ├── debug
│ └── screenshotTest
│ │ └── reference
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── results
│ │ └── ResultsScreenScreenshotTest
│ │ ├── ResultsScreen_AdaptivePreview_748aa731_0.png
│ │ ├── ResultsScreen_AdaptivePreview_Desktop preview_995fc4f1_0.png
│ │ ├── ResultsScreen_AdaptivePreview_Foldable preview_aaa67744_0.png
│ │ ├── ResultsScreen_AdaptivePreview_Phone preview_d57eb7c3_0.png
│ │ ├── ResultsScreen_AdaptivePreview_Tablet preview_deda3981_0.png
│ │ ├── ResultsScreen_OriginalInputPreview_748aa731_0.png
│ │ ├── ResultsScreen_SmallPreview_748aa731_0.png
│ │ └── ResultsScreen_SmallPreview_Phone small preview_d6ff5f5b_0.png
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── android
│ │ │ └── developers
│ │ │ └── androidify
│ │ │ └── results
│ │ │ ├── BotResultCard.kt
│ │ │ ├── FlippableCard.kt
│ │ │ ├── Permissions.kt
│ │ │ ├── ResultOption.kt
│ │ │ ├── ResultsScreen.kt
│ │ │ ├── ResultsViewModel.kt
│ │ │ └── ShareAction.kt
│ └── res
│ │ ├── drawable-nodpi
│ │ └── placeholderbot.png
│ │ ├── drawable
│ │ ├── pen_spark.xml
│ │ ├── placeholderbot.png
│ │ ├── rounded_download_24.xml
│ │ └── shape_result_bg.xml
│ │ └── values
│ │ └── strings.xml
│ ├── screenshotTest
│ └── java
│ │ └── com
│ │ └── android
│ │ └── developers
│ │ └── androidify
│ │ └── results
│ │ └── ResultsScreenScreenshotTest.kt
│ └── test
│ └── kotlin
│ └── com
│ └── android
│ └── developers
│ └── androidify
│ ├── data
│ └── TextGenerationRepositoryImplTest.kt
│ └── results
│ └── ResultsViewModelTest.kt
├── gradle.properties
├── gradle
├── init.gradle.kts
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── spotless
├── copyright.kt
├── copyright.kts
└── copyright.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org/
2 | # This configuration is used by ktlint when spotless invokes it
3 |
4 | [*.{kt,kts}]
5 | ij_kotlin_allow_trailing_comma=true
6 | ij_kotlin_allow_trailing_comma_on_call_site=true
7 | ktlint_function_naming_ignore_when_annotated_with=Composable, Test
8 | ktlint_standard_backing-property-naming = disabled
9 | ktlint_standard_binary-expression-wrapping = disabled
10 | ktlint_standard_chain-method-continuation = disabled
11 | ktlint_standard_class-signature = disabled
12 | ktlint_standard_condition-wrapping = disabled
13 | ktlint_standard_function-expression-body = disabled
14 | ktlint_standard_function-literal = disabled
15 | ktlint_standard_function-type-modifier-spacing = disabled
16 | ktlint_standard_multiline-loop = disabled
17 | ktlint_standard_function-signature = disabled
--------------------------------------------------------------------------------
/.github/ci-gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2020 The Android Open Source Project
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | org.gradle.daemon=false
18 | org.gradle.parallel=true
19 | org.gradle.jvmargs=-Xmx5120m
20 | org.gradle.workers.max=2
21 |
22 | kotlin.incremental=false
23 | kotlin.compiler.execution.strategy=in-process
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle/buildOutputCleanup/*
2 | /.idea/*
3 | /.gradle/*
4 | /build
5 | /local.properties
6 | .DS_Store
7 | /.kotlin/
8 | /app/release.keystore
9 | /baselineprofile/build/*
10 | /app/src/release/generated/*
11 | /core/build/
12 | /feature/build/
13 |
--------------------------------------------------------------------------------
/.run/spotlessApply.run.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
31 |
32 |
33 | true
34 | true
35 | false
36 |
37 |
38 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | We'd love to accept your patches and contributions to this project.
4 | Please create a bug report before you begin work on large code changes.
5 |
6 | ## Before you begin
7 |
8 | ### Sign our Contributor License Agreement
9 |
10 | Contributions to this project must be accompanied by a
11 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
12 | You (or your employer) retain the copyright to your contribution; this simply
13 | gives us permission to use and redistribute your contributions as part of the
14 | project.
15 |
16 | If you or your current employer have already signed the Google CLA (even if it
17 | was for a different project), you probably don't need to do it again.
18 |
19 | Visit to see your current agreements or to
20 | sign a new one.
21 |
22 | ### Review our community guidelines
23 |
24 | This project follows
25 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/).
26 |
27 | ## Contribution process
28 |
29 | ### Code reviews
30 |
31 | All submissions, including submissions by project members, require review. We
32 | use GitHub pull requests for this purpose. Consult
33 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
34 | information on using pull requests.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Androidify on Android
2 |
3 | 
4 |
5 | The Android bot is a beloved mascot for Android users and developers, with previous versions of the
6 | bot builder being very popular - we decided that this year we’d rebuild the bot maker from the
7 | ground up, using the latest technology backed by Gemini. Today we are releasing a new open source
8 | app, Androidify, for learning how to build powerful AI driven experiences on Android using the
9 | latest technologies such as Jetpack Compose, Gemini API through Firebase AI Logic SDK, CameraX, and
10 | Navigation 3.
11 |
12 | Note: This app is still under development. This sample app is currently using a standard Imagen
13 | model, but we've been working on a fine-tuned model that's trained specifically on all of the pieces
14 | that make the Android bot cute and fun; we'll share that version later this summer. In the meantime,
15 | don't be surprised if the sample app puts out some interesting looking examples!
16 |
17 | For the full blog post on app, [read here](https://android-developers.googleblog.com/2025/05/androidify-building-ai-driven-experiences-jetpack-compose-gemini-camerax.html).
18 |
19 | ## Under the hood
20 | The app combines a variety of different Google technologies, such as:
21 | * Gemini API - through Firebase AI Logic SDK, for accessing the underlying Imagen and Gemini models.
22 | * Jetpack Compose - for building the UI with delightful animations and making the app adapt to different screen sizes.
23 | * Navigation 3 - the latest navigation library for building up Navigation graphs with Compose.
24 | * CameraX and Media3 Compose - for building up a custom camera with custom UI controls (rear camera support, zoom support, tap-to-focus) and playing the promotional video.
25 |
26 | ## Setup and installation
27 |
28 | 1. Clone the repository.
29 | 2. Create a [Firebase project](https://firebase.google.com/products/firebase-ai-logic) and
30 | generate a `google-services.json` file.
31 | Replace the current placeholder app/google-services.json file with your own json file created
32 | above. Be sure to enable Vertex AI SDK.
33 | Ensure to also enable AppCheck on your Firebase project to prevent API abuse.
34 |
35 | 3. This project makes use of remote config on Firebase too, you can import the [Firebase Remote config](https://firebase.google.com/docs/remote-config) settings from
36 | [`remote_config_defaults.xml`](core/network/src/main/res/xml/remote_config_defaults.xml)
37 |
38 | 4. If you'd like to change the font that the app renders with, an optional spec can be placed in
39 | `~/gradlew/gradle.properties` file:
40 |
41 | ```
42 | fontName="Roboto Flex"
43 | ```
44 |
45 | For Googlers, get this info from go/androidify-api-setup
46 |
47 | ## Contributing
48 |
49 | See [Contributing](CONTRIBUTING.md).
50 |
51 | ## License
52 |
53 | Androidify 2.0 is licensed under the [Apache License 2.0](LICENSE). See the `LICENSE` file for
54 | details.
55 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "abc",
4 | "project_id": "YourProjectId",
5 | "storage_bucket": "abc"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "APlaceholderAPIKeyWith-ThirtyNineCharsX",
11 | "android_client_info": {
12 | "package_name": "com.android.developers.androidify"
13 | }
14 | },
15 | "oauth_client": [],
16 | "api_key": [
17 | {
18 | "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
19 | }
20 | ],
21 | "services": {
22 | "appinvite_service": {
23 | "other_platform_oauth_client": []
24 | }
25 | }
26 | }
27 | ],
28 | "configuration_version": "1"
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class com.firebase.** { *; }
23 | -keep interface com.firebase.** { *; }
24 | -keep class org.apache.** { *; }
25 | -keepnames class com.fasterxml.jackson.** { *; }
26 | -keepnames class javax.servlet.** { *; }
27 | -keepnames class org.ietf.jgss.** { *; }
28 | -dontwarn org.w3c.dom.**
29 | -dontwarn org.joda.time.**
30 | -dontwarn org.shaded.apache.**
31 | -dontwarn org.ietf.jgss.**
32 |
33 | -keepattributes Signature
34 | -keepattributes *Annotation*
35 | -keepattributes InnerClasses
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/android/developers/androidify/AndroidifyApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify
17 |
18 | import android.app.Application
19 | import android.content.pm.ApplicationInfo
20 | import android.os.StrictMode
21 | import android.os.StrictMode.ThreadPolicy.Builder
22 | import coil3.ImageLoader
23 | import coil3.PlatformContext
24 | import coil3.SingletonImageLoader
25 | import dagger.hilt.android.HiltAndroidApp
26 | import javax.inject.Inject
27 | @HiltAndroidApp
28 | class AndroidifyApplication : Application(), SingletonImageLoader.Factory {
29 |
30 | @Inject
31 | lateinit var imageLoader: dagger.Lazy
32 |
33 | override fun onCreate() {
34 | super.onCreate()
35 | setStrictModePolicy()
36 | }
37 |
38 | /**
39 | * Return true if the application is debuggable.
40 | */
41 | private fun isDebuggable(): Boolean {
42 | return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
43 | }
44 |
45 | /**
46 | * Set a thread policy that detects all potential problems on the main thread, such as network
47 | * and disk access.
48 | *
49 | * If a problem is found, the offending call will be logged and the application will be killed.
50 | */
51 | private fun setStrictModePolicy() {
52 | if (isDebuggable()) {
53 | StrictMode.setThreadPolicy(
54 | Builder().detectAll().penaltyLog().build(),
55 | )
56 | }
57 | }
58 |
59 | override fun newImageLoader(context: PlatformContext): ImageLoader {
60 | return imageLoader.get()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/android/developers/androidify/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify
17 |
18 | import android.os.Bundle
19 | import androidx.activity.ComponentActivity
20 | import androidx.activity.SystemBarStyle
21 | import androidx.activity.compose.setContent
22 | import androidx.activity.enableEdgeToEdge
23 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
24 | import androidx.compose.ui.graphics.Color
25 | import androidx.compose.ui.graphics.toArgb
26 | import com.android.developers.androidify.navigation.MainNavigation
27 | import com.android.developers.androidify.theme.AndroidifyTheme
28 | import dagger.hilt.android.AndroidEntryPoint
29 |
30 | @ExperimentalMaterial3ExpressiveApi
31 | @AndroidEntryPoint
32 | class MainActivity : ComponentActivity() {
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 |
37 | setContent {
38 | AndroidifyTheme {
39 | enableEdgeToEdge(
40 | statusBarStyle = SystemBarStyle.light(
41 | Color.Transparent.toArgb(),
42 | Color.Transparent.toArgb(),
43 | ),
44 | navigationBarStyle = SystemBarStyle.light(
45 | Color.Transparent.toArgb(),
46 | Color.Transparent.toArgb(),
47 | ),
48 | )
49 | MainNavigation()
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/android/developers/androidify/navigation/NavigationRoutes.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | @file:OptIn(ExperimentalSerializationApi::class)
17 |
18 | package com.android.developers.androidify.navigation
19 |
20 | import kotlinx.serialization.ExperimentalSerializationApi
21 | import kotlinx.serialization.Serializable
22 |
23 | interface NavigationRoute
24 |
25 | @Serializable
26 | data object Home : NavigationRoute
27 |
28 | @Serializable
29 | data class Create(val fileName: String? = null, val prompt: String? = null) : NavigationRoute
30 |
31 | @Serializable
32 | object Camera : NavigationRoute
33 |
34 | @Serializable
35 | object About : NavigationRoute
36 |
--------------------------------------------------------------------------------
/app/src/main/res/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/play_graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/app/src/main/res/play_graphic.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v32/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #FFBB86FC
19 | #FF6200EE
20 | #FF3700B3
21 | #FF03DAC5
22 | #FF018786
23 | #FF000000
24 | #FFFFFFFF
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Androidify
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/art/androidify_banner.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/art/androidify_banner.webp
--------------------------------------------------------------------------------
/benchmark/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/benchmark/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/benchmark/src/main/kotlin/com/android/developers/androidify/Utils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify
17 |
18 | import androidx.benchmark.macro.ExperimentalMetricApi
19 | import androidx.benchmark.macro.TraceSectionMetric
20 |
21 | @OptIn(ExperimentalMetricApi::class)
22 | val jitCompilationMetric = TraceSectionMetric("JIT Compiling %", label = "JIT compilation")
23 |
24 | /**
25 | * A [TraceSectionMetric] that tracks the time spent in class initialization.
26 | *
27 | * This number should go down when a baseline profile is applied properly.
28 | */
29 | @OptIn(ExperimentalMetricApi::class)
30 | val classInitMetric = TraceSectionMetric("L%/%;", label = "ClassInit")
31 |
--------------------------------------------------------------------------------
/benchmark/src/main/kotlin/com/android/developers/androidify/baselineprofile/BaselineProfileGenerator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.baselineprofile
17 |
18 | import android.os.Build
19 | import androidx.annotation.RequiresApi
20 | import androidx.benchmark.macro.junit4.BaselineProfileRule
21 | import androidx.test.ext.junit.runners.AndroidJUnit4
22 | import androidx.test.filters.LargeTest
23 | import androidx.test.uiautomator.textAsString
24 | import androidx.test.uiautomator.uiAutomator
25 | import org.junit.Rule
26 | import org.junit.Test
27 | import org.junit.runner.RunWith
28 |
29 | @RunWith(AndroidJUnit4::class)
30 | @LargeTest
31 | @RequiresApi(Build.VERSION_CODES.P)
32 | class BaselineProfileGenerator {
33 |
34 | @get:Rule
35 | val rule = BaselineProfileRule()
36 |
37 | @Test
38 | fun generate() {
39 | // The application id for the running build variant is read from the instrumentation arguments.
40 | rule.collect(
41 | packageName = "com.android.developers.androidify",
42 | // See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
43 | stableIterations = 3,
44 | includeInStartupProfile = true,
45 | ) {
46 | uiAutomator {
47 | startApp(packageName = packageName)
48 | onView { textAsString() == "Let's Go" }.click()
49 | onView { textAsString() == "Prompt" }.click()
50 | onView { isEditable }.apply {
51 | click()
52 | text =
53 | "wearing brown sneakers, a red t-shirt, " +
54 | "big sunglasses and sports a purple mohawk."
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/benchmark/src/main/kotlin/com/android/developers/androidify/benchmark/StartupBenchmark.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.benchmark
17 |
18 | import android.os.Build
19 | import androidx.annotation.RequiresApi
20 | import androidx.benchmark.macro.CompilationMode
21 | import androidx.benchmark.macro.ExperimentalMetricApi
22 | import androidx.benchmark.macro.FrameTimingMetric
23 | import androidx.benchmark.macro.MemoryUsageMetric
24 | import androidx.benchmark.macro.PowerMetric
25 | import androidx.benchmark.macro.StartupMode
26 | import androidx.benchmark.macro.StartupTimingMetric
27 | import androidx.benchmark.macro.junit4.MacrobenchmarkRule
28 | import androidx.test.ext.junit.runners.AndroidJUnit4
29 | import androidx.test.uiautomator.uiAutomator
30 | import org.junit.Rule
31 | import org.junit.Test
32 | import org.junit.runner.RunWith
33 |
34 | @RunWith(AndroidJUnit4::class)
35 | @RequiresApi(Build.VERSION_CODES.Q)
36 | class StartupBenchmark {
37 | @get:Rule
38 | val benchmarkRule = MacrobenchmarkRule()
39 |
40 | @Test
41 | fun startupNoPrecompilation() = startup(CompilationMode.None())
42 |
43 | @Test
44 | fun startupBaselineProfile() = startup(CompilationMode.DEFAULT)
45 |
46 | @Test
47 | fun startupFullCompilation() = startup(CompilationMode.Full())
48 |
49 | @OptIn(ExperimentalMetricApi::class)
50 | private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
51 | packageName = "com.android.developers.androidify",
52 | metrics = listOf(
53 | StartupTimingMetric(),
54 | FrameTimingMetric(),
55 | MemoryUsageMetric(MemoryUsageMetric.Mode.Max),
56 | PowerMetric(PowerMetric.Type.Power()),
57 | ),
58 | iterations = 10,
59 | compilationMode = compilationMode,
60 | startupMode = StartupMode.COLD,
61 | ) {
62 | uiAutomator {
63 | startApp(packageName = packageName)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.kotlin.ksp) apply false
6 | alias(libs.plugins.kotlin.compose) apply false
7 | alias(libs.plugins.serialization) apply false
8 | alias(libs.plugins.hilt) apply false
9 | alias(libs.plugins.android.library) apply false
10 | alias(libs.plugins.google.services) apply false
11 | alias(libs.plugins.crashlytics) apply false
12 | alias(libs.plugins.android.test) apply false
13 | alias(libs.plugins.baselineprofile) apply false
14 | }
15 |
--------------------------------------------------------------------------------
/core/network/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/network/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/network/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
25 |
26 |
28 |
29 |
31 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/android/developers/androidify/RemoteConfigDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify
17 |
18 | import com.google.firebase.Firebase
19 | import com.google.firebase.remoteconfig.remoteConfig
20 | import javax.inject.Inject
21 | import javax.inject.Singleton
22 |
23 | interface RemoteConfigDataSource {
24 | fun isAppInactive(): Boolean
25 | fun textModelName(): String
26 | fun imageModelName(): String
27 | fun promptTextVerify(): String
28 | fun promptImageValidation(): String
29 | fun promptImageDescription(): String
30 | fun useGeminiNano(): Boolean
31 | fun generateBotPrompt(): String
32 | fun promptImageGenerationWithSkinTone(): String
33 |
34 | fun getPromoVideoLink(): String
35 |
36 | fun getDancingDroidLink(): String
37 | }
38 |
39 | @Singleton
40 | class RemoteConfigDataSourceImpl @Inject constructor() : RemoteConfigDataSource {
41 | private val remoteConfig = Firebase.remoteConfig
42 |
43 | override fun isAppInactive(): Boolean {
44 | return remoteConfig.getBoolean("is_android_app_inactive")
45 | }
46 |
47 | override fun textModelName(): String {
48 | return remoteConfig.getString("text_model_name")
49 | }
50 |
51 | override fun imageModelName(): String {
52 | return remoteConfig.getString("image_model_name")
53 | }
54 |
55 | override fun promptTextVerify(): String {
56 | return remoteConfig.getString("prompt_text_verify")
57 | }
58 |
59 | override fun promptImageValidation(): String {
60 | return remoteConfig.getString("prompt_image_validation")
61 | }
62 |
63 | override fun promptImageDescription(): String {
64 | return remoteConfig.getString("prompt_image_description")
65 | }
66 |
67 | override fun useGeminiNano(): Boolean {
68 | return remoteConfig.getBoolean("use_gemini_nano")
69 | }
70 |
71 | override fun generateBotPrompt(): String {
72 | return remoteConfig.getString("generate_bot_prompt")
73 | }
74 |
75 | override fun promptImageGenerationWithSkinTone(): String {
76 | return remoteConfig.getString("prompt_image_generation_skin_tone")
77 | }
78 |
79 | override fun getPromoVideoLink(): String {
80 | return remoteConfig.getString("promo_video_link")
81 | }
82 |
83 | override fun getDancingDroidLink(): String {
84 | return remoteConfig.getString("dancing_droid_gif_link")
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/android/developers/androidify/model/ImageGenerationModels.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.model
17 |
18 | import androidx.annotation.Keep
19 |
20 | @Keep
21 | data class ValidatedDescription(
22 | val success: Boolean,
23 | val userDescription: String?,
24 | )
25 |
26 | @Keep
27 | data class ValidatedImage(
28 | val success: Boolean,
29 | val errorMessage: ImageValidationError?,
30 | )
31 |
32 | @Keep
33 | data class GeneratedPrompt(
34 | val success: Boolean,
35 | val generatedPrompts: List?,
36 | )
37 |
38 | @Keep
39 | enum class ImageValidationError(val description: String) {
40 | NOT_PERSON("not_a_person"),
41 | NOT_ENOUGH_DETAIL("not_enough_detail"),
42 | POLICY_VIOLATION("policy_violation"),
43 | OTHER("other"),
44 | }
45 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/android/developers/androidify/startup/FirebaseAppCheckInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.startup
17 |
18 | import android.content.Context
19 | import androidx.startup.Initializer
20 | import com.google.firebase.appcheck.FirebaseAppCheck
21 | import com.google.firebase.appcheck.ktx.appCheck
22 | import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
23 | import com.google.firebase.ktx.Firebase
24 |
25 | /**
26 | * Initialize [FirebaseAppCheck] using the App Startup Library.
27 | */
28 | class FirebaseAppCheckInitializer : Initializer {
29 | override fun create(context: Context): FirebaseAppCheck {
30 | return Firebase.appCheck.apply {
31 | installAppCheckProviderFactory(
32 | PlayIntegrityAppCheckProviderFactory.getInstance(),
33 | )
34 | }
35 | }
36 |
37 | override fun dependencies(): List?>?> {
38 | return listOf(FirebaseAppInitializer::class.java)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/android/developers/androidify/startup/FirebaseAppInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.startup
17 |
18 | import android.content.Context
19 | import androidx.startup.Initializer
20 | import com.google.firebase.Firebase
21 | import com.google.firebase.FirebaseApp
22 | import com.google.firebase.initialize
23 |
24 | /**
25 | * Initialize [FirebaseApp] using the App Startup Library.
26 | */
27 | class FirebaseAppInitializer : Initializer {
28 | override fun create(context: Context): FirebaseApp {
29 | return Firebase.initialize(context)!!
30 | }
31 |
32 | override fun dependencies(): List?>?> {
33 | // No dependencies
34 | return emptyList()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/android/developers/androidify/startup/FirebaseRemoteConfigInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.startup
17 |
18 | import android.content.Context
19 | import android.util.Log
20 | import androidx.startup.Initializer
21 | import com.android.developers.androidify.network.R
22 | import com.google.firebase.Firebase
23 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig
24 | import com.google.firebase.remoteconfig.remoteConfig
25 | import com.google.firebase.remoteconfig.remoteConfigSettings
26 |
27 | /**
28 | * Initialize [FirebaseRemoteConfig] using the App Startup Library.
29 | */
30 | class FirebaseRemoteConfigInitializer : Initializer {
31 | override fun create(context: Context): FirebaseRemoteConfig {
32 | return Firebase.remoteConfig.apply {
33 | val configSettings = remoteConfigSettings {
34 | minimumFetchIntervalInSeconds = 600
35 | }
36 | setConfigSettingsAsync(configSettings)
37 | setDefaultsAsync(R.xml.remote_config_defaults)
38 | fetchAndActivate()
39 | .addOnSuccessListener {
40 | Log.d("FirebaseRemoteConfig", "Config params updated: $it")
41 | }
42 | .addOnFailureListener {
43 | Log.d("FirebaseRemoteConfig", "Config params failed: $it")
44 | }
45 | }
46 | }
47 |
48 | override fun dependencies(): List?>?> {
49 | return listOf(FirebaseAppInitializer::class.java)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/network/src/main/res/xml/startup_initializers.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
22 |
23 |
--------------------------------------------------------------------------------
/core/testing/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/testing/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | alias(libs.plugins.android.library)
18 | alias(libs.plugins.kotlin.android)
19 | alias(libs.plugins.kotlin.compose)
20 | alias(libs.plugins.kotlin.ksp)
21 | alias(libs.plugins.hilt)
22 | }
23 |
24 | android {
25 | namespace = "com.android.developers.androidify.testing"
26 | compileSdk = libs.versions.compileSdk.get().toInt()
27 |
28 | defaultConfig {
29 | minSdk = libs.versions.minSdk.get().toInt()
30 | testInstrumentationRunner = "com.android.developers.testing.AndroidifyTestRunner"
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
35 | targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
36 | }
37 | kotlinOptions {
38 | jvmTarget = libs.versions.jvmTarget.get()
39 | }
40 | }
41 |
42 | // Explicitly disable the connectedAndroidTest task for this module
43 | androidComponents {
44 | beforeVariants(selector().all()) { variant ->
45 | variant.enableAndroidTest = false
46 | }
47 | }
48 |
49 | dependencies {
50 | api(libs.kotlinx.coroutines.test)
51 | implementation(platform(libs.androidx.compose.bom))
52 | implementation(libs.androidx.ui)
53 | implementation(libs.androidx.ui.graphics)
54 | implementation(libs.androidx.ui.tooling.preview)
55 | implementation(libs.hilt.android)
56 | implementation(libs.androidx.runner)
57 | implementation(libs.hilt.android.testing)
58 | implementation(project(":data"))
59 | implementation(project(":core:network"))
60 | implementation(project(":core:util"))
61 | ksp(libs.hilt.compiler)
62 |
63 | androidTestImplementation(platform(libs.androidx.compose.bom))
64 | androidTestImplementation(libs.androidx.ui.test.junit4)
65 | androidTestImplementation(libs.hilt.android.testing)
66 | kspAndroidTest(libs.hilt.compiler)
67 | }
68 |
--------------------------------------------------------------------------------
/core/testing/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/testing/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/AndroidifyTestRunner.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing
17 |
18 | import android.app.Application
19 | import android.content.Context
20 | import androidx.test.runner.AndroidJUnitRunner
21 | import dagger.hilt.android.testing.HiltTestApplication
22 |
23 | /**
24 | * A custom runner to set up the instrumented application class for tests.
25 | */
26 | class AndroidifyTestRunner : AndroidJUnitRunner() {
27 | override fun newApplication(cl: ClassLoader, name: String, context: Context): Application =
28 | super.newApplication(cl, HiltTestApplication::class.java.name, context)
29 | }
30 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/data/TestFileProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.data
17 |
18 | import android.graphics.Bitmap
19 | import android.net.Uri
20 | import com.android.developers.androidify.util.LocalFileProvider
21 | import java.io.File
22 | /**
23 | * A test implementation of [LocalFileProvider].
24 | *
25 | * This class provides a stub implementation for testing purposes.
26 | * It does not perform any actual file operations.
27 | */
28 | class TestFileProvider : LocalFileProvider {
29 | override fun saveBitmapToFile(
30 | bitmap: Bitmap,
31 | file: File,
32 | ): File {
33 | TODO("Not yet implemented")
34 | }
35 |
36 | override fun getFileFromCache(fileName: String): File {
37 | TODO("Not yet implemented")
38 | }
39 |
40 | override fun createCacheFile(fileName: String): File {
41 | TODO("Not yet implemented")
42 | }
43 |
44 | override fun saveToSharedStorage(
45 | file: File,
46 | fileName: String,
47 | mimeType: String,
48 | ): Uri {
49 | TODO("Not yet implemented")
50 | }
51 |
52 | override fun sharingUriForFile(file: File): Uri {
53 | TODO("Not yet implemented")
54 | }
55 |
56 | override fun copyToInternalStorage(uri: Uri): File {
57 | return File("")
58 | }
59 |
60 | override fun saveUriToSharedStorage(
61 | inputUri: Uri,
62 | fileName: String,
63 | mimeType: String,
64 | ): Uri {
65 | TODO("Not yet implemented")
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/data/TestGeminiNanoGenerationDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.data
17 |
18 | import com.android.developers.androidify.data.GeminiNanoGenerationDataSource
19 |
20 | class TestGeminiNanoGenerationDataSource(val promptOutput: String?) : GeminiNanoGenerationDataSource {
21 | override suspend fun initialize() {
22 | }
23 |
24 | override suspend fun generatePrompt(prompt: String): String? {
25 | return promptOutput
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/data/TestInternetConnectivityManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.data
17 |
18 | import com.android.developers.androidify.data.InternetConnectivityManager
19 |
20 | /**
21 | * Test implementation of [InternetConnectivityManager].
22 | *
23 | * @property internetAvailable Whether internet is available.
24 | */
25 | class TestInternetConnectivityManager(var internetAvailable: Boolean) : InternetConnectivityManager {
26 |
27 | override fun isInternetAvailable(): Boolean {
28 | return internetAvailable
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/network/TestFirebaseAiDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.network
17 |
18 | import android.graphics.Bitmap
19 | import androidx.core.graphics.createBitmap
20 | import com.android.developers.androidify.model.GeneratedPrompt
21 | import com.android.developers.androidify.model.ValidatedDescription
22 | import com.android.developers.androidify.model.ValidatedImage
23 | import com.android.developers.androidify.vertexai.FirebaseAiDataSource
24 |
25 | class TestFirebaseAiDataSource(val promptOutput: List) : FirebaseAiDataSource {
26 | override suspend fun validatePromptHasEnoughInformation(inputPrompt: String): ValidatedDescription {
27 | return ValidatedDescription(true, "User description")
28 | }
29 |
30 | override suspend fun validateImageHasEnoughInformation(image: Bitmap): ValidatedImage {
31 | return ValidatedImage(true, null)
32 | }
33 |
34 | override suspend fun generateDescriptivePromptFromImage(image: Bitmap): ValidatedDescription {
35 | return ValidatedDescription(true, "User description")
36 | }
37 |
38 | override suspend fun generateImageFromPromptAndSkinTone(
39 | prompt: String,
40 | skinTone: String,
41 | ): Bitmap {
42 | return createBitmap(1, 1)
43 | }
44 |
45 | override suspend fun generatePrompt(prompt: String): GeneratedPrompt {
46 | return GeneratedPrompt(true, promptOutput)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/network/TestRemoteConfigDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.network
17 |
18 | import com.android.developers.androidify.RemoteConfigDataSource
19 |
20 | class TestRemoteConfigDataSource(private val useGeminiNano: Boolean) : RemoteConfigDataSource {
21 | override fun isAppInactive(): Boolean {
22 | TODO("Not yet implemented")
23 | }
24 |
25 | override fun textModelName(): String {
26 | TODO("Not yet implemented")
27 | }
28 |
29 | override fun imageModelName(): String {
30 | TODO("Not yet implemented")
31 | }
32 |
33 | override fun promptTextVerify(): String {
34 | TODO("Not yet implemented")
35 | }
36 |
37 | override fun promptImageValidation(): String {
38 | TODO("Not yet implemented")
39 | }
40 |
41 | override fun promptImageDescription(): String {
42 | TODO("Not yet implemented")
43 | }
44 |
45 | override fun useGeminiNano(): Boolean {
46 | return useGeminiNano
47 | }
48 |
49 | override fun generateBotPrompt(): String {
50 | return "generateBotPrompt"
51 | }
52 |
53 | override fun promptImageGenerationWithSkinTone(): String {
54 | TODO("Not yet implemented")
55 | }
56 | override fun getPromoVideoLink(): String {
57 | TODO("Not yet implemented")
58 | }
59 | override fun getDancingDroidLink(): String {
60 | TODO("Not yet implemented")
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/repository/FakeImageGenerationRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.repository
17 |
18 | import android.graphics.Bitmap
19 | import android.net.Uri
20 | import androidx.core.graphics.createBitmap
21 | import androidx.core.net.toUri
22 | import com.android.developers.androidify.data.ImageGenerationRepository
23 | import java.io.File
24 |
25 | class FakeImageGenerationRepository : ImageGenerationRepository {
26 | override suspend fun initialize() {
27 | }
28 | var exceptionToThrow: Exception? = null
29 |
30 | override suspend fun generateFromDescription(
31 | description: String,
32 | skinTone: String,
33 | ): Bitmap {
34 | if (exceptionToThrow != null) throw exceptionToThrow!!
35 | return createBitmap(1, 1)
36 | }
37 |
38 | override suspend fun generateFromImage(
39 | file: File,
40 | skinTone: String,
41 | ): Bitmap {
42 | if (exceptionToThrow != null) throw exceptionToThrow!!
43 | return createBitmap(1, 1)
44 | }
45 |
46 | override suspend fun saveImage(imageBitmap: Bitmap): Uri {
47 | if (exceptionToThrow != null) throw exceptionToThrow!!
48 | return "content://com.example.app/images/saveImageInternal.jpg".toUri()
49 | }
50 |
51 | override suspend fun saveImageToExternalStorage(imageBitmap: Bitmap): Uri {
52 | if (exceptionToThrow != null) throw exceptionToThrow!!
53 | return "content://com.example.app/images/original.jpg".toUri()
54 | }
55 |
56 | override suspend fun saveImageToExternalStorage(imageUri: Uri): Uri {
57 | if (exceptionToThrow != null) throw exceptionToThrow!!
58 | return imageUri
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/repository/TestTextGenerationRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.testing.repository
17 |
18 | import com.android.developers.androidify.data.TextGenerationRepository
19 |
20 | class TestTextGenerationRepository : TextGenerationRepository {
21 | override suspend fun initialize() {
22 | }
23 |
24 | override suspend fun getNextGeneratedBotPrompt(): String? {
25 | return "Test prompt"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/testing/src/main/java/com/android/developers/testing/util/MainDispatcherRule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | @file:OptIn(ExperimentalCoroutinesApi::class)
17 |
18 | package com.android.developers.testing.util
19 |
20 | import kotlinx.coroutines.Dispatchers
21 | import kotlinx.coroutines.ExperimentalCoroutinesApi
22 | import kotlinx.coroutines.test.TestDispatcher
23 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
24 | import kotlinx.coroutines.test.resetMain
25 | import kotlinx.coroutines.test.setMain
26 | import org.junit.rules.TestRule
27 | import org.junit.rules.TestWatcher
28 | import org.junit.runner.Description
29 |
30 | /**
31 | * A JUnit [TestRule] that sets the Main dispatcher to [testDispatcher]
32 | * for the duration of the test.
33 | */
34 | class MainDispatcherRule(
35 | private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
36 | ) : TestWatcher() {
37 | override fun starting(description: Description) = Dispatchers.setMain(testDispatcher)
38 |
39 | override fun finished(description: Description) = Dispatchers.resetMain()
40 | }
41 |
--------------------------------------------------------------------------------
/core/theme/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/theme/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | alias(libs.plugins.android.library)
18 | alias(libs.plugins.kotlin.android)
19 | alias(libs.plugins.kotlin.compose)
20 | }
21 | val fontName = properties["fontName"] as String?
22 |
23 | android {
24 | namespace = "com.android.developers.androidify.theme"
25 | compileSdk = libs.versions.compileSdk.get().toInt()
26 |
27 | defaultConfig {
28 | minSdk = libs.versions.minSdk.get().toInt()
29 | }
30 |
31 | buildFeatures {
32 | compose = true
33 | buildConfig = true
34 | }
35 |
36 | buildTypes {
37 | debug {
38 | buildConfigField("String" , "fontName" , fontName ?: "\"Roboto Flex\"")
39 | }
40 | release {
41 | buildConfigField("String" , "fontName" , fontName ?: "\"Roboto Flex\"")
42 | }
43 | }
44 | compileOptions {
45 | sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
46 | targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
47 | }
48 | kotlinOptions {
49 | jvmTarget = libs.versions.jvmTarget.get()
50 | }
51 |
52 | }
53 |
54 | dependencies {
55 | implementation(libs.androidx.ui.text.google.fonts)
56 | implementation(platform(libs.androidx.compose.bom))
57 | implementation(libs.androidx.ui.tooling.preview)
58 | implementation(libs.androidx.material3)
59 | implementation(project(":core:util"))
60 |
61 | implementation(libs.androidx.adaptive)
62 | implementation(libs.androidx.adaptive.layout)
63 | // api because we need to access LocalNavAnimatedScope in feature modules for animations.
64 | api(libs.androidx.navigation3.ui)
65 | debugImplementation(libs.ui.tooling)
66 |
67 | // Android Instrumented Tests
68 | androidTestImplementation(platform(libs.androidx.compose.bom))
69 | androidTestImplementation(libs.androidx.ui.test.junit4)
70 | androidTestImplementation(libs.androidx.junit)
71 | androidTestImplementation(libs.androidx.espresso.core)
72 | androidTestImplementation(libs.hilt.android.testing)
73 | androidTestImplementation(project(":core:testing"))
74 |
75 | debugImplementation(libs.androidx.ui.test.manifest)
76 | }
77 |
--------------------------------------------------------------------------------
/core/theme/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/theme/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/theme/src/main/java/com/android/developers/androidify/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.theme
17 |
18 | import androidx.compose.ui.graphics.Color
19 |
20 | val LimeGreen = Color(0xFFC6FF00)
21 | val Primary80 = Color(0xFF6DDD81)
22 | val Primary90 = Color(0xFF89FA9B)
23 | val Blue = Color(0xFF4285F4)
24 |
25 | // Primary colors
26 | val Primary = Color(0xFF34A853)
27 | val OnPrimary = Color(0xFFFFFFFF)
28 | val PrimaryContainer = Color(0xFF34A853)
29 | val OnPrimaryContainer = Color(0xFF202124)
30 |
31 | // Secondary colors
32 | val Secondary = Color(0xFFE6F4EA)
33 | val OnSecondary = Color(0xFF202124)
34 | val SecondaryContainer = Color(0xFFE6F4EA)
35 | val OnSecondaryContainer = Color(0xFF202124)
36 |
37 | // Tertiary colors
38 | val Tertiary = Color(0xFF202124)
39 | val OnTertiary = Color(0xFFFFFFFF)
40 | val TertiaryContainer = Color(0xFF202124)
41 | val OnTertiaryContainer = Color(0xFFFFFFFF)
42 |
43 | // Error colors
44 | val Error = Color(0xFFBA1A1A) // Red
45 | val OnError = Color(0xFFFFFFFF) // White
46 | val ErrorContainer = Color(0xFFFFDAD6) // Light Red
47 | val OnErrorContainer = Color(0xFF93000A) // Dark Red
48 |
49 | // Surface colors
50 | val Surface = Color(0xFFF1F3F4) // White
51 | val SurfaceBright = Color(0xFFE6F4EA) // Light Green
52 | val InverseSurface = Color(0xFF313030) // Dark Gray
53 | val InverseOnSurface = Color(0xFFF3F0EF) // Light gray
54 | val SurfaceContainerLowest = Color(0xFFFFFFFF) // Off White
55 | val SurfaceContainerLow = Color(0xFFEEF0F2) // Light gray
56 | val SurfaceContainer = Color(0xFFE8EBED) // Gray
57 | val SurfaceContainerHigh = Color(0xFFE5E9EB) // Dark Gray
58 | val SurfaceContainerHighest = Color(0xFFE5E9EB) // Very dark Gray
59 |
60 | // Others colors
61 | val OnSurface = Color(0xFF202124) // Black
62 | val OnSurfaceVariant = Color(0xFF434846) // Dark Gray
63 | val Outline = Color(0xFF202124) // Dark Gray
64 | val OutlineVariant = Color(0xFF313030) // Light Dark Gray
65 | val Scrim = Color(0xFF000000) // Black
66 | val Shadow = Color(0xFF000000) // Dark gray
67 |
--------------------------------------------------------------------------------
/core/theme/src/main/java/com/android/developers/androidify/theme/Motion.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.theme
17 |
18 | import androidx.compose.animation.BoundsTransform
19 | import androidx.compose.animation.ExperimentalSharedTransitionApi
20 | import androidx.compose.animation.core.FiniteAnimationSpec
21 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
22 | import androidx.compose.material3.MotionScheme
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.geometry.Rect
25 |
26 | @OptIn(
27 | ExperimentalMaterial3ExpressiveApi::class,
28 | ExperimentalSharedTransitionApi::class,
29 | )
30 | val MotionScheme.sharedElementTransitionSpec: BoundsTransform
31 | @Composable
32 | get() = object : BoundsTransform {
33 | override fun transform(
34 | initialBounds: Rect,
35 | targetBounds: Rect,
36 | ): FiniteAnimationSpec {
37 | return this@sharedElementTransitionSpec.slowSpatialSpec()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/theme/src/main/java/com/android/developers/androidify/theme/components/AnimatingGradientBrush.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.theme.components
17 |
18 | import androidx.compose.animation.core.DurationBasedAnimationSpec
19 | import androidx.compose.animation.core.LinearEasing
20 | import androidx.compose.animation.core.RepeatMode
21 | import androidx.compose.animation.core.animateFloat
22 | import androidx.compose.animation.core.infiniteRepeatable
23 | import androidx.compose.animation.core.rememberInfiniteTransition
24 | import androidx.compose.animation.core.tween
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.runtime.getValue
27 | import androidx.compose.runtime.remember
28 | import androidx.compose.ui.geometry.Offset
29 | import androidx.compose.ui.geometry.Size
30 | import androidx.compose.ui.graphics.Brush
31 | import androidx.compose.ui.graphics.Color
32 | import androidx.compose.ui.graphics.LinearGradientShader
33 | import androidx.compose.ui.graphics.Shader
34 | import androidx.compose.ui.graphics.ShaderBrush
35 | import androidx.compose.ui.graphics.TileMode
36 |
37 | /**
38 | * LinearGradientBrush that animates back and forth with the provided set of colors.
39 | * It assumes a few defaults based on the size of the area passed in and the passed in animation spec is used for the animation itself.
40 | */
41 | @Composable
42 | fun Brush.Companion.infinitelyAnimatingLinearGradient(
43 | colors: List,
44 | animationSpec: DurationBasedAnimationSpec = tween(durationMillis = 2000, easing = LinearEasing),
45 | ): Brush {
46 | val infiniteTransition = rememberInfiniteTransition()
47 |
48 | val offset by infiniteTransition.animateFloat(
49 | initialValue = 0f,
50 | targetValue = 1f,
51 | animationSpec = infiniteRepeatable(
52 | animation = animationSpec,
53 | repeatMode = RepeatMode.Reverse,
54 | ),
55 | )
56 |
57 | return remember(offset) {
58 | object : ShaderBrush() {
59 | override fun createShader(size: Size): Shader {
60 | val widthOffset = size.width * offset
61 | val heightOffset = size.height * offset
62 | return LinearGradientShader(
63 | colors = colors,
64 | from = Offset(widthOffset, heightOffset),
65 | to = Offset(widthOffset + size.width, heightOffset + size.height),
66 | tileMode = TileMode.Mirror,
67 | )
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/core/theme/src/main/java/com/android/developers/androidify/theme/components/ScaleIndication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.theme.components
17 |
18 | import androidx.compose.animation.core.Animatable
19 | import androidx.compose.animation.core.AnimationSpec
20 | import androidx.compose.foundation.IndicationNodeFactory
21 | import androidx.compose.foundation.interaction.InteractionSource
22 | import androidx.compose.foundation.interaction.PressInteraction
23 | import androidx.compose.runtime.Stable
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope
26 | import androidx.compose.ui.graphics.drawscope.scale
27 | import androidx.compose.ui.node.DelegatableNode
28 | import androidx.compose.ui.node.DrawModifierNode
29 | import kotlinx.coroutines.flow.collectLatest
30 | import kotlinx.coroutines.launch
31 |
32 | class ScaleIndicationNode(
33 | private val interactionSource: InteractionSource,
34 | private val animationSpec: AnimationSpec,
35 | private val targetScale: Float = 1.04f,
36 | ) : Modifier.Node(), DrawModifierNode {
37 | val animatedScalePercent = Animatable(1f)
38 |
39 | private suspend fun animateToPressed() {
40 | animatedScalePercent.animateTo(targetScale, animationSpec)
41 | }
42 |
43 | private suspend fun animateToResting() {
44 | animatedScalePercent.animateTo(1f, animationSpec)
45 | }
46 |
47 | override fun onAttach() {
48 | coroutineScope.launch {
49 | interactionSource.interactions.collectLatest { interaction ->
50 | when (interaction) {
51 | is PressInteraction.Press -> animateToPressed()
52 | is PressInteraction.Release -> animateToResting()
53 | is PressInteraction.Cancel -> animateToResting()
54 | }
55 | }
56 | }
57 | }
58 |
59 | override fun ContentDrawScope.draw() {
60 | scale(animatedScalePercent.value) { this@draw.drawContent() }
61 | }
62 | }
63 |
64 | @Stable
65 | class ScaleIndicationNodeFactory(
66 | private val animationSpec: AnimationSpec,
67 | ) : IndicationNodeFactory {
68 | override fun create(interactionSource: InteractionSource): DelegatableNode {
69 | return ScaleIndicationNode(interactionSource, animationSpec)
70 | }
71 |
72 | override fun hashCode(): Int = -1
73 |
74 | override fun equals(other: Any?) = other === this
75 | }
76 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/decorative_squiggle.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/decorative_squiggle_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/outline_info_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/photo_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/rounded_arrow_back_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/rounded_arrow_forward_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/rounded_close_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/shape_home_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
27 |
28 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/shape_result_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/sharp_share_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/drawable/squiggle.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
28 |
31 |
32 |
--------------------------------------------------------------------------------
/core/theme/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Androidify
19 |
--------------------------------------------------------------------------------
/core/util/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/util/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | alias(libs.plugins.android.library)
18 | alias(libs.plugins.kotlin.android)
19 | alias(libs.plugins.kotlin.compose)
20 | alias(libs.plugins.kotlin.ksp)
21 | alias(libs.plugins.hilt)
22 | }
23 |
24 | android {
25 | namespace = "com.android.developers.androidify.util"
26 | compileSdk = libs.versions.compileSdk.get().toInt()
27 |
28 | defaultConfig {
29 | minSdk = libs.versions.minSdk.get().toInt()
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
34 | targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
35 | }
36 | kotlinOptions {
37 | jvmTarget = libs.versions.jvmTarget.get()
38 | }
39 | }
40 | // Explicitly disable the connectedAndroidTest task for this module
41 | androidComponents {
42 | beforeVariants(selector().all()) { variant ->
43 | variant.enableAndroidTest = false
44 | }
45 | }
46 |
47 | dependencies {
48 | implementation(platform(libs.androidx.compose.bom))
49 | implementation(libs.androidx.ui)
50 | implementation(libs.androidx.ui.graphics)
51 | implementation(libs.androidx.material3)
52 | implementation(libs.androidx.ui.tooling.preview)
53 | implementation(libs.hilt.android)
54 | implementation(libs.androidx.animation.android)
55 | implementation(libs.androidx.window)
56 | implementation(libs.androidx.window.core)
57 | ksp(libs.hilt.compiler)
58 |
59 | // debugImplementation(libs.androidx.ui.tooling)
60 | // debugImplementation(libs.androidx.ui.test.manifest)
61 | }
62 |
--------------------------------------------------------------------------------
/core/util/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/util/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/core/util/src/main/java/com/android/developers/androidify/util/AdaptivePreview.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.util
17 |
18 | import androidx.compose.ui.tooling.preview.Devices.DESKTOP
19 | import androidx.compose.ui.tooling.preview.Devices.PIXEL_3A_XL
20 | import androidx.compose.ui.tooling.preview.Devices.PIXEL_7_PRO
21 | import androidx.compose.ui.tooling.preview.Devices.PIXEL_FOLD
22 | import androidx.compose.ui.tooling.preview.Devices.PIXEL_TABLET
23 | import androidx.compose.ui.tooling.preview.Preview
24 |
25 | @Preview(device = PIXEL_7_PRO, name = "Phone preview")
26 | annotation class PhonePreview
27 |
28 | @Preview(device = PIXEL_3A_XL, name = "Phone small preview", heightDp = 300, widthDp = 500)
29 | annotation class SmallPhonePreview
30 |
31 | @Preview(device = PIXEL_FOLD, name = "Foldable preview")
32 | annotation class FoldablePreview
33 |
34 | @Preview(device = PIXEL_TABLET, name = "Tablet preview")
35 | annotation class TabletPreview
36 |
37 | @Preview(device = DESKTOP, name = "Desktop preview")
38 | annotation class DesktopPreview
39 |
40 | @FoldablePreview
41 | @TabletPreview
42 | @DesktopPreview
43 | annotation class LargeScreensPreview
44 |
45 | @PhonePreview
46 | @LargeScreensPreview
47 | annotation class AdaptivePreview
48 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | alias(libs.plugins.android.library)
18 | alias(libs.plugins.kotlin.android)
19 | alias(libs.plugins.serialization)
20 | alias(libs.plugins.kotlin.ksp)
21 | alias(libs.plugins.hilt)
22 | }
23 |
24 | android {
25 | namespace = "com.android.developers.androidify.data"
26 | compileSdk = libs.versions.compileSdk.get().toInt()
27 |
28 | defaultConfig {
29 | minSdk = libs.versions.minSdk.get().toInt()
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
34 | targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
35 | }
36 | kotlinOptions {
37 | jvmTarget = libs.versions.jvmTarget.get()
38 | }
39 | }
40 | // Explicitly disable the connectedAndroidTest task for this module
41 | androidComponents {
42 | beforeVariants(selector().all()) { variant ->
43 | variant.enableAndroidTest = false
44 | }
45 | }
46 |
47 | dependencies {
48 | implementation(project(":core:network"))
49 | implementation(project(":core:util"))
50 |
51 | implementation(libs.kotlinx.serialization.json)
52 | implementation(libs.retrofit)
53 | implementation(libs.converter.gson)
54 | implementation(libs.logging.interceptor)
55 | implementation(libs.hilt.android)
56 | implementation(libs.androidx.hilt.navigation.compose)
57 | implementation(libs.okhttp)
58 | implementation(libs.retrofit.kotlin.serialization)
59 | implementation(libs.ai.edge) {
60 | exclude(group = "com.google.guava")
61 | }
62 | ksp(libs.hilt.compiler)
63 | }
64 |
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/data/src/main/java/com/android/developers/androidify/data/ConfigProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.data
17 |
18 | import com.android.developers.androidify.RemoteConfigDataSource
19 | import javax.inject.Singleton
20 |
21 | @Singleton
22 | class ConfigProvider(val remoteConfigDataSource: RemoteConfigDataSource) {
23 |
24 | fun isAppInactive(): Boolean {
25 | return remoteConfigDataSource.isAppInactive()
26 | }
27 |
28 | fun getPromoVideoLink(): String {
29 | return remoteConfigDataSource.getPromoVideoLink()
30 | }
31 |
32 | fun getDancingDroidLink(): String {
33 | return remoteConfigDataSource.getDancingDroidLink()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/data/src/main/java/com/android/developers/androidify/data/Exceptions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.data
17 |
18 | import com.android.developers.androidify.model.ImageValidationError as ModelImageValidationError
19 |
20 | class InsufficientInformationException(errorMessage: String? = null) : Exception(errorMessage)
21 |
22 | class ImageValidationException(val imageValidationError: ImageValidationError? = null) : Exception(imageValidationError.toString())
23 |
24 | class ImageDescriptionFailedGenerationException() : Exception()
25 | class NoInternetException : Exception("No internet connection")
26 |
27 | enum class ImageValidationError {
28 | NOT_PERSON,
29 | NOT_ENOUGH_DETAIL,
30 | POLICY_VIOLATION,
31 | OTHER,
32 | }
33 |
34 | fun ModelImageValidationError.toImageValidationError(): com.android.developers.androidify.data.ImageValidationError {
35 | return when (this) {
36 | ModelImageValidationError.NOT_PERSON -> com.android.developers.androidify.data.ImageValidationError.NOT_PERSON
37 | ModelImageValidationError.NOT_ENOUGH_DETAIL -> com.android.developers.androidify.data.ImageValidationError.NOT_ENOUGH_DETAIL
38 | ModelImageValidationError.POLICY_VIOLATION -> com.android.developers.androidify.data.ImageValidationError.POLICY_VIOLATION
39 | ModelImageValidationError.OTHER -> com.android.developers.androidify.data.ImageValidationError.OTHER
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/data/src/main/java/com/android/developers/androidify/data/GeminiNanoGenerationDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.data
17 |
18 | import android.util.Log
19 | import javax.inject.Inject
20 | import javax.inject.Singleton
21 |
22 | interface GeminiNanoGenerationDataSource {
23 | suspend fun initialize()
24 | suspend fun generatePrompt(prompt: String): String?
25 | }
26 |
27 | @Singleton
28 | class GeminiNanoGenerationDataSourceImpl @Inject constructor(val downloader: GeminiNanoDownloader) :
29 | GeminiNanoGenerationDataSource {
30 |
31 | override suspend fun initialize() {
32 | downloader.downloadModel()
33 | }
34 |
35 | /**
36 | * Generate a prompt to create an Android bot using Gemini Nano.
37 | * If Gemini Nano is not available, return null.
38 | */
39 | override suspend fun generatePrompt(prompt: String): String? {
40 | if (!downloader.isModelDownloaded()) return null
41 | val response = downloader.generativeModel?.generateContent(prompt)
42 | Log.d("GeminiNanoGenerationDataSource", "generatePrompt: ${response?.text}")
43 | return response?.text
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/data/src/main/java/com/android/developers/androidify/data/InternetConnectivityManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.data
17 |
18 | import android.content.Context
19 | import android.net.ConnectivityManager
20 | import android.net.NetworkCapabilities
21 | import dagger.hilt.android.qualifiers.ApplicationContext
22 | import javax.inject.Inject
23 | import javax.inject.Singleton
24 |
25 | interface InternetConnectivityManager {
26 | fun isInternetAvailable(): Boolean
27 | }
28 |
29 | @Singleton
30 | class InternetConnectivityManagerImpl @Inject constructor(@ApplicationContext val context: Context) :
31 | InternetConnectivityManager {
32 | override fun isInternetAvailable(): Boolean {
33 | val connectivityManager =
34 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
35 | val network = connectivityManager.activeNetwork ?: return false
36 | val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
37 | return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/feature/camera/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/camera/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/camera/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
13 |
14 |
16 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenCannotFlipScreenshot_Cannot Flip Camera_24a71026_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenCannotFlipScreenshot_Cannot Flip Camera_24a71026_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenMediumHorizontalScreenshot_Medium Horizontal_d8421eca_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenMediumHorizontalScreenshot_Medium Horizontal_d8421eca_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenPoseNotDetectedScreenshot_Pose Not Detected_02190099_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenPoseNotDetectedScreenshot_Pose Not Detected_02190099_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenRearCamDisabledScreenshot_Rear Camera Button (Disabled)_c5370fe4_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenRearCamDisabledScreenshot_Rear Camera Button (Disabled)_c5370fe4_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenRearCamEnabledScreenshot_Rear Camera Button (Enabled)_5de4fe84_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenRearCamEnabledScreenshot_Rear Camera Button (Enabled)_5de4fe84_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenScreenshot_Default State_32838ce9_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenScreenshot_Default State_32838ce9_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenSubcompactHorizontalScreenshot_Subcompact Horizontal_096b7a06_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenSubcompactHorizontalScreenshot_Subcompact Horizontal_096b7a06_0.png
--------------------------------------------------------------------------------
/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenTabletopScreenshot_Tabletop Mode_046c4d4b_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/camera/src/debug/screenshotTest/reference/com/android/developers/androidify/camera/CameraScreenScreenshotTest/CameraScreenTabletopScreenshot_Tabletop Mode_046c4d4b_0.png
--------------------------------------------------------------------------------
/feature/camera/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.camera
17 |
18 | import androidx.compose.foundation.layout.Box
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.foundation.layout.Row
21 | import androidx.compose.foundation.layout.Spacer
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.unit.dp
28 | import com.android.developers.androidify.theme.AndroidifyTheme
29 |
30 | @Composable
31 | internal fun CameraControls(
32 | captureImageClicked: () -> Unit,
33 | canFlipCamera: Boolean,
34 | flipCameraDirectionClicked: () -> Unit,
35 | detectedPose: Boolean,
36 | defaultZoomOptions: List,
37 | zoomLevel: () -> Float,
38 | onZoomLevelSelected: (Float) -> Unit,
39 | modifier: Modifier = Modifier,
40 | ) {
41 | Column(
42 | modifier = modifier,
43 | horizontalAlignment = Alignment.CenterHorizontally,
44 | ) {
45 | ZoomToolbar(
46 | defaultZoomOptions = defaultZoomOptions,
47 | zoomLevel = zoomLevel,
48 | onZoomLevelSelected = onZoomLevelSelected
49 | )
50 | Spacer(Modifier.height(12.dp))
51 | Row(verticalAlignment = Alignment.CenterVertically) {
52 | Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
53 | if (canFlipCamera) {
54 | CameraDirectionButton(
55 | flipCameraDirection = flipCameraDirectionClicked,
56 | )
57 | }
58 | }
59 | CameraCaptureButton(
60 | captureImageClicked = captureImageClicked,
61 | enabled = detectedPose,
62 | )
63 | Spacer(modifier = Modifier.weight(1f))
64 | }
65 | }
66 | }
67 |
68 | @Preview
69 | @Composable
70 | private fun CameraControlsPreview() {
71 | AndroidifyTheme {
72 | CameraControls(
73 | captureImageClicked = { },
74 | canFlipCamera = true,
75 | flipCameraDirectionClicked = { },
76 | detectedPose = true,
77 | zoomLevel = {0.4f},
78 | onZoomLevelSelected = {},
79 | defaultZoomOptions = listOf(.6f, 1f),
80 | )
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraDirectionButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.camera
17 |
18 | import androidx.compose.foundation.layout.size
19 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
20 | import androidx.compose.material3.FilledTonalIconButton
21 | import androidx.compose.material3.Icon
22 | import androidx.compose.material3.IconButtonDefaults
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.res.painterResource
26 | import androidx.compose.ui.res.stringResource
27 | import androidx.compose.ui.semantics.onClick
28 | import androidx.compose.ui.semantics.semantics
29 | import androidx.compose.ui.tooling.preview.Preview
30 | import com.android.developers.androidify.theme.AndroidifyTheme
31 |
32 | @OptIn(ExperimentalMaterial3ExpressiveApi::class)
33 | @Composable
34 | fun CameraDirectionButton(
35 | flipCameraDirection: () -> Unit,
36 | modifier: Modifier = Modifier,
37 | ) {
38 | val actionLabel = stringResource(R.string.flip_camera_direction)
39 | FilledTonalIconButton(
40 | onClick = flipCameraDirection,
41 | modifier = modifier
42 | .semantics {
43 | onClick(
44 | label = actionLabel,
45 | action = {
46 | flipCameraDirection()
47 | true
48 | },
49 | )
50 | }
51 | .size(
52 | IconButtonDefaults.mediumContainerSize(
53 | IconButtonDefaults.IconButtonWidthOption.Narrow,
54 | ),
55 | ),
56 | shape = IconButtonDefaults.filledShape,
57 |
58 | ) {
59 | Icon(painterResource(R.drawable.outline_cameraswitch_24), contentDescription = null)
60 | }
61 | }
62 |
63 | @Preview
64 | @Composable
65 | fun CameraDirectionButtonPreview() {
66 | AndroidifyTheme {
67 | CameraDirectionButton(
68 | flipCameraDirection = {},
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraGuideText.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.camera
17 |
18 | import androidx.compose.foundation.background
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.foundation.shape.RoundedCornerShape
21 | import androidx.compose.foundation.text.BasicText
22 | import androidx.compose.foundation.text.TextAutoSize
23 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.res.stringResource
28 | import androidx.compose.ui.unit.dp
29 |
30 | @OptIn(ExperimentalMaterial3ExpressiveApi::class)
31 | @Composable
32 | fun CameraGuideText(
33 | modifier: Modifier = Modifier,
34 | ) {
35 | BasicText(
36 | stringResource(R.string.camera_guide_text_label),
37 | style = MaterialTheme.typography.bodyMediumEmphasized,
38 | autoSize = TextAutoSize.StepBased(maxFontSize = MaterialTheme.typography.bodyMediumEmphasized.fontSize),
39 | maxLines = 1,
40 | modifier = modifier
41 | .background(
42 | MaterialTheme.colorScheme.surfaceContainerHighest,
43 | RoundedCornerShape(24.dp),
44 | )
45 | .padding(10.dp),
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/ImageAnalysisExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.camera
17 |
18 | import androidx.camera.core.ImageAnalysis
19 | import androidx.camera.core.ImageProxy
20 | import kotlinx.coroutines.CoroutineStart
21 | import kotlinx.coroutines.DelicateCoroutinesApi
22 | import kotlinx.coroutines.coroutineScope
23 | import kotlinx.coroutines.launch
24 | import kotlinx.coroutines.suspendCancellableCoroutine
25 |
26 | /**
27 | * Sets the [ImageAnalysis.Analyzer] on image analysis until the calling coroutine is cancelled.
28 | *
29 | * Each time a new [ImageProxy] is available, it will be sent to [block]. The block will be called
30 | * within the [kotlin.coroutines.CoroutineContext] of the calling coroutine. The provided image
31 | * proxy will be automatically closed when [block] completes.
32 | */
33 | @OptIn(DelicateCoroutinesApi::class)
34 | suspend fun ImageAnalysis.analyze(block: suspend (ImageProxy) -> Unit) {
35 | coroutineScope {
36 | try {
37 | suspendCancellableCoroutine { cont ->
38 | setAnalyzer(Runnable::run) { imageProxy ->
39 | // Launch ATOMIC to ensure we close the ImageProxy
40 | launch(start = CoroutineStart.ATOMIC) {
41 | imageProxy.use { if (cont.isActive) block(it) }
42 | }
43 | }
44 | }
45 | } finally {
46 | clearAnalyzer()
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/RearCameraButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.camera
17 |
18 | import androidx.compose.foundation.layout.size
19 | import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
20 | import androidx.compose.material3.FilledTonalIconButton
21 | import androidx.compose.material3.Icon
22 | import androidx.compose.material3.IconButtonDefaults
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.res.painterResource
27 | import androidx.compose.ui.res.stringResource
28 | import androidx.compose.ui.semantics.onClick
29 | import androidx.compose.ui.semantics.semantics
30 | import androidx.compose.ui.unit.dp
31 | import com.android.developers.androidify.theme.Primary80
32 |
33 | @OptIn(ExperimentalMaterial3ExpressiveApi::class)
34 | @Composable
35 | fun RearCameraButton(
36 | isRearCameraEnabled: Boolean = false,
37 | toggleRearCamera: () -> Unit,
38 | modifier: Modifier = Modifier,
39 | ) {
40 | val actionLabel = stringResource(R.string.rear_camera_description)
41 | val colors = if (isRearCameraEnabled) {
42 | IconButtonDefaults.filledTonalIconButtonColors().copy(
43 | containerColor = Primary80,
44 | contentColor = MaterialTheme.colorScheme.onPrimary,
45 | )
46 | } else {
47 | IconButtonDefaults.filledTonalIconButtonColors()
48 | }
49 | FilledTonalIconButton(
50 | onClick = toggleRearCamera,
51 | modifier = modifier
52 | .semantics {
53 | onClick(
54 | label = actionLabel,
55 | action = {
56 | toggleRearCamera()
57 | true
58 | },
59 | )
60 | }
61 | .size(
62 | IconButtonDefaults.mediumContainerSize(
63 | IconButtonDefaults.IconButtonWidthOption.Narrow,
64 | ),
65 | ),
66 | shape = IconButtonDefaults.filledShape,
67 | colors = colors,
68 | ) {
69 | Icon(
70 | painterResource(R.drawable.outline_rear_camera),
71 | contentDescription = null,
72 | modifier = Modifier.size(24.dp),
73 | )
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.developers.androidify.camera
18 |
19 | import androidx.compose.animation.core.Animatable
20 | import androidx.compose.animation.core.AnimationSpec
21 | import androidx.compose.animation.core.tween
22 | import androidx.compose.foundation.MutatorMutex
23 | import androidx.compose.runtime.Stable
24 |
25 | @Stable
26 | class ZoomState(
27 | initialZoomLevel: Float,
28 | val zoomRange: ClosedFloatingPointRange,
29 | val onChangeZoomLevel: (Float) -> Unit,
30 | ) {
31 | private var functionalZoom = initialZoomLevel
32 |
33 | private val mutatorMutex = MutatorMutex()
34 |
35 | /**
36 | * Immediately set the current zoom level to [targetZoomLevel].
37 | */
38 | suspend fun absoluteZoom(targetZoomLevel: Float) {
39 | mutatorMutex.mutate {
40 | functionalZoom = targetZoomLevel.coerceIn(zoomRange)
41 | onChangeZoomLevel(functionalZoom)
42 | }
43 | }
44 |
45 | /**
46 | * Scale the current zoom level.
47 | */
48 | suspend fun scaleZoom(scalingFactor: Float) {
49 | absoluteZoom(scalingFactor * functionalZoom)
50 | }
51 |
52 | /**
53 | * Ease towards a specific zoom level
54 | *
55 | * @param animationSpec [AnimationSpec] used for the animation, default to tween over 500ms
56 | */
57 | suspend fun animatedZoom(
58 | targetZoomLevel: Float,
59 | animationSpec: AnimationSpec = tween(durationMillis = 500),
60 | ) {
61 | mutatorMutex.mutate {
62 | Animatable(initialValue = functionalZoom).animateTo(
63 | targetValue = targetZoomLevel,
64 | animationSpec = animationSpec,
65 | ) {
66 | // this is called every animation frame
67 | functionalZoom = value.coerceIn(zoomRange)
68 | onChangeZoomLevel(functionalZoom)
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/feature/camera/src/main/res/drawable/outline_cameraswitch_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/camera/src/main/res/drawable/outline_rear_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/feature/camera/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Grant camera permission
19 | Grant permission to access the Camera to enable this experience.
20 | uploaded image
21 | +
22 | -
23 | X
24 | Capture
25 | Flip camera direction
26 | Enable Rear Camera
27 | Use guides to frame yourself for the perfect Bot
28 |
29 |
30 | - Great shot!
31 | - Looking good
32 | - What a shot...
33 | - That\'s a beaut!
34 | - Love your outfit!
35 | - Dazzle them with your charm
36 |
37 |
38 |
--------------------------------------------------------------------------------
/feature/creation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/creation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/creation/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
13 |
14 |
16 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/creation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/choose_picture_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/gemini_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/pen_spark_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/rounded_check_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/rounded_draw_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/rounded_photo_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/creation/src/main/res/drawable/rounded_redo_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/home/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/home/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
13 |
14 |
16 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_748aa731_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_748aa731_0.png
--------------------------------------------------------------------------------
/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Desktop preview_995fc4f1_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Desktop preview_995fc4f1_0.png
--------------------------------------------------------------------------------
/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Foldable preview_aaa67744_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Foldable preview_aaa67744_0.png
--------------------------------------------------------------------------------
/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Phone preview_d57eb7c3_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Phone preview_d57eb7c3_0.png
--------------------------------------------------------------------------------
/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Tablet preview_deda3981_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/home/src/debug/screenshotTest/reference/com/android/developers/androidify/home/HomeScreenScreenshotTest/HomeScreenScreenshot_Tablet preview_deda3981_0.png
--------------------------------------------------------------------------------
/feature/home/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/home/src/main/java/com/android/developers/androidify/home/AppInactiveScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.home
17 |
18 | import androidx.compose.foundation.background
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.Spacer
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.layout.size
26 | import androidx.compose.material3.MaterialTheme
27 | import androidx.compose.material3.Scaffold
28 | import androidx.compose.material3.Text
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.res.stringResource
32 | import androidx.compose.ui.text.style.TextAlign
33 | import androidx.compose.ui.tooling.preview.Preview
34 | import androidx.compose.ui.unit.dp
35 | import com.android.developers.androidify.theme.SharedElementContextPreview
36 | import com.android.developers.androidify.theme.components.AndroidifyTopAppBar
37 |
38 | @Preview
39 | @Composable
40 | fun AppInactivePreview() {
41 | SharedElementContextPreview {
42 | AppInactiveScreen()
43 | }
44 | }
45 |
46 | @Composable
47 | fun AppInactiveScreen() {
48 | Scaffold(
49 | topBar = {
50 | AndroidifyTopAppBar()
51 | },
52 | ) { padding ->
53 | Column(
54 | modifier = Modifier
55 | .background(MaterialTheme.colorScheme.secondary)
56 | .fillMaxSize()
57 | .padding(padding)
58 | .padding(64.dp),
59 | verticalArrangement = Arrangement.Center,
60 | ) {
61 | Text(
62 | stringResource(R.string.check_back_later),
63 | style = MaterialTheme.typography.titleLarge,
64 | textAlign = TextAlign.Center,
65 | modifier = Modifier.fillMaxWidth(),
66 | )
67 | Spacer(Modifier.size(16.dp))
68 | Text(
69 | stringResource(R.string.app_is_under_construction),
70 | textAlign = TextAlign.Center,
71 | modifier = Modifier.fillMaxWidth(),
72 | )
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/feature/home/src/main/java/com/android/developers/androidify/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.home
17 |
18 | import androidx.lifecycle.ViewModel
19 | import androidx.lifecycle.viewModelScope
20 | import com.android.developers.androidify.data.ConfigProvider
21 | import dagger.hilt.android.lifecycle.HiltViewModel
22 | import kotlinx.coroutines.flow.MutableStateFlow
23 | import kotlinx.coroutines.flow.asStateFlow
24 | import kotlinx.coroutines.launch
25 | import javax.inject.Inject
26 |
27 | @HiltViewModel
28 | class HomeViewModel @Inject constructor(val configProvider: ConfigProvider) : ViewModel() {
29 | private val _state = MutableStateFlow(HomeState())
30 | val state = _state.asStateFlow()
31 | init {
32 | viewModelScope.launch {
33 | _state.value = _state.value.copy(
34 | isAppActive = !configProvider.isAppInactive(),
35 | dancingDroidLink = configProvider.getDancingDroidLink(),
36 | videoLink = configProvider.getPromoVideoLink(),
37 | )
38 | }
39 | }
40 | }
41 |
42 | data class HomeState(
43 | val isAppActive: Boolean = true,
44 | val videoLink: String? = null,
45 | val dancingDroidLink: String? = null,
46 | )
47 |
--------------------------------------------------------------------------------
/feature/home/src/main/res/drawable/rounded_pause_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/home/src/main/res/drawable/rounded_play_arrow_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/home/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Turning your selfie into a unique Android Bot is quick and easy, powered by Google AI. Here\'s a peek behind the curtain:
19 | Share a selfie and Gemini Flash instantly analyzes it, creating a comprehensive description that captures key details like your accessories, clothing and overall style.
20 | Choose a photo
21 | Our specially fine tuned imagen model uses the detailed description from Gemini Flash to generate a personalized Android Bot that reflects your unique look in the iconic Android style.
22 | AI Creates your bot
23 | Before you meet your bot, ShieldGemma evaluates the generated image to ensure it meets safety guidelines. Once approved, your one-of-a-kind Android bot is revealed, ready for you to share.
24 | Bot Reveal and Safety Check
25 | Back
26 | Create a prompt
27 | Start with photo
28 | Customize your own\n
29 | \n\nAndroid bot
30 | Let\'s Go
31 | Check back later.
32 | App is under construction.
33 | How it works
34 | Upload your photo, make sure it\'s accessorized, good quality, and click let\'s go
35 | Play
36 | Pause
37 | Terms
38 | Privacy
39 |
40 |
--------------------------------------------------------------------------------
/feature/home/src/screenshotTest/java/com/android/developers/androidify/home/HomeScreenScreenshotTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.home
17 |
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import com.android.developers.androidify.theme.AndroidifyTheme
21 | import com.android.developers.androidify.theme.SharedElementContextPreview
22 | import com.android.developers.androidify.util.AdaptivePreview
23 | import com.android.developers.androidify.util.isAtLeastMedium
24 |
25 | class HomeScreenScreenshotTest {
26 |
27 | @AdaptivePreview
28 | @Preview(showBackground = true)
29 | @Composable
30 | fun HomeScreenScreenshot() {
31 | AndroidifyTheme {
32 | SharedElementContextPreview {
33 | HomeScreenContents(
34 | isMediumWindowSize = isAtLeastMedium(),
35 | onClickLetsGo = { },
36 | onAboutClicked = {},
37 | videoLink = "",
38 | dancingBotLink = "",
39 | )
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/feature/results/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/results/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/results/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
13 |
14 |
16 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_748aa731_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_748aa731_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Desktop preview_995fc4f1_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Desktop preview_995fc4f1_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Foldable preview_aaa67744_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Foldable preview_aaa67744_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Phone preview_d57eb7c3_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Phone preview_d57eb7c3_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Tablet preview_deda3981_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_AdaptivePreview_Tablet preview_deda3981_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_OriginalInputPreview_748aa731_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_OriginalInputPreview_748aa731_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_SmallPreview_748aa731_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_SmallPreview_748aa731_0.png
--------------------------------------------------------------------------------
/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_SmallPreview_Phone small preview_d6ff5f5b_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/debug/screenshotTest/reference/com/android/developers/androidify/results/ResultsScreenScreenshotTest/ResultsScreen_SmallPreview_Phone small preview_d6ff5f5b_0.png
--------------------------------------------------------------------------------
/feature/results/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/feature/results/src/main/java/com/android/developers/androidify/results/Permissions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | @file:OptIn(ExperimentalPermissionsApi::class)
17 |
18 | package com.android.developers.androidify.results
19 |
20 | import androidx.compose.material3.AlertDialog
21 | import androidx.compose.material3.Text
22 | import androidx.compose.material3.TextButton
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.res.stringResource
25 | import com.google.accompanist.permissions.ExperimentalPermissionsApi
26 | import com.google.accompanist.permissions.PermissionState
27 |
28 | @Composable
29 | fun PermissionRationaleDialog(
30 | showRationaleDialog: Boolean,
31 | onDismiss: () -> Unit,
32 | externalStoragePermission: PermissionState,
33 | ) {
34 | if (showRationaleDialog) {
35 | AlertDialog(
36 | title = {
37 | Text(text = stringResource(R.string.grant_storage_permission))
38 | },
39 | text = {
40 | Text(text = stringResource(R.string.to_save_your_bot_to_the_device_you_need_to_grant_storage_permission))
41 | },
42 | onDismissRequest = {
43 | onDismiss()
44 | },
45 | confirmButton = {
46 | TextButton(
47 | onClick = {
48 | externalStoragePermission.launchPermissionRequest()
49 | },
50 | ) {
51 | Text(stringResource(R.string.confirm))
52 | }
53 | },
54 | dismissButton = {
55 | TextButton(
56 | onClick = {
57 | onDismiss()
58 | },
59 | ) {
60 | Text(stringResource(R.string.dismiss))
61 | }
62 | },
63 | )
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/feature/results/src/main/java/com/android/developers/androidify/results/ShareAction.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.developers.androidify.results
17 |
18 | import android.content.Context
19 | import android.net.Uri
20 | import androidx.core.app.ShareCompat
21 |
22 | fun shareImage(context: Context, uri: Uri) {
23 | ShareCompat.IntentBuilder(context)
24 | .setType("image/jpeg")
25 | .addStream(uri)
26 | .setChooserTitle(context.getString(R.string.share_text))
27 | .startChooser()
28 | }
29 |
--------------------------------------------------------------------------------
/feature/results/src/main/res/drawable-nodpi/placeholderbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/main/res/drawable-nodpi/placeholderbot.png
--------------------------------------------------------------------------------
/feature/results/src/main/res/drawable/pen_spark.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/feature/results/src/main/res/drawable/placeholderbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/androidify/f84eab31ec32e51f2929e846650700f16d3a2687/feature/results/src/main/res/drawable/placeholderbot.png
--------------------------------------------------------------------------------
/feature/results/src/main/res/drawable/rounded_download_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/feature/results/src/main/res/drawable/shape_result_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/feature/results/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Share your bot
19 | Grant storage permission
20 | To save your bot to the device, you need to grant storage permission.
21 | Confirm
22 | Dismiss
23 | Download complete
24 | Resultant Android bot
25 | Original image
26 | My bot is wearing
27 | Photo
28 | Bot
29 | Share your customized Android bot
30 | Download bot
31 | Prompt
32 |
33 | - You are in your bot era!
34 | - Say hello to mini you!
35 | - Wow, look at that glow up!
36 | - Wow. Spitting image.
37 | - Dressed to impress!
38 | - Hey good looking!
39 |
40 |
41 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.caching=true
10 | org.gradle.parallel=true
11 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. For more details, visit
14 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
15 | # org.gradle.parallel=true
16 | # AndroidX package structure to make it clearer which packages are bundled with the
17 | # Android operating system, and which are packaged with your app's APK
18 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
19 | android.useAndroidX=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | # Enables namespacing of each library's R class so that its R class includes only the
23 | # resources declared in the library itself and none from the library's dependencies,
24 | # thereby reducing the size of the R class for that library
25 | android.nonTransitiveRClass=true
26 | android.disableMinifyLocalDependenciesForLibraries=true
27 | org.gradle.configuration-cache=true
28 | android.experimental.enableScreenshotTest=true
--------------------------------------------------------------------------------
/gradle/init.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | val ktlintVersion = "1.5.0"
3 |
4 | initscript {
5 | val spotlessVersion = "7.0.2"
6 |
7 | repositories {
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath("com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion")
13 | }
14 | }
15 |
16 | rootProject {
17 | subprojects {
18 | apply()
19 | extensions.configure {
20 | kotlin {
21 | target("**/*.kt")
22 | targetExclude("**/build/**/*.kt")
23 | ktlint(ktlintVersion).editorConfigOverride(
24 | mapOf(
25 | "android" to "true",
26 | "ktlint_standard_property-naming" to "disabled"
27 | ),
28 | )
29 | licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
30 | }
31 | format("kts") {
32 | target("**/*.kts")
33 | targetExclude("**/build/**/*.kts")
34 | // Look for the first line that doesn't have a block comment (assumed to be the license)
35 | licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
36 | }
37 | format("xml") {
38 | target("**/*.xml")
39 | targetExclude("**/build/**/*.xml")
40 | // Look for the first XML tag that isn't a comment (
--------------------------------------------------------------------------------