The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.md
    │   ├── feature_request.md
    │   └── support_request.md
    └── workflows
    │   └── build_android.yaml
├── .gitignore
├── Android
    ├── .gitignore
    ├── README.md
    └── src
    │   ├── .gitignore
    │   ├── app
    │       ├── .gitignore
    │       ├── build.gradle.kts
    │       └── src
    │       │   ├── main
    │       │       ├── AndroidManifest.xml
    │       │       ├── java
    │       │       │   └── com
    │       │       │   │   └── google
    │       │       │   │       └── ai
    │       │       │   │           └── edge
    │       │       │   │               └── gallery
    │       │       │   │                   ├── Analytics.kt
    │       │       │   │                   ├── GalleryApp.kt
    │       │       │   │                   ├── GalleryAppTopBar.kt
    │       │       │   │                   ├── GalleryApplication.kt
    │       │       │   │                   ├── GalleryLifecycleProvider.kt
    │       │       │   │                   ├── MainActivity.kt
    │       │       │   │                   ├── SettingsSerializer.kt
    │       │       │   │                   ├── common
    │       │       │   │                       ├── Types.kt
    │       │       │   │                       └── Utils.kt
    │       │       │   │                   ├── data
    │       │       │   │                       ├── AppBarAction.kt
    │       │       │   │                       ├── AppContainer.kt
    │       │       │   │                       ├── Config.kt
    │       │       │   │                       ├── ConfigValue.kt
    │       │       │   │                       ├── Consts.kt
    │       │       │   │                       ├── DataStoreRepository.kt
    │       │       │   │                       ├── DownloadRepository.kt
    │       │       │   │                       ├── Model.kt
    │       │       │   │                       ├── ModelAllowlist.kt
    │       │       │   │                       ├── Tasks.kt
    │       │       │   │                       └── Types.kt
    │       │       │   │                   ├── di
    │       │       │   │                       └── AppModule.kt
    │       │       │   │                   ├── ui
    │       │       │   │                       ├── common
    │       │       │   │                       │   ├── AuthConfig.kt
    │       │       │   │                       │   ├── ClickableLink.kt
    │       │       │   │                       │   ├── ColorUtils.kt
    │       │       │   │                       │   ├── ConfigDialog.kt
    │       │       │   │                       │   ├── DownloadAndTryButton.kt
    │       │       │   │                       │   ├── ErrorDialog.kt
    │       │       │   │                       │   ├── MarkdownText.kt
    │       │       │   │                       │   ├── ModelPageAppBar.kt
    │       │       │   │                       │   ├── ModelPicker.kt
    │       │       │   │                       │   ├── ModelPickerChipsPager.kt
    │       │       │   │                       │   ├── TaskIcon.kt
    │       │       │   │                       │   ├── TosSheet.kt
    │       │       │   │                       │   ├── Utils.kt
    │       │       │   │                       │   ├── chat
    │       │       │   │                       │   │   ├── AudioPlaybackPanel.kt
    │       │       │   │                       │   │   ├── AudioRecorderPanel.kt
    │       │       │   │                       │   │   ├── BenchmarkConfigDialog.kt
    │       │       │   │                       │   │   ├── ChatMessage.kt
    │       │       │   │                       │   │   ├── ChatPanel.kt
    │       │       │   │                       │   │   ├── ChatView.kt
    │       │       │   │                       │   │   ├── ChatViewModel.kt
    │       │       │   │                       │   │   ├── DataCard.kt
    │       │       │   │                       │   │   ├── MessageActionButton.kt
    │       │       │   │                       │   │   ├── MessageBodyAudioClip.kt
    │       │       │   │                       │   │   ├── MessageBodyBenchmark.kt
    │       │       │   │                       │   │   ├── MessageBodyBenchmarkLlm.kt
    │       │       │   │                       │   │   ├── MessageBodyClassification.kt
    │       │       │   │                       │   │   ├── MessageBodyConfigUpdate.kt
    │       │       │   │                       │   │   ├── MessageBodyImage.kt
    │       │       │   │                       │   │   ├── MessageBodyImageWithHistory.kt
    │       │       │   │                       │   │   ├── MessageBodyInfo.kt
    │       │       │   │                       │   │   ├── MessageBodyLoading.kt
    │       │       │   │                       │   │   ├── MessageBodyPromptTemplates.kt
    │       │       │   │                       │   │   ├── MessageBodyText.kt
    │       │       │   │                       │   │   ├── MessageBodyWarning.kt
    │       │       │   │                       │   │   ├── MessageBubbleShape.kt
    │       │       │   │                       │   │   ├── MessageInputImage.kt
    │       │       │   │                       │   │   ├── MessageInputText.kt
    │       │       │   │                       │   │   ├── MessageLatency.kt
    │       │       │   │                       │   │   ├── MessageSender.kt
    │       │       │   │                       │   │   ├── ModelDownloadStatusInfoPanel.kt
    │       │       │   │                       │   │   ├── ModelDownloadingAnimation.kt
    │       │       │   │                       │   │   ├── ModelInitializationStatus.kt
    │       │       │   │                       │   │   ├── ModelNotDownloaded.kt
    │       │       │   │                       │   │   ├── ModelSelector.kt
    │       │       │   │                       │   │   ├── TextInputHistorySheet.kt
    │       │       │   │                       │   │   └── ZoomableBox.kt
    │       │       │   │                       │   └── modelitem
    │       │       │   │                       │   │   ├── ConfirmDeleteModelDialog.kt
    │       │       │   │                       │   │   ├── ModelItem.kt
    │       │       │   │                       │   │   ├── ModelItemActionButton.kt
    │       │       │   │                       │   │   ├── ModelNameAndStatus.kt
    │       │       │   │                       │   │   └── StatusIcon.kt
    │       │       │   │                       ├── home
    │       │       │   │                       │   ├── HomeScreen.kt
    │       │       │   │                       │   ├── ModelImportDialog.kt
    │       │       │   │                       │   ├── NewReleaseNotification.kt
    │       │       │   │                       │   └── SettingsDialog.kt
    │       │       │   │                       ├── icon
    │       │       │   │                       │   └── Deploy.kt
    │       │       │   │                       ├── llmchat
    │       │       │   │                       │   ├── LlmChatModelHelper.kt
    │       │       │   │                       │   ├── LlmChatScreen.kt
    │       │       │   │                       │   └── LlmChatViewModel.kt
    │       │       │   │                       ├── llmsingleturn
    │       │       │   │                       │   ├── LlmSingleTurnScreen.kt
    │       │       │   │                       │   ├── LlmSingleTurnViewModel.kt
    │       │       │   │                       │   ├── PromptTemplateConfigs.kt
    │       │       │   │                       │   ├── PromptTemplatesPanel.kt
    │       │       │   │                       │   ├── ResponsePanel.kt
    │       │       │   │                       │   ├── SingleSelectButton.kt
    │       │       │   │                       │   └── VerticalSplitView.kt
    │       │       │   │                       ├── modelmanager
    │       │       │   │                       │   ├── ModelList.kt
    │       │       │   │                       │   ├── ModelManager.kt
    │       │       │   │                       │   └── ModelManagerViewModel.kt
    │       │       │   │                       ├── navigation
    │       │       │   │                       │   └── GalleryNavGraph.kt
    │       │       │   │                       └── theme
    │       │       │   │                       │   ├── Color.kt
    │       │       │   │                       │   ├── Theme.kt
    │       │       │   │                       │   ├── ThemeSettings.kt
    │       │       │   │                       │   └── Type.kt
    │       │       │   │                   └── worker
    │       │       │   │                       └── DownloadWorker.kt
    │       │       ├── proto
    │       │       │   └── settings.proto
    │       │       └── res
    │       │       │   ├── drawable
    │       │       │       ├── chat_spark.xml
    │       │       │       ├── circle.xml
    │       │       │       ├── double_circle.xml
    │       │       │       ├── four_circle.xml
    │       │       │       ├── ic_launcher_background.xml
    │       │       │       ├── ic_launcher_foreground.xml
    │       │       │       ├── image_spark.xml
    │       │       │       ├── logo.xml
    │       │       │       ├── pantegon.xml
    │       │       │       └── text_spark.xml
    │       │       │   ├── font
    │       │       │       ├── nunito_black.ttf
    │       │       │       ├── nunito_bold.ttf
    │       │       │       ├── nunito_extrabold.ttf
    │       │       │       ├── nunito_extralight.ttf
    │       │       │       ├── nunito_light.ttf
    │       │       │       ├── nunito_medium.ttf
    │       │       │       ├── nunito_regular.ttf
    │       │       │       └── nunito_semibold.ttf
    │       │       │   ├── mipmap-anydpi-v26
    │       │       │       └── ic_launcher.xml
    │       │       │   ├── mipmap-hdpi
    │       │       │       ├── ic_launcher.png
    │       │       │       ├── ic_launcher_background.png
    │       │       │       ├── ic_launcher_foreground.png
    │       │       │       └── ic_launcher_monochrome.png
    │       │       │   ├── mipmap-mdpi
    │       │       │       ├── ic_launcher.png
    │       │       │       ├── ic_launcher_background.png
    │       │       │       ├── ic_launcher_foreground.png
    │       │       │       └── ic_launcher_monochrome.png
    │       │       │   ├── mipmap-xhdpi
    │       │       │       ├── ic_launcher.png
    │       │       │       ├── ic_launcher_background.png
    │       │       │       ├── ic_launcher_foreground.png
    │       │       │       └── ic_launcher_monochrome.png
    │       │       │   ├── mipmap-xxhdpi
    │       │       │       ├── ic_launcher.png
    │       │       │       ├── ic_launcher_background.png
    │       │       │       ├── ic_launcher_foreground.png
    │       │       │       └── ic_launcher_monochrome.png
    │       │       │   ├── mipmap-xxxhdpi
    │       │       │       ├── ic_launcher.png
    │       │       │       ├── ic_launcher_background.png
    │       │       │       ├── ic_launcher_foreground.png
    │       │       │       └── ic_launcher_monochrome.png
    │       │       │   ├── values-night
    │       │       │       └── themes.xml
    │       │       │   ├── values
    │       │       │       ├── dimens.xml
    │       │       │       ├── ic_launcher_background.xml
    │       │       │       ├── strings.xml
    │       │       │       └── themes.xml
    │       │       │   └── xml
    │       │       │       ├── backup_rules.xml
    │       │       │       ├── data_extraction_rules.xml
    │       │       │       └── file_paths.xml
    │       │   └── test
    │       │       └── java
    │       │           └── com
    │       │               └── google
    │       │                   └── ai
    │       │                       └── edge
    │       │                           └── gallery
    │       │                               └── data
    │       │                                   └── ModelAllowlistTest.kt
    │   ├── build.gradle.kts
    │   ├── gradle.properties
    │   ├── gradle
    │       ├── libs.versions.toml
    │       └── wrapper
    │       │   ├── gradle-wrapper.jar
    │       │   └── gradle-wrapper.properties
    │   ├── gradlew
    │   ├── gradlew.bat
    │   └── settings.gradle.kts
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── model_allowlist.json


/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: 🐛 Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Describe the bug:**    
11 | A clear and concise description of what the bug is.
12 | 
13 | **To Reproduce:**      
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 | 
20 | **Expected behavior:**        
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Screenshots:**    
24 | If applicable, add screenshots to help explain your problem.
25 | 
26 | **Device & App Information (Please complete the following):**
27 | - Device: [e.g., Samsung Galaxy S23, Google Pixel 7]
28 | - Android Version: [e.g., Android 12, Android 13]
29 | - App Version: [e.g., 1.0.1, v1.0.2]
30 | 
31 | **Additional context:**    
32 | Add any other context about the problem here.
33 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: ✨ Feature request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Is your feature request related to a problem? Please describe.**    
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | 
13 | **Describe the solution you'd like**       
14 | A clear and concise description of what you want to happen.
15 | 
16 | **Describe alternatives you've considered**     
17 | A clear and concise description of any alternative solutions or features you've considered.
18 | 
19 | **Additional context**     
20 | Add any other context or screenshots about the feature request here.
21 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: 🆘 Support Request
 3 | about: Ask a question or get help with usage.
 4 | title: "[Support]: "
 5 | labels: ["support", "question"]
 6 | assignees: []
 7 | ---
 8 | 
 9 | <!--
10 | Thanks for reaching out for help! To assist you efficiently, please provide as much detail as possible.
11 | -->
12 | 
13 | **What do you need help with?**
14 | 
15 | Is this a question about how to do something, a configuration problem, or a general issue you can't solve?
16 | 
17 | **Describe the issue/question:**
18 | 
19 | Clearly describe what you are trying to achieve, what problem you are facing, or what question you have.
20 | 
21 | **What have you tried so far? (Optional):**
22 | 
23 | List any steps you've already taken to troubleshoot, find information, or attempt a solution.
24 | 
25 | **Expected outcome (Optional):**
26 | 
27 | If applicable, what did you hope would happen, or what solution are you looking for?
28 | 
29 | **Screenshots/Videos (Optional):**
30 | 
31 | If applicable, add screenshots or a short video that might help explain your situation.
32 | 
33 | **Environment & Details:**
34 | 
35 | Please provide details about your operating environment, relevant URLs, or any messages you see.
36 | 
37 | - **Operating System:**
38 | - **Browser & Version (if applicable):**
39 | - **Any relevant messages (e.g., from UI, console):**
40 |     ```
41 |     PASTE_ANY_MESSAGES_HERE
42 |     ```
43 | 
44 | **Any additional context?:**
45 | 
46 | Is there anything else that might be useful for us to know?
47 | 


--------------------------------------------------------------------------------
/.github/workflows/build_android.yaml:
--------------------------------------------------------------------------------
 1 | name: Build Android APK
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |   push:
 6 |     branches: [ "main" ]
 7 |     paths:
 8 |       - 'Android/**'
 9 |   pull_request:
10 |     branches: [ "main" ]
11 |     paths:
12 |       - 'Android/**'
13 | 
14 | jobs:
15 |   build_apk:
16 |     name: Build Android APK
17 |     runs-on: ubuntu-latest
18 |     defaults:
19 |       run:
20 |         working-directory: ./Android/src
21 |     steps:
22 |       - name: Checkout the source code
23 |         uses: actions/checkout@v3
24 |       - uses: actions/setup-java@v4
25 |         with:
26 |           distribution: 'temurin'
27 |           java-version: '21'
28 |       - name: Build
29 |         run: ./gradlew assembleRelease
30 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | 


--------------------------------------------------------------------------------
/Android/.gitignore:
--------------------------------------------------------------------------------
 1 | # @license
 2 | # Copyright 2025 Google LLC
 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 | # Gradle files
18 | .gradle/
19 | build/
20 | 
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 | 
24 | # Log/OS Files
25 | *.log
26 | 
27 | # Android Studio generated files and folders
28 | captures/
29 | .externalNativeBuild/
30 | .cxx/
31 | *.apk
32 | output.json
33 | 
34 | # IntelliJ
35 | *.iml
36 | .idea/
37 | misc.xml
38 | deploymentTargetDropDown.xml
39 | render.experimental.xml
40 | 
41 | # Keystore files
42 | *.jks
43 | *.keystore
44 | 
45 | # Google Services (e.g. APIs or Firebase)
46 | google-services.json
47 | 
48 | # Android Profiling
49 | *.hprof
50 | 
51 | .DS_Store
52 | 


--------------------------------------------------------------------------------
/Android/README.md:
--------------------------------------------------------------------------------
1 | # Google AI Edge Gallery (Android)
2 | 


--------------------------------------------------------------------------------
/Android/src/.gitignore:
--------------------------------------------------------------------------------
 1 | # @license
 2 | # Copyright 2025 Google LLC
 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 | *.iml
17 | 
18 | .gradle
19 | /local.properties
20 | /.idea/caches
21 | /.idea/libraries
22 | /.idea/modules.xml
23 | /.idea/workspace.xml
24 | /.idea/navEditor.xml
25 | /.idea/assetWizardSettings.xml
26 | .DS_Store
27 | /build
28 | /captures
29 | .externalNativeBuild
30 | .cxx
31 | local.properties
32 | 


--------------------------------------------------------------------------------
/Android/src/app/.gitignore:
--------------------------------------------------------------------------------
 1 | # @license
 2 | # Copyright 2025 Google LLC
 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 | /build
18 | /release


--------------------------------------------------------------------------------
/Android/src/app/build.gradle.kts:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | plugins {
 18 |   alias(libs.plugins.android.application)
 19 |   // Note: set apply to true to enable google-services (requires google-services.json).
 20 |   alias(libs.plugins.google.services) apply false
 21 |   alias(libs.plugins.kotlin.android)
 22 |   alias(libs.plugins.kotlin.compose)
 23 |   alias(libs.plugins.kotlin.serialization)
 24 |   alias(libs.plugins.protobuf)
 25 |   alias(libs.plugins.hilt.application)
 26 |   alias(libs.plugins.oss.licenses)
 27 |   kotlin("kapt")
 28 | }
 29 | 
 30 | android {
 31 |   namespace = "com.google.ai.edge.gallery"
 32 |   compileSdk = 35
 33 | 
 34 |   defaultConfig {
 35 |     applicationId = "com.google.aiedge.gallery"
 36 |     minSdk = 31
 37 |     targetSdk = 35
 38 |     versionCode = 1
 39 |     versionName = "1.0.4"
 40 | 
 41 |     // Needed for HuggingFace auth workflows.
 42 |     // Use the scheme of the "Redirect URLs" in HuggingFace app.
 43 |     manifestPlaceholders["appAuthRedirectScheme"] =
 44 |         "REPLACE_WITH_YOUR_REDIRECT_SCHEME_IN_HUGGINGFACE_APP"
 45 | 
 46 |     testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 47 |   }
 48 | 
 49 |   buildTypes {
 50 |     release {
 51 |       isMinifyEnabled = false
 52 |       proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
 53 |       signingConfig = signingConfigs.getByName("debug")
 54 |     }
 55 |   }
 56 |   compileOptions {
 57 |     sourceCompatibility = JavaVersion.VERSION_11
 58 |     targetCompatibility = JavaVersion.VERSION_11
 59 |   }
 60 |   kotlinOptions {
 61 |     jvmTarget = "11"
 62 |     freeCompilerArgs += "-Xcontext-receivers"
 63 |   }
 64 |   buildFeatures {
 65 |     compose = true
 66 |     buildConfig = true
 67 |   }
 68 | }
 69 | 
 70 | dependencies {
 71 |   implementation(libs.androidx.core.ktx)
 72 |   implementation(libs.androidx.lifecycle.runtime.ktx)
 73 |   implementation(libs.androidx.activity.compose)
 74 |   implementation(platform(libs.androidx.compose.bom))
 75 |   implementation(libs.androidx.ui)
 76 |   implementation(libs.androidx.ui.graphics)
 77 |   implementation(libs.androidx.ui.tooling.preview)
 78 |   implementation(libs.androidx.material3)
 79 |   implementation(libs.androidx.compose.navigation)
 80 |   implementation(libs.kotlinx.serialization.json)
 81 |   implementation(libs.material.icon.extended)
 82 |   implementation(libs.androidx.work.runtime)
 83 |   implementation(libs.androidx.datastore)
 84 |   implementation(libs.com.google.code.gson)
 85 |   implementation(libs.androidx.lifecycle.process)
 86 |   implementation(libs.mediapipe.tasks.text)
 87 |   implementation(libs.mediapipe.tasks.genai)
 88 |   implementation(libs.mediapipe.tasks.imagegen)
 89 |   implementation(libs.commonmark)
 90 |   implementation(libs.richtext)
 91 |   implementation(libs.tflite)
 92 |   implementation(libs.tflite.gpu)
 93 |   implementation(libs.tflite.support)
 94 |   implementation(libs.camerax.core)
 95 |   implementation(libs.camerax.camera2)
 96 |   implementation(libs.camerax.lifecycle)
 97 |   implementation(libs.camerax.view)
 98 |   implementation(libs.openid.appauth)
 99 |   implementation(libs.androidx.splashscreen)
100 |   implementation(libs.protobuf.javalite)
101 |   implementation(libs.hilt.android)
102 |   implementation(libs.hilt.navigation.compose)
103 |   implementation(libs.play.services.oss.licenses)
104 |   implementation(platform(libs.firebase.bom))
105 |   implementation(libs.firebase.analytics)
106 |   kapt(libs.hilt.android.compiler)
107 |   testImplementation(libs.junit)
108 |   androidTestImplementation(libs.androidx.junit)
109 |   androidTestImplementation(libs.androidx.espresso.core)
110 |   androidTestImplementation(platform(libs.androidx.compose.bom))
111 |   androidTestImplementation(libs.androidx.ui.test.junit4)
112 |   androidTestImplementation(libs.hilt.android.testing)
113 |   debugImplementation(libs.androidx.ui.tooling)
114 |   debugImplementation(libs.androidx.ui.test.manifest)
115 | }
116 | 
117 | protobuf {
118 |   protoc { artifact = "com.google.protobuf:protoc:4.26.1" }
119 |   generateProtoTasks { all().forEach { it.plugins { create("java") { option("lite") } } } }
120 | }
121 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | import android.util.Log
20 | import com.google.firebase.Firebase
21 | import com.google.firebase.analytics.FirebaseAnalytics
22 | import com.google.firebase.analytics.analytics
23 | 
24 | private var hasLoggedAnalyticsWarning = false
25 | 
26 | val firebaseAnalytics: FirebaseAnalytics?
27 |   get() =
28 |     runCatching { Firebase.analytics }
29 |       .onFailure { exception ->
30 |         // Firebase.analytics can throw an exception if goolgle-services is not set up, e.g.,
31 |         // missing google-services.json.
32 |         if (!hasLoggedAnalyticsWarning) {
33 |           Log.w("AGAnalyticsFirebase", "Firebase Analytics is not available", exception)
34 |         }
35 |       }
36 |       .getOrNull()
37 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryApp.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | import androidx.compose.runtime.Composable
20 | import androidx.navigation.NavHostController
21 | import androidx.navigation.compose.rememberNavController
22 | import com.google.ai.edge.gallery.ui.navigation.GalleryNavHost
23 | 
24 | /** Top level composable representing the main screen of the application. */
25 | @Composable
26 | fun GalleryApp(navController: NavHostController = rememberNavController()) {
27 |   GalleryNavHost(navController = navController)
28 | }
29 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryApplication.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | import android.app.Application
20 | import com.google.ai.edge.gallery.common.writeLaunchInfo
21 | import com.google.ai.edge.gallery.data.DataStoreRepository
22 | import com.google.ai.edge.gallery.ui.theme.ThemeSettings
23 | import com.google.firebase.FirebaseApp
24 | import dagger.hilt.android.HiltAndroidApp
25 | import javax.inject.Inject
26 | 
27 | @HiltAndroidApp
28 | class GalleryApplication : Application() {
29 | 
30 |   @Inject lateinit var dataStoreRepository: DataStoreRepository
31 | 
32 |   override fun onCreate() {
33 |     super.onCreate()
34 | 
35 |     writeLaunchInfo(context = this)
36 | 
37 |     // Load saved theme.
38 |     ThemeSettings.themeOverride.value = dataStoreRepository.readTheme()
39 | 
40 |     FirebaseApp.initializeApp(this)
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryLifecycleProvider.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | interface AppLifecycleProvider {
20 |   var isAppInForeground: Boolean
21 | }
22 | 
23 | class GalleryLifecycleProvider : AppLifecycleProvider {
24 |   private var _isAppInForeground = false
25 | 
26 |   override var isAppInForeground: Boolean
27 |     get() = _isAppInForeground
28 |     set(value) {
29 |       _isAppInForeground = value
30 |     }
31 | }
32 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | import android.os.Build
20 | import android.os.Bundle
21 | import android.view.WindowManager
22 | import androidx.activity.ComponentActivity
23 | import androidx.activity.compose.setContent
24 | import androidx.activity.enableEdgeToEdge
25 | import androidx.compose.foundation.layout.fillMaxSize
26 | import androidx.compose.material3.Surface
27 | import androidx.compose.ui.Modifier
28 | import androidx.core.os.bundleOf
29 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
30 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme
31 | import com.google.firebase.analytics.FirebaseAnalytics
32 | import dagger.hilt.android.AndroidEntryPoint
33 | 
34 | @AndroidEntryPoint
35 | class MainActivity : ComponentActivity() {
36 | 
37 |   override fun onCreate(savedInstanceState: Bundle?) {
38 |     super.onCreate(savedInstanceState)
39 | 
40 |     installSplashScreen()
41 | 
42 |     enableEdgeToEdge()
43 |     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
44 |       // Fix for three-button nav not properly going edge-to-edge.
45 |       // See: https://issuetracker.google.com/issues/298296168
46 |       window.isNavigationBarContrastEnforced = false
47 |     }
48 |     setContent { GalleryTheme { Surface(modifier = Modifier.fillMaxSize()) { GalleryApp() } } }
49 |     // Keep the screen on while the app is running for better demo experience.
50 |     window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
51 |   }
52 | 
53 |   override fun onResume() {
54 |     super.onResume()
55 | 
56 |     firebaseAnalytics?.logEvent(
57 |       FirebaseAnalytics.Event.APP_OPEN,
58 |       bundleOf(
59 |         "app_version" to BuildConfig.VERSION_NAME,
60 |         "os_version" to Build.VERSION.SDK_INT.toString(),
61 |         "device_model" to Build.MODEL,
62 |       ),
63 |     )
64 |   }
65 | 
66 |   companion object {
67 |     private const val TAG = "AGMainActivity"
68 |   }
69 | }
70 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/SettingsSerializer.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery
18 | 
19 | import androidx.datastore.core.CorruptionException
20 | import androidx.datastore.core.Serializer
21 | import com.google.ai.edge.gallery.proto.Settings
22 | import com.google.protobuf.InvalidProtocolBufferException
23 | import java.io.InputStream
24 | import java.io.OutputStream
25 | 
26 | object SettingsSerializer : Serializer<Settings> {
27 |   override val defaultValue: Settings = Settings.getDefaultInstance()
28 | 
29 |   override suspend fun readFrom(input: InputStream): Settings {
30 |     try {
31 |       return Settings.parseFrom(input)
32 |     } catch (exception: InvalidProtocolBufferException) {
33 |       throw CorruptionException("Cannot read proto.", exception)
34 |     }
35 |   }
36 | 
37 |   override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
38 | }
39 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.common
18 | 
19 | import androidx.compose.ui.graphics.Color
20 | 
21 | interface LatencyProvider {
22 |   val latencyMs: Float
23 | }
24 | 
25 | data class Classification(val label: String, val score: Float, val color: Color)
26 | 
27 | data class JsonObjAndTextContent<T>(val jsonObj: T, val textContent: String)
28 | 
29 | class AudioClip(val audioData: ByteArray, val sampleRate: Int)
30 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/AppBarAction.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.data
18 | 
19 | /** Possible action for app bar. */
20 | enum class AppBarActionType {
21 |   NO_ACTION,
22 |   APP_SETTING,
23 |   DOWNLOAD_MANAGER,
24 |   MODEL_SELECTOR,
25 |   NAVIGATE_UP,
26 |   REFRESH_MODELS,
27 |   REFRESHING_MODELS,
28 | }
29 | 
30 | class AppBarAction(val actionType: AppBarActionType, val actionFn: () -> Unit)
31 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/AppContainer.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.data
18 | 
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import com.google.ai.edge.gallery.AppLifecycleProvider
22 | import com.google.ai.edge.gallery.GalleryLifecycleProvider
23 | import com.google.ai.edge.gallery.proto.Settings
24 | 
25 | /**
26 |  * App container for Dependency injection.
27 |  *
28 |  * This interface defines the dependencies required by the application.
29 |  */
30 | interface AppContainer {
31 |   val context: Context
32 |   val lifecycleProvider: AppLifecycleProvider
33 |   val dataStoreRepository: DataStoreRepository
34 |   val downloadRepository: DownloadRepository
35 | }
36 | 
37 | /**
38 |  * Default implementation of the AppContainer interface.
39 |  *
40 |  * This class provides concrete implementations for the application's dependencies,
41 |  */
42 | class DefaultAppContainer(ctx: Context, dataStore: DataStore<Settings>) : AppContainer {
43 |   override val context = ctx
44 |   override val lifecycleProvider = GalleryLifecycleProvider()
45 |   override val dataStoreRepository = DefaultDataStoreRepository(dataStore)
46 |   override val downloadRepository = DefaultDownloadRepository(ctx, lifecycleProvider)
47 | }
48 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ConfigValue.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.data
 18 | 
 19 | // @Serializable(with = ConfigValueSerializer::class)
 20 | sealed class ConfigValue {
 21 |   // @Serializable
 22 |   data class IntValue(val value: Int) : ConfigValue()
 23 | 
 24 |   // @Serializable
 25 |   data class FloatValue(val value: Float) : ConfigValue()
 26 | 
 27 |   // @Serializable
 28 |   data class StringValue(val value: String) : ConfigValue()
 29 | }
 30 | 
 31 | // /**
 32 | //  * Custom serializer for the ConfigValue class.
 33 | //  *
 34 | //  * This object implements the KSerializer interface to provide custom serialization and
 35 | //  * deserialization logic for the ConfigValue class. It handles different types of ConfigValue
 36 | //  * (IntValue, FloatValue, StringValue) and supports JSON format.
 37 | //  */
 38 | // object ConfigValueSerializer : KSerializer<ConfigValue> {
 39 | //   override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ConfigValue")
 40 | 
 41 | //   override fun serialize(encoder: Encoder, value: ConfigValue) {
 42 | //     when (value) {
 43 | //       is ConfigValue.IntValue -> encoder.encodeInt(value.value)
 44 | //       is ConfigValue.FloatValue -> encoder.encodeFloat(value.value)
 45 | //       is ConfigValue.StringValue -> encoder.encodeString(value.value)
 46 | //     }
 47 | //   }
 48 | 
 49 | //   override fun deserialize(decoder: Decoder): ConfigValue {
 50 | //     val input =
 51 | //       decoder as? JsonDecoder
 52 | //         ?: throw SerializationException("This serializer only works with Json")
 53 | //     return when (val element = input.decodeJsonElement()) {
 54 | //       is JsonPrimitive -> {
 55 | //         if (element.isString) {
 56 | //           ConfigValue.StringValue(element.content)
 57 | //         } else if (element.content.contains('.')) {
 58 | //           ConfigValue.FloatValue(element.content.toFloat())
 59 | //         } else {
 60 | //           ConfigValue.IntValue(element.content.toInt())
 61 | //         }
 62 | //       }
 63 | 
 64 | //       else -> throw SerializationException("Expected JsonPrimitive")
 65 | //     }
 66 | //   }
 67 | // }
 68 | 
 69 | fun getIntConfigValue(configValue: ConfigValue?, default: Int): Int {
 70 |   if (configValue == null) {
 71 |     return default
 72 |   }
 73 |   return when (configValue) {
 74 |     is ConfigValue.IntValue -> configValue.value
 75 |     is ConfigValue.FloatValue -> configValue.value.toInt()
 76 |     is ConfigValue.StringValue -> 0
 77 |   }
 78 | }
 79 | 
 80 | fun getFloatConfigValue(configValue: ConfigValue?, default: Float): Float {
 81 |   if (configValue == null) {
 82 |     return default
 83 |   }
 84 |   return when (configValue) {
 85 |     is ConfigValue.IntValue -> configValue.value.toFloat()
 86 |     is ConfigValue.FloatValue -> configValue.value
 87 |     is ConfigValue.StringValue -> 0f
 88 |   }
 89 | }
 90 | 
 91 | fun getStringConfigValue(configValue: ConfigValue?, default: String): String {
 92 |   if (configValue == null) {
 93 |     return default
 94 |   }
 95 |   return when (configValue) {
 96 |     is ConfigValue.IntValue -> "${configValue.value}"
 97 |     is ConfigValue.FloatValue -> "${configValue.value}"
 98 |     is ConfigValue.StringValue -> configValue.value
 99 |   }
100 | }
101 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Consts.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.data
18 | 
19 | // Keys used to send/receive data to Work.
20 | const val KEY_MODEL_URL = "KEY_MODEL_URL"
21 | const val KEY_MODEL_NAME = "KEY_MODEL_NAME"
22 | const val KEY_MODEL_VERSION = "KEY_MODEL_VERSION"
23 | const val KEY_MODEL_DOWNLOAD_MODEL_DIR = "KEY_MODEL_DOWNLOAD_MODEL_DIR"
24 | const val KEY_MODEL_DOWNLOAD_FILE_NAME = "KEY_MODEL_DOWNLOAD_FILE_NAME"
25 | const val KEY_MODEL_TOTAL_BYTES = "KEY_MODEL_TOTAL_BYTES"
26 | const val KEY_MODEL_DOWNLOAD_RECEIVED_BYTES = "KEY_MODEL_DOWNLOAD_RECEIVED_BYTES"
27 | const val KEY_MODEL_DOWNLOAD_RATE = "KEY_MODEL_DOWNLOAD_RATE"
28 | const val KEY_MODEL_DOWNLOAD_REMAINING_MS = "KEY_MODEL_DOWNLOAD_REMAINING_SECONDS"
29 | const val KEY_MODEL_DOWNLOAD_ERROR_MESSAGE = "KEY_MODEL_DOWNLOAD_ERROR_MESSAGE"
30 | const val KEY_MODEL_DOWNLOAD_ACCESS_TOKEN = "KEY_MODEL_DOWNLOAD_ACCESS_TOKEN"
31 | const val KEY_MODEL_DOWNLOAD_APP_TS = "KEY_MODEL_DOWNLOAD_APP_TS"
32 | const val KEY_MODEL_EXTRA_DATA_URLS = "KEY_MODEL_EXTRA_DATA_URLS"
33 | const val KEY_MODEL_EXTRA_DATA_DOWNLOAD_FILE_NAMES = "KEY_MODEL_EXTRA_DATA_DOWNLOAD_FILE_NAMES"
34 | const val KEY_MODEL_IS_ZIP = "KEY_MODEL_IS_ZIP"
35 | const val KEY_MODEL_UNZIPPED_DIR = "KEY_MODEL_UNZIPPED_DIR"
36 | const val KEY_MODEL_START_UNZIPPING = "KEY_MODEL_START_UNZIPPING"
37 | 
38 | // Default values for LLM models.
39 | const val DEFAULT_MAX_TOKEN = 1024
40 | const val DEFAULT_TOPK = 40
41 | const val DEFAULT_TOPP = 0.9f
42 | const val DEFAULT_TEMPERATURE = 1.0f
43 | val DEFAULT_ACCELERATORS = listOf(Accelerator.GPU)
44 | 
45 | // Max number of images allowed in a "ask image" session.
46 | const val MAX_IMAGE_COUNT = 10
47 | 
48 | // Max number of audio clip in an "ask audio" session.
49 | const val MAX_AUDIO_CLIP_COUNT = 1
50 | 
51 | // Max audio clip duration in seconds.
52 | const val MAX_AUDIO_CLIP_DURATION_SEC = 30
53 | 
54 | // Audio-recording related consts.
55 | const val SAMPLE_RATE = 16000
56 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/DataStoreRepository.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.data
 18 | 
 19 | import androidx.datastore.core.DataStore
 20 | import com.google.ai.edge.gallery.proto.AccessTokenData
 21 | import com.google.ai.edge.gallery.proto.ImportedModel
 22 | import com.google.ai.edge.gallery.proto.Settings
 23 | import com.google.ai.edge.gallery.proto.Theme
 24 | import kotlinx.coroutines.flow.first
 25 | import kotlinx.coroutines.runBlocking
 26 | 
 27 | // TODO(b/423700720): Change to async (suspend) functions
 28 | interface DataStoreRepository {
 29 |   fun saveTextInputHistory(history: List<String>)
 30 | 
 31 |   fun readTextInputHistory(): List<String>
 32 | 
 33 |   fun saveTheme(theme: Theme)
 34 | 
 35 |   fun readTheme(): Theme
 36 | 
 37 |   fun saveAccessTokenData(accessToken: String, refreshToken: String, expiresAt: Long)
 38 | 
 39 |   fun clearAccessTokenData()
 40 | 
 41 |   fun readAccessTokenData(): AccessTokenData?
 42 | 
 43 |   fun saveImportedModels(importedModels: List<ImportedModel>)
 44 | 
 45 |   fun readImportedModels(): List<ImportedModel>
 46 | 
 47 |   fun isTosAccepted(): Boolean
 48 | 
 49 |   fun acceptTos()
 50 | }
 51 | 
 52 | /** Repository for managing data using Proto DataStore. */
 53 | class DefaultDataStoreRepository(private val dataStore: DataStore<Settings>) : DataStoreRepository {
 54 |   override fun saveTextInputHistory(history: List<String>) {
 55 |     runBlocking {
 56 |       dataStore.updateData { settings ->
 57 |         settings.toBuilder().clearTextInputHistory().addAllTextInputHistory(history).build()
 58 |       }
 59 |     }
 60 |   }
 61 | 
 62 |   override fun readTextInputHistory(): List<String> {
 63 |     return runBlocking {
 64 |       val settings = dataStore.data.first()
 65 |       settings.textInputHistoryList
 66 |     }
 67 |   }
 68 | 
 69 |   override fun saveTheme(theme: Theme) {
 70 |     runBlocking {
 71 |       dataStore.updateData { settings -> settings.toBuilder().setTheme(theme).build() }
 72 |     }
 73 |   }
 74 | 
 75 |   override fun readTheme(): Theme {
 76 |     return runBlocking {
 77 |       val settings = dataStore.data.first()
 78 |       val curTheme = settings.theme
 79 |       // Use "auto" as the default theme.
 80 |       if (curTheme == Theme.THEME_UNSPECIFIED) Theme.THEME_AUTO else curTheme
 81 |     }
 82 |   }
 83 | 
 84 |   override fun saveAccessTokenData(accessToken: String, refreshToken: String, expiresAt: Long) {
 85 |     runBlocking {
 86 |       dataStore.updateData { settings ->
 87 |         settings
 88 |           .toBuilder()
 89 |           .setAccessTokenData(
 90 |             AccessTokenData.newBuilder()
 91 |               .setAccessToken(accessToken)
 92 |               .setRefreshToken(refreshToken)
 93 |               .setExpiresAtMs(expiresAt)
 94 |               .build()
 95 |           )
 96 |           .build()
 97 |       }
 98 |     }
 99 |   }
100 | 
101 |   override fun clearAccessTokenData() {
102 |     runBlocking {
103 |       dataStore.updateData { settings -> settings.toBuilder().clearAccessTokenData().build() }
104 |     }
105 |   }
106 | 
107 |   override fun readAccessTokenData(): AccessTokenData? {
108 |     return runBlocking {
109 |       val settings = dataStore.data.first()
110 |       settings.accessTokenData
111 |     }
112 |   }
113 | 
114 |   override fun saveImportedModels(importedModels: List<ImportedModel>) {
115 |     runBlocking {
116 |       dataStore.updateData { settings ->
117 |         settings.toBuilder().clearImportedModel().addAllImportedModel(importedModels).build()
118 |       }
119 |     }
120 |   }
121 | 
122 |   override fun readImportedModels(): List<ImportedModel> {
123 |     return runBlocking {
124 |       val settings = dataStore.data.first()
125 |       settings.importedModelList
126 |     }
127 |   }
128 | 
129 |   override fun isTosAccepted(): Boolean {
130 |     return runBlocking {
131 |       val settings = dataStore.data.first()
132 |       settings.isTosAccepted
133 |     }
134 |   }
135 | 
136 |   override fun acceptTos() {
137 |     runBlocking {
138 |       dataStore.updateData { settings -> settings.toBuilder().setIsTosAccepted(true).build() }
139 |     }
140 |   }
141 | }
142 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ModelAllowlist.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.data
 18 | 
 19 | import com.google.gson.annotations.SerializedName
 20 | 
 21 | data class DefaultConfig(
 22 |   @SerializedName("topK") val topK: Int?,
 23 |   @SerializedName("topP") val topP: Float?,
 24 |   @SerializedName("temperature") val temperature: Float?,
 25 |   @SerializedName("accelerators") val accelerators: String?,
 26 |   @SerializedName("maxTokens") val maxTokens: Int?,
 27 | )
 28 | 
 29 | /** A model in the model allowlist. */
 30 | data class AllowedModel(
 31 |   val name: String,
 32 |   val modelId: String,
 33 |   val modelFile: String,
 34 |   val description: String,
 35 |   val sizeInBytes: Long,
 36 |   val version: String,
 37 |   val defaultConfig: DefaultConfig,
 38 |   val taskTypes: List<String>,
 39 |   val disabled: Boolean? = null,
 40 |   val llmSupportImage: Boolean? = null,
 41 |   val llmSupportAudio: Boolean? = null,
 42 |   val estimatedPeakMemoryInBytes: Long? = null,
 43 | ) {
 44 |   fun toModel(): Model {
 45 |     // Construct HF download url.
 46 |     val downloadUrl = "https://huggingface.co/$modelId/resolve/main/$modelFile?download=true"
 47 | 
 48 |     // Config.
 49 |     val isLlmModel =
 50 |       taskTypes.contains(TASK_LLM_CHAT.type.id) || taskTypes.contains(TASK_LLM_PROMPT_LAB.type.id)
 51 |     var configs: List<Config> = listOf()
 52 |     if (isLlmModel) {
 53 |       val defaultTopK: Int = defaultConfig.topK ?: DEFAULT_TOPK
 54 |       val defaultTopP: Float = defaultConfig.topP ?: DEFAULT_TOPP
 55 |       val defaultTemperature: Float = defaultConfig.temperature ?: DEFAULT_TEMPERATURE
 56 |       val defaultMaxToken = defaultConfig.maxTokens ?: 1024
 57 |       var accelerators: List<Accelerator> = DEFAULT_ACCELERATORS
 58 |       if (defaultConfig.accelerators != null) {
 59 |         val items = defaultConfig.accelerators.split(",")
 60 |         accelerators = mutableListOf()
 61 |         for (item in items) {
 62 |           if (item == "cpu") {
 63 |             accelerators.add(Accelerator.CPU)
 64 |           } else if (item == "gpu") {
 65 |             accelerators.add(Accelerator.GPU)
 66 |           }
 67 |         }
 68 |       }
 69 |       configs =
 70 |         createLlmChatConfigs(
 71 |           defaultTopK = defaultTopK,
 72 |           defaultTopP = defaultTopP,
 73 |           defaultTemperature = defaultTemperature,
 74 |           defaultMaxToken = defaultMaxToken,
 75 |           accelerators = accelerators,
 76 |         )
 77 |     }
 78 | 
 79 |     // Misc.
 80 |     var showBenchmarkButton = true
 81 |     var showRunAgainButton = true
 82 |     if (isLlmModel) {
 83 |       showBenchmarkButton = false
 84 |       showRunAgainButton = false
 85 |     }
 86 | 
 87 |     return Model(
 88 |       name = name,
 89 |       version = version,
 90 |       info = description,
 91 |       url = downloadUrl,
 92 |       sizeInBytes = sizeInBytes,
 93 |       estimatedPeakMemoryInBytes = estimatedPeakMemoryInBytes,
 94 |       configs = configs,
 95 |       downloadFileName = modelFile,
 96 |       showBenchmarkButton = showBenchmarkButton,
 97 |       showRunAgainButton = showRunAgainButton,
 98 |       learnMoreUrl = "https://huggingface.co/${modelId}",
 99 |       llmSupportImage = llmSupportImage == true,
100 |       llmSupportAudio = llmSupportAudio == true,
101 |     )
102 |   }
103 | 
104 |   override fun toString(): String {
105 |     return "$modelId/$modelFile"
106 |   }
107 | }
108 | 
109 | /** The model allowlist. */
110 | data class ModelAllowlist(val models: List<AllowedModel>)
111 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Types.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.data
18 | 
19 | enum class Accelerator(val label: String) {
20 |   CPU(label = "CPU"),
21 |   GPU(label = "GPU"),
22 | }
23 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/di/AppModule.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.di
18 | 
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import androidx.datastore.core.DataStoreFactory
22 | import androidx.datastore.core.Serializer
23 | import androidx.datastore.dataStoreFile
24 | import com.google.ai.edge.gallery.AppLifecycleProvider
25 | import com.google.ai.edge.gallery.GalleryLifecycleProvider
26 | import com.google.ai.edge.gallery.SettingsSerializer
27 | import com.google.ai.edge.gallery.data.DataStoreRepository
28 | import com.google.ai.edge.gallery.data.DefaultDataStoreRepository
29 | import com.google.ai.edge.gallery.data.DefaultDownloadRepository
30 | import com.google.ai.edge.gallery.data.DownloadRepository
31 | import com.google.ai.edge.gallery.proto.Settings
32 | import dagger.Module
33 | import dagger.Provides
34 | import dagger.hilt.InstallIn
35 | import dagger.hilt.android.qualifiers.ApplicationContext
36 | import dagger.hilt.components.SingletonComponent
37 | import javax.inject.Singleton
38 | 
39 | @Module
40 | @InstallIn(SingletonComponent::class)
41 | internal object AppModule {
42 | 
43 |   // Provides the SettingsSerializer
44 |   @Provides
45 |   @Singleton
46 |   fun provideSettingsSerializer(): Serializer<Settings> {
47 |     return SettingsSerializer
48 |   }
49 | 
50 |   // Provides DataStore<Settings>
51 |   @Provides
52 |   @Singleton
53 |   fun provideSettingsDataStore(
54 |     @ApplicationContext context: Context,
55 |     settingsSerializer: Serializer<Settings>,
56 |   ): DataStore<Settings> {
57 |     return DataStoreFactory.create(
58 |       serializer = settingsSerializer,
59 |       produceFile = { context.dataStoreFile("settings.pb") },
60 |     )
61 |   }
62 | 
63 |   // Provides AppLifecycleProvider
64 |   @Provides
65 |   @Singleton
66 |   fun provideAppLifecycleProvider(): AppLifecycleProvider {
67 |     return GalleryLifecycleProvider()
68 |   }
69 | 
70 |   // Provides DataStoreRepository
71 |   @Provides
72 |   @Singleton
73 |   fun provideDataStoreRepository(dataStore: DataStore<Settings>): DataStoreRepository {
74 |     return DefaultDataStoreRepository(dataStore)
75 |   }
76 | 
77 |   // Provides DownloadRepository
78 |   @Provides
79 |   @Singleton
80 |   fun provideDownloadRepository(
81 |     @ApplicationContext context: Context,
82 |     lifecycleProvider: AppLifecycleProvider,
83 |   ): DownloadRepository {
84 |     return DefaultDownloadRepository(context, lifecycleProvider)
85 |   }
86 | }
87 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/AuthConfig.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common
18 | 
19 | import androidx.core.net.toUri
20 | import net.openid.appauth.AuthorizationServiceConfiguration
21 | 
22 | object AuthConfig {
23 |   // Hugging Face Client ID.
24 |   const val clientId = "REPLACE_WITH_YOUR_CLIENT_ID_IN_HUGGINGFACE_APP"
25 | 
26 |   // Registered redirect URI.
27 |   //
28 |   // The scheme needs to match the
29 |   // "android.defaultConfig.manifestPlaceholders["appAuthRedirectScheme"]" field in
30 |   // "build.gradle.kts".
31 |   const val redirectUri = "REPLACE_WITH_YOUR_REDIRECT_URI_IN_HUGGINGFACE_APP"
32 | 
33 |   // OAuth 2.0 Endpoints (Authorization + Token Exchange)
34 |   private const val authEndpoint = "https://huggingface.co/oauth/authorize"
35 |   private const val tokenEndpoint = "https://huggingface.co/oauth/token"
36 | 
37 |   // OAuth service configuration (AppAuth library requires this)
38 |   val authServiceConfig =
39 |     AuthorizationServiceConfiguration(
40 |       authEndpoint.toUri(), // Authorization endpoint
41 |       tokenEndpoint.toUri(), // Token exchange endpoint
42 |     )
43 | }
44 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common
18 | 
19 | import androidx.compose.foundation.clickable
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.padding
23 | import androidx.compose.foundation.layout.size
24 | import androidx.compose.material3.Icon
25 | import androidx.compose.material3.MaterialTheme
26 | import androidx.compose.material3.Text
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.graphics.vector.ImageVector
31 | import androidx.compose.ui.platform.LocalUriHandler
32 | import androidx.compose.ui.text.AnnotatedString
33 | import androidx.compose.ui.text.SpanStyle
34 | import androidx.compose.ui.text.style.TextAlign
35 | import androidx.compose.ui.text.style.TextDecoration
36 | import androidx.compose.ui.unit.dp
37 | import androidx.core.os.bundleOf
38 | import com.google.ai.edge.gallery.firebaseAnalytics
39 | import com.google.ai.edge.gallery.ui.theme.customColors
40 | 
41 | @Composable
42 | fun ClickableLink(url: String, linkText: String, icon: ImageVector) {
43 |   val uriHandler = LocalUriHandler.current
44 |   val annotatedText =
45 |     AnnotatedString(
46 |       text = linkText,
47 |       spanStyles =
48 |         listOf(
49 |           AnnotatedString.Range(
50 |             item =
51 |               SpanStyle(
52 |                 color = MaterialTheme.customColors.linkColor,
53 |                 textDecoration = TextDecoration.Underline,
54 |               ),
55 |             start = 0,
56 |             end = linkText.length,
57 |           )
58 |         ),
59 |     )
60 | 
61 |   Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
62 |     Icon(icon, contentDescription = "", modifier = Modifier.size(16.dp))
63 |     Text(
64 |       text = annotatedText,
65 |       textAlign = TextAlign.Center,
66 |       style = MaterialTheme.typography.bodyLarge,
67 |       modifier =
68 |         Modifier.padding(start = 6.dp).clickable {
69 |           uriHandler.openUri(url)
70 |           firebaseAnalytics?.logEvent("resource_link_click", bundleOf("link_destination" to url))
71 |         },
72 |     )
73 |   }
74 | }
75 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ColorUtils.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common
18 | 
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.graphics.Color
22 | import com.google.ai.edge.gallery.data.Task
23 | import com.google.ai.edge.gallery.ui.theme.customColors
24 | 
25 | @Composable
26 | fun getTaskBgColor(task: Task): Color {
27 |   val colorIndex: Int = task.index % MaterialTheme.customColors.taskBgColors.size
28 |   return MaterialTheme.customColors.taskBgColors[colorIndex]
29 | }
30 | 
31 | @Composable
32 | fun getTaskIconColor(task: Task): Color {
33 |   val colorIndex: Int = task.index % MaterialTheme.customColors.taskIconColors.size
34 |   return MaterialTheme.customColors.taskIconColors[colorIndex]
35 | }
36 | 
37 | @Composable
38 | fun getTaskIconColor(index: Int): Color {
39 |   val colorIndex: Int = index % MaterialTheme.customColors.taskIconColors.size
40 |   return MaterialTheme.customColors.taskIconColors[colorIndex]
41 | }
42 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common
18 | 
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.fillMaxWidth
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.foundation.shape.RoundedCornerShape
25 | import androidx.compose.material3.Button
26 | import androidx.compose.material3.Card
27 | import androidx.compose.material3.MaterialTheme
28 | import androidx.compose.material3.Text
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.unit.dp
32 | import androidx.compose.ui.window.Dialog
33 | 
34 | @Composable
35 | fun ErrorDialog(error: String, onDismiss: () -> Unit) {
36 |   Dialog(onDismissRequest = onDismiss) {
37 |     Card(modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp)) {
38 |       Column(
39 |         modifier = Modifier.padding(20.dp),
40 |         verticalArrangement = Arrangement.spacedBy(16.dp),
41 |       ) {
42 |         // Title
43 |         Text(
44 |           "Error",
45 |           style = MaterialTheme.typography.titleLarge,
46 |           modifier = Modifier.padding(bottom = 8.dp),
47 |         )
48 | 
49 |         // Error
50 |         Text(
51 |           error,
52 |           style = MaterialTheme.typography.bodySmall,
53 |           color = MaterialTheme.colorScheme.error,
54 |         )
55 | 
56 |         Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
57 |           Button(onClick = onDismiss) { Text("Close") }
58 |         }
59 |       }
60 |     }
61 |   }
62 | }
63 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/MarkdownText.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common
18 | 
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.ProvideTextStyle
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.CompositionLocalProvider
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.text.SpanStyle
25 | import androidx.compose.ui.text.TextLinkStyles
26 | import androidx.compose.ui.text.TextStyle
27 | import androidx.compose.ui.text.font.FontFamily
28 | import com.google.ai.edge.gallery.ui.theme.customColors
29 | import com.halilibo.richtext.commonmark.Markdown
30 | import com.halilibo.richtext.ui.CodeBlockStyle
31 | import com.halilibo.richtext.ui.RichTextStyle
32 | import com.halilibo.richtext.ui.material3.RichText
33 | import com.halilibo.richtext.ui.string.RichTextStringStyle
34 | 
35 | /** Composable function to display Markdown-formatted text. */
36 | @Composable
37 | fun MarkdownText(text: String, modifier: Modifier = Modifier, smallFontSize: Boolean = false) {
38 |   val fontSize =
39 |     if (smallFontSize) MaterialTheme.typography.bodyMedium.fontSize
40 |     else MaterialTheme.typography.bodyLarge.fontSize
41 |   CompositionLocalProvider {
42 |     ProvideTextStyle(value = TextStyle(fontSize = fontSize, lineHeight = fontSize * 1.3)) {
43 |       RichText(
44 |         modifier = modifier,
45 |         style =
46 |           RichTextStyle(
47 |             codeBlockStyle =
48 |               CodeBlockStyle(
49 |                 textStyle =
50 |                   TextStyle(
51 |                     fontSize = MaterialTheme.typography.bodySmall.fontSize,
52 |                     fontFamily = FontFamily.Monospace,
53 |                   )
54 |               ),
55 |             stringStyle =
56 |               RichTextStringStyle(
57 |                 linkStyle =
58 |                   TextLinkStyles(style = SpanStyle(color = MaterialTheme.customColors.linkColor))
59 |               ),
60 |           ),
61 |       ) {
62 |         Markdown(content = text)
63 |       }
64 |     }
65 |   }
66 | }
67 | 
68 | // @Preview(showBackground = true)
69 | // @Composable
70 | // fun MarkdownTextPreview() {
71 | //   GalleryTheme {
72 | //     MarkdownText(text = "*Hello World*\n**Good morning!!**")
73 | //   }
74 | // }
75 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/TaskIcon.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common
 18 | 
 19 | import androidx.compose.foundation.Image
 20 | import androidx.compose.foundation.background
 21 | import androidx.compose.foundation.layout.Box
 22 | import androidx.compose.foundation.layout.Column
 23 | import androidx.compose.foundation.layout.aspectRatio
 24 | import androidx.compose.foundation.layout.fillMaxSize
 25 | import androidx.compose.foundation.layout.size
 26 | import androidx.compose.foundation.layout.width
 27 | import androidx.compose.material3.Icon
 28 | import androidx.compose.material3.MaterialTheme
 29 | import androidx.compose.runtime.Composable
 30 | import androidx.compose.ui.Alignment
 31 | import androidx.compose.ui.Modifier
 32 | import androidx.compose.ui.draw.alpha
 33 | import androidx.compose.ui.graphics.BlendMode
 34 | import androidx.compose.ui.graphics.Color
 35 | import androidx.compose.ui.graphics.ColorFilter
 36 | import androidx.compose.ui.graphics.painter.Painter
 37 | import androidx.compose.ui.graphics.vector.ImageVector
 38 | import androidx.compose.ui.layout.ContentScale
 39 | import androidx.compose.ui.res.painterResource
 40 | import androidx.compose.ui.res.vectorResource
 41 | import androidx.compose.ui.tooling.preview.Preview
 42 | import androidx.compose.ui.unit.Dp
 43 | import androidx.compose.ui.unit.dp
 44 | import com.google.ai.edge.gallery.R
 45 | import com.google.ai.edge.gallery.data.TASKS
 46 | import com.google.ai.edge.gallery.data.TASK_LLM_CHAT
 47 | import com.google.ai.edge.gallery.data.Task
 48 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 49 | import com.google.ai.edge.gallery.ui.theme.customColors
 50 | 
 51 | private val SHAPES: List<Int> =
 52 |   listOf(R.drawable.pantegon, R.drawable.double_circle, R.drawable.circle, R.drawable.four_circle)
 53 | 
 54 | /**
 55 |  * Composable that displays an icon representing a task. It consists of a background image and a
 56 |  * foreground icon, both centered within a square box.
 57 |  */
 58 | @Composable
 59 | fun TaskIcon(task: Task, modifier: Modifier = Modifier, width: Dp = 56.dp) {
 60 |   Box(modifier = modifier.width(width).aspectRatio(1f), contentAlignment = Alignment.Center) {
 61 |     Image(
 62 |       painter = getTaskIconBgShape(task = task),
 63 |       contentDescription = "",
 64 |       modifier = Modifier.fillMaxSize().alpha(0.6f),
 65 |       contentScale = ContentScale.Fit,
 66 |       colorFilter =
 67 |         ColorFilter.tint(
 68 |           MaterialTheme.customColors.taskIconShapeBgColor,
 69 |           blendMode = BlendMode.SrcIn,
 70 |         ),
 71 |     )
 72 |     Icon(
 73 |       task.icon ?: ImageVector.vectorResource(task.iconVectorResourceId!!),
 74 |       tint = getTaskIconColor(task = task),
 75 |       modifier = Modifier.size(width * 0.6f),
 76 |       contentDescription = "",
 77 |     )
 78 |   }
 79 | }
 80 | 
 81 | @Composable
 82 | private fun getTaskIconBgShape(task: Task): Painter {
 83 |   val colorIndex: Int = task.index % SHAPES.size
 84 |   return painterResource(SHAPES[colorIndex])
 85 | }
 86 | 
 87 | @Preview(showBackground = true)
 88 | @Composable
 89 | fun TaskIconPreview() {
 90 |   for ((index, task) in TASKS.withIndex()) {
 91 |     task.index = index
 92 |   }
 93 | 
 94 |   GalleryTheme {
 95 |     Column(modifier = Modifier.background(Color.Gray)) {
 96 |       TaskIcon(task = TASK_LLM_CHAT, width = 80.dp)
 97 |     }
 98 |   }
 99 | }
100 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/Utils.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common
 18 | 
 19 | import android.Manifest
 20 | import android.content.Context
 21 | import android.content.pm.PackageManager
 22 | import android.net.Uri
 23 | import android.os.Build
 24 | import androidx.activity.compose.ManagedActivityResultLauncher
 25 | import androidx.compose.ui.graphics.Color
 26 | import androidx.core.content.ContextCompat
 27 | import androidx.core.content.FileProvider
 28 | import com.google.ai.edge.gallery.data.Model
 29 | import com.google.ai.edge.gallery.data.Task
 30 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel
 31 | import java.io.File
 32 | import kotlin.math.ln
 33 | import kotlin.math.pow
 34 | 
 35 | private const val TAG = "AGUtils"
 36 | 
 37 | /** Format the bytes into a human-readable format. */
 38 | fun Long.humanReadableSize(si: Boolean = true, extraDecimalForGbAndAbove: Boolean = false): String {
 39 |   val bytes = this
 40 | 
 41 |   val unit = if (si) 1000 else 1024
 42 |   if (bytes < unit) return "$bytes B"
 43 |   val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
 44 |   val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
 45 |   var formatString = "%.1f %sB"
 46 |   if (extraDecimalForGbAndAbove && pre.lowercase() != "k" && pre != "M") {
 47 |     formatString = "%.2f %sB"
 48 |   }
 49 |   return formatString.format(bytes / unit.toDouble().pow(exp.toDouble()), pre)
 50 | }
 51 | 
 52 | fun Float.humanReadableDuration(): String {
 53 |   val milliseconds = this
 54 |   if (milliseconds < 1000) {
 55 |     return "$milliseconds ms"
 56 |   }
 57 |   val seconds = milliseconds / 1000f
 58 |   if (seconds < 60) {
 59 |     return "%.1f s".format(seconds)
 60 |   }
 61 | 
 62 |   val minutes = seconds / 60f
 63 |   if (minutes < 60) {
 64 |     return "%.1f min".format(minutes)
 65 |   }
 66 | 
 67 |   val hours = minutes / 60f
 68 |   return "%.1f h".format(hours)
 69 | }
 70 | 
 71 | fun Long.formatToHourMinSecond(): String {
 72 |   val ms = this
 73 |   if (ms < 0) {
 74 |     return "-"
 75 |   }
 76 | 
 77 |   val seconds = ms / 1000
 78 |   val hours = seconds / 3600
 79 |   val minutes = (seconds % 3600) / 60
 80 |   val remainingSeconds = seconds % 60
 81 | 
 82 |   val parts = mutableListOf<String>()
 83 | 
 84 |   if (hours > 0) {
 85 |     parts.add("$hours h")
 86 |   }
 87 |   if (minutes > 0) {
 88 |     parts.add("$minutes min")
 89 |   }
 90 |   if (remainingSeconds > 0 || (hours == 0L && minutes == 0L)) {
 91 |     parts.add("$remainingSeconds sec")
 92 |   }
 93 | 
 94 |   return parts.joinToString(" ")
 95 | }
 96 | 
 97 | fun getDistinctiveColor(index: Int): Color {
 98 |   val colors =
 99 |     listOf(
100 |       //      Color(0xffe6194b),
101 |       Color(0xff3cb44b),
102 |       Color(0xffffe119),
103 |       Color(0xff4363d8),
104 |       Color(0xfff58231),
105 |       Color(0xff911eb4),
106 |       Color(0xff46f0f0),
107 |       Color(0xfff032e6),
108 |       Color(0xffbcf60c),
109 |       Color(0xfffabebe),
110 |       Color(0xff008080),
111 |       Color(0xffe6beff),
112 |       Color(0xff9a6324),
113 |       Color(0xfffffac8),
114 |       Color(0xff800000),
115 |       Color(0xffaaffc3),
116 |       Color(0xff808000),
117 |       Color(0xffffd8b1),
118 |       Color(0xff000075),
119 |     )
120 |   return colors[index % colors.size]
121 | }
122 | 
123 | fun Context.createTempPictureUri(
124 |   fileName: String = "picture_${System.currentTimeMillis()}",
125 |   fileExtension: String = ".png",
126 | ): Uri {
127 |   val tempFile = File.createTempFile(fileName, fileExtension, cacheDir).apply { createNewFile() }
128 | 
129 |   return FileProvider.getUriForFile(
130 |     applicationContext,
131 |     "com.google.aiedge.gallery.provider" /* {applicationId}.provider */,
132 |     tempFile,
133 |   )
134 | }
135 | 
136 | fun checkNotificationPermissionAndStartDownload(
137 |   context: Context,
138 |   launcher: ManagedActivityResultLauncher<String, Boolean>,
139 |   modelManagerViewModel: ModelManagerViewModel,
140 |   task: Task,
141 |   model: Model,
142 | ) {
143 |   // Check permission
144 |   when (PackageManager.PERMISSION_GRANTED) {
145 |     // Already got permission. Call the lambda.
146 |     ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) -> {
147 |       modelManagerViewModel.downloadModel(task = task, model = model)
148 |     }
149 | 
150 |     // Otherwise, ask for permission
151 |     else -> {
152 |       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
153 |         launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
154 |       }
155 |     }
156 |   }
157 | }
158 | 
159 | fun ensureValidFileName(fileName: String): String {
160 |   return fileName.replace(Regex("[^a-zA-Z0-9._-]"), "_")
161 | }
162 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/BenchmarkConfigDialog.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.chat
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.runtime.Composable
 22 | import com.google.ai.edge.gallery.data.Config
 23 | import com.google.ai.edge.gallery.data.ConfigKey
 24 | import com.google.ai.edge.gallery.data.NumberSliderConfig
 25 | import com.google.ai.edge.gallery.data.ValueType
 26 | import com.google.ai.edge.gallery.data.convertValueToTargetType
 27 | import com.google.ai.edge.gallery.ui.common.ConfigDialog
 28 | 
 29 | private const val DEFAULT_BENCHMARK_WARM_UP_ITERATIONS = 50f
 30 | private const val DEFAULT_BENCHMARK_ITERATIONS = 200f
 31 | 
 32 | private val BENCHMARK_CONFIGS: List<Config> =
 33 |   listOf(
 34 |     NumberSliderConfig(
 35 |       key = ConfigKey.WARM_UP_ITERATIONS,
 36 |       sliderMin = 10f,
 37 |       sliderMax = 200f,
 38 |       defaultValue = DEFAULT_BENCHMARK_WARM_UP_ITERATIONS,
 39 |       valueType = ValueType.INT,
 40 |     ),
 41 |     NumberSliderConfig(
 42 |       key = ConfigKey.BENCHMARK_ITERATIONS,
 43 |       sliderMin = 50f,
 44 |       sliderMax = 500f,
 45 |       defaultValue = DEFAULT_BENCHMARK_ITERATIONS,
 46 |       valueType = ValueType.INT,
 47 |     ),
 48 |   )
 49 | 
 50 | private val BENCHMARK_CONFIGS_INITIAL_VALUES =
 51 |   mapOf(
 52 |     ConfigKey.WARM_UP_ITERATIONS.label to DEFAULT_BENCHMARK_WARM_UP_ITERATIONS,
 53 |     ConfigKey.BENCHMARK_ITERATIONS.label to DEFAULT_BENCHMARK_ITERATIONS,
 54 |   )
 55 | 
 56 | /**
 57 |  * Composable function to display a configuration dialog for benchmarking a chat message.
 58 |  *
 59 |  * This function renders a configuration dialog specifically tailored for setting up benchmark
 60 |  * parameters. It allows users to specify warm-up and benchmark iterations before running a
 61 |  * benchmark test on a given chat message.
 62 |  */
 63 | @Composable
 64 | fun BenchmarkConfigDialog(
 65 |   onDismissed: () -> Unit,
 66 |   messageToBenchmark: ChatMessage?,
 67 |   onBenchmarkClicked: (ChatMessage, warmUpIterations: Int, benchmarkIterations: Int) -> Unit,
 68 | ) {
 69 |   ConfigDialog(
 70 |     title = "Benchmark configs",
 71 |     okBtnLabel = "Start",
 72 |     configs = BENCHMARK_CONFIGS,
 73 |     initialValues = BENCHMARK_CONFIGS_INITIAL_VALUES,
 74 |     onDismissed = onDismissed,
 75 |     onOk = { curConfigValues ->
 76 |       // Hide config dialog.
 77 |       onDismissed()
 78 | 
 79 |       // Start benchmark.
 80 |       messageToBenchmark?.let { message ->
 81 |         val warmUpIterations =
 82 |           convertValueToTargetType(
 83 |             value = curConfigValues.getValue(ConfigKey.WARM_UP_ITERATIONS.label),
 84 |             valueType = ValueType.INT,
 85 |           )
 86 |             as Int
 87 |         val benchmarkIterations =
 88 |           convertValueToTargetType(
 89 |             value = curConfigValues.getValue(ConfigKey.BENCHMARK_ITERATIONS.label),
 90 |             valueType = ValueType.INT,
 91 |           )
 92 |             as Int
 93 |         onBenchmarkClicked(message, warmUpIterations, benchmarkIterations)
 94 |       }
 95 |     },
 96 |   )
 97 | }
 98 | 
 99 | // @Preview(showBackground = true)
100 | // @Composable
101 | // fun BenchmarkConfigDialogPreview() {
102 | //   GalleryTheme {
103 | //     BenchmarkConfigDialog(
104 | //       onDismissed = {},
105 | //       messageToBenchmark = null,
106 | //       onBenchmarkClicked = { _, _, _ -> },
107 | //     )
108 | //   }
109 | // }
110 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/DataCard.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.offset
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.material3.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.draw.alpha
29 | import androidx.compose.ui.tooling.preview.Preview
30 | import androidx.compose.ui.unit.dp
31 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme
32 | import com.google.ai.edge.gallery.ui.theme.bodySmallMediumNarrow
33 | import com.google.ai.edge.gallery.ui.theme.bodySmallMediumNarrowBold
34 | import com.google.ai.edge.gallery.ui.theme.labelSmallNarrow
35 | import com.google.ai.edge.gallery.ui.theme.labelSmallNarrowMedium
36 | 
37 | /**
38 |  * Composable function to display a data card with a label and a numeric value.
39 |  *
40 |  * This function renders a column containing a label and a formatted numeric value. It provides
41 |  * options for highlighting the value and displaying a placeholder when the value is not available.
42 |  */
43 | @Composable
44 | fun DataCard(
45 |   label: String,
46 |   value: Float?,
47 |   unit: String,
48 |   highlight: Boolean = false,
49 |   showPlaceholder: Boolean = false,
50 | ) {
51 |   var strValue = "-"
52 |   Column {
53 |     Text(label, style = labelSmallNarrowMedium)
54 |     if (showPlaceholder) {
55 |       Text("-", style = bodySmallMediumNarrow)
56 |     } else {
57 |       strValue = if (value == null) "-" else "%.2f".format(value)
58 |       if (highlight) {
59 |         Text(strValue, style = bodySmallMediumNarrowBold, color = MaterialTheme.colorScheme.primary)
60 |       } else {
61 |         Text(strValue, style = bodySmallMediumNarrow)
62 |       }
63 |     }
64 |     if (strValue != "-") {
65 |       Text(unit, style = labelSmallNarrow, modifier = Modifier.alpha(0.5f).offset(y = (-1).dp))
66 |     }
67 |   }
68 | }
69 | 
70 | @Preview(showBackground = true)
71 | @Composable
72 | fun DataCardPreview() {
73 |   GalleryTheme {
74 |     Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
75 |       DataCard(
76 |         label = "sum",
77 |         value = 123.45f,
78 |         unit = "ms",
79 |         highlight = true,
80 |         showPlaceholder = false,
81 |       )
82 |       DataCard(
83 |         label = "average",
84 |         value = 12.3f,
85 |         unit = "ms",
86 |         highlight = false,
87 |         showPlaceholder = false,
88 |       )
89 |       DataCard(
90 |         label = "test",
91 |         value = null,
92 |         unit = "ms",
93 |         highlight = false,
94 |         showPlaceholder = false,
95 |       )
96 |     }
97 |   }
98 | }
99 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageActionButton.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | import androidx.compose.foundation.background
22 | import androidx.compose.foundation.clickable
23 | import androidx.compose.foundation.layout.Row
24 | import androidx.compose.foundation.layout.offset
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.foundation.layout.size
27 | import androidx.compose.foundation.shape.CircleShape
28 | import androidx.compose.material3.Icon
29 | import androidx.compose.material3.MaterialTheme
30 | import androidx.compose.material3.Text
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.ui.Alignment
33 | import androidx.compose.ui.Modifier
34 | import androidx.compose.ui.draw.alpha
35 | import androidx.compose.ui.draw.clip
36 | import androidx.compose.ui.graphics.vector.ImageVector
37 | import androidx.compose.ui.unit.dp
38 | import com.google.ai.edge.gallery.ui.theme.bodySmallNarrow
39 | 
40 | /** Composable function to display an action button below a chat message. */
41 | @Composable
42 | fun MessageActionButton(
43 |   label: String,
44 |   icon: ImageVector,
45 |   onClick: () -> Unit,
46 |   modifier: Modifier = Modifier,
47 |   enabled: Boolean = true,
48 | ) {
49 |   val curModifier =
50 |     modifier
51 |       .padding(top = 4.dp)
52 |       .clip(CircleShape)
53 |       .background(
54 |         if (enabled) MaterialTheme.colorScheme.secondaryContainer
55 |         else MaterialTheme.colorScheme.surfaceContainerHigh
56 |       )
57 |   val alpha: Float = if (enabled) 1.0f else 0.3f
58 |   Row(
59 |     modifier = if (enabled) curModifier.clickable { onClick() } else modifier,
60 |     verticalAlignment = Alignment.CenterVertically,
61 |   ) {
62 |     Icon(
63 |       icon,
64 |       contentDescription = "",
65 |       modifier = Modifier.size(16.dp).offset(x = 6.dp).alpha(alpha),
66 |     )
67 |     Text(
68 |       label,
69 |       color = MaterialTheme.colorScheme.onSecondaryContainer,
70 |       style = bodySmallNarrow,
71 |       modifier = Modifier.padding(start = 10.dp, end = 8.dp, top = 4.dp, bottom = 4.dp).alpha(alpha),
72 |     )
73 |   }
74 | }
75 | 
76 | // @Preview(showBackground = true)
77 | // @Composable
78 | // fun MessageActionButtonPreview() {
79 | //   GalleryTheme {
80 | //     Column {
81 | //       MessageActionButton(label = "run", icon = Icons.Default.PlayArrow, onClick = {})
82 | //       MessageActionButton(
83 | //         label = "run",
84 | //         icon = Icons.Default.PlayArrow,
85 | //         enabled = false,
86 | //         onClick = {})
87 | //     }
88 | //   }
89 | // }
90 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyAudioClip.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.unit.dp
23 | 
24 | @Composable
25 | fun MessageBodyAudioClip(message: ChatMessageAudioClip, modifier: Modifier = Modifier) {
26 |   AudioPlaybackPanel(
27 |     audioData = message.audioData,
28 |     sampleRate = message.sampleRate,
29 |     isRecording = false,
30 |     modifier = Modifier.padding(end = 16.dp),
31 |     onDarkBg = true,
32 |   )
33 | }
34 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyBenchmark.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.chat
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.foundation.background
 22 | import androidx.compose.foundation.layout.Arrangement
 23 | import androidx.compose.foundation.layout.Box
 24 | import androidx.compose.foundation.layout.Column
 25 | import androidx.compose.foundation.layout.Row
 26 | import androidx.compose.foundation.layout.fillMaxWidth
 27 | import androidx.compose.foundation.layout.height
 28 | import androidx.compose.foundation.layout.padding
 29 | import androidx.compose.foundation.layout.width
 30 | import androidx.compose.foundation.shape.RoundedCornerShape
 31 | import androidx.compose.material3.MaterialTheme
 32 | import androidx.compose.runtime.Composable
 33 | import androidx.compose.ui.Modifier
 34 | import androidx.compose.ui.draw.alpha
 35 | import androidx.compose.ui.draw.clip
 36 | import androidx.compose.ui.unit.dp
 37 | import kotlin.math.max
 38 | 
 39 | private const val DEFAULT_HISTOGRAM_BAR_HEIGHT = 50f
 40 | 
 41 | /**
 42 |  * Composable function to display benchmark results within a chat message.
 43 |  *
 44 |  * This function renders benchmark statistics (e.g., average latency) in data cards and visualizes
 45 |  * the latency distribution using a histogram.
 46 |  */
 47 | @Composable
 48 | fun MessageBodyBenchmark(message: ChatMessageBenchmarkResult) {
 49 |   Column(
 50 |     modifier = Modifier.padding(12.dp).fillMaxWidth(),
 51 |     verticalArrangement = Arrangement.spacedBy(8.dp),
 52 |   ) {
 53 |     // Data cards.
 54 |     Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
 55 |       for (stat in message.orderedStats) {
 56 |         DataCard(
 57 |           label = stat.label,
 58 |           unit = stat.unit,
 59 |           value = message.statValues[stat.id],
 60 |           highlight = stat.id == message.highlightStat,
 61 |           showPlaceholder = message.isWarmingUp(),
 62 |         )
 63 |       }
 64 |     }
 65 | 
 66 |     // Histogram
 67 |     if (message.histogram.buckets.isNotEmpty()) {
 68 |       Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
 69 |         for ((index, count) in message.histogram.buckets.withIndex()) {
 70 |           var barBgColor = MaterialTheme.colorScheme.onSurfaceVariant
 71 |           var alpha = 0.3f
 72 |           if (count != 0) {
 73 |             alpha = 0.5f
 74 |           }
 75 |           if (index == message.histogram.highlightBucketIndex) {
 76 |             barBgColor = MaterialTheme.colorScheme.primary
 77 |             alpha = 0.8f
 78 |           }
 79 |           // Bar container.
 80 |           Column(
 81 |             modifier = Modifier.height(DEFAULT_HISTOGRAM_BAR_HEIGHT.dp).width(4.dp),
 82 |             verticalArrangement = Arrangement.Bottom,
 83 |           ) {
 84 |             // Bar content.
 85 |             Box(
 86 |               modifier =
 87 |                 Modifier.height(
 88 |                     max(
 89 |                         1f,
 90 |                         count.toFloat() / message.histogram.maxCount.toFloat() *
 91 |                           DEFAULT_HISTOGRAM_BAR_HEIGHT,
 92 |                       )
 93 |                       .dp
 94 |                   )
 95 |                   .fillMaxWidth()
 96 |                   .clip(RoundedCornerShape(20.dp, 20.dp, 0.dp, 0.dp))
 97 |                   .alpha(alpha)
 98 |                   .background(barBgColor)
 99 |             )
100 |           }
101 |         }
102 |       }
103 |     }
104 |   }
105 | }
106 | 
107 | // @Preview(showBackground = true)
108 | // @Composable
109 | // fun MessageBodyBenchmarkPreview() {
110 | //   GalleryTheme {
111 | //     MessageBodyBenchmark(
112 | //       message = ChatMessageBenchmarkResult(
113 | //         orderedStats = listOf(
114 | //           Stat(id = "stat1", label = "Stat1", unit = "ms"),
115 | //           Stat(id = "stat2", label = "Stat2", unit = "ms"),
116 | //           Stat(id = "stat3", label = "Stat3", unit = "ms"),
117 | //           Stat(id = "stat4", label = "Stat4", unit = "ms")
118 | //         ),
119 | //         statValues = mutableMapOf(
120 | //           "stat1" to 0.3f,
121 | //           "stat2" to 0.4f,
122 | //           "stat3" to 0.5f,
123 | //         ),
124 | //         values = listOf(),
125 | //         histogram = Histogram(listOf(), 0),
126 | //         warmupCurrent = 0,
127 | //         warmupTotal = 0,
128 | //         iterationCurrent = 0,
129 | //         iterationTotal = 0,
130 | //         highlightStat = "stat2"
131 | //       )
132 | //     )
133 | //   }
134 | // }
135 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyBenchmarkLlm.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | import androidx.compose.foundation.layout.Arrangement
22 | import androidx.compose.foundation.layout.Column
23 | import androidx.compose.foundation.layout.Row
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.unit.dp
29 | 
30 | /**
31 |  * Composable function to display benchmark LLM results within a chat message.
32 |  *
33 |  * This function renders benchmark statistics (e.g., various token speed) in data cards
34 |  */
35 | @Composable
36 | fun MessageBodyBenchmarkLlm(message: ChatMessageBenchmarkLlmResult, modifier: Modifier = Modifier) {
37 |   Column(modifier = modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
38 |     // Data cards.
39 |     Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
40 |       for (stat in message.orderedStats) {
41 |         DataCard(label = stat.label, unit = stat.unit, value = message.statValues[stat.id])
42 |       }
43 |     }
44 |   }
45 | }
46 | 
47 | // @Preview(showBackground = true)
48 | // @Composable
49 | // fun MessageBodyBenchmarkLlmPreview() {
50 | //   GalleryTheme {
51 | //     MessageBodyBenchmarkLlm(
52 | //       message = ChatMessageBenchmarkLlmResult(
53 | //         orderedStats = listOf(
54 | //           Stat(id = "stat1", label = "Stat1", unit = "tokens/s"),
55 | //           Stat(id = "stat2", label = "Stat2", unit = "tokens/s")
56 | //         ),
57 | //         statValues = mutableMapOf(
58 | //           "stat1" to 0.3f,
59 | //           "stat2" to 0.4f,
60 | //         ),
61 | //         running = false,
62 | //       )
63 | //     )
64 | //   }
65 | // }
66 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyClassification.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.chat
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.foundation.background
 22 | import androidx.compose.foundation.layout.Arrangement
 23 | import androidx.compose.foundation.layout.Box
 24 | import androidx.compose.foundation.layout.Column
 25 | import androidx.compose.foundation.layout.Row
 26 | import androidx.compose.foundation.layout.Spacer
 27 | import androidx.compose.foundation.layout.fillMaxWidth
 28 | import androidx.compose.foundation.layout.height
 29 | import androidx.compose.foundation.layout.padding
 30 | import androidx.compose.foundation.shape.CircleShape
 31 | import androidx.compose.material3.MaterialTheme
 32 | import androidx.compose.material3.Text
 33 | import androidx.compose.runtime.Composable
 34 | import androidx.compose.ui.Alignment
 35 | import androidx.compose.ui.Modifier
 36 | import androidx.compose.ui.draw.clip
 37 | import androidx.compose.ui.text.style.TextOverflow
 38 | import androidx.compose.ui.unit.dp
 39 | 
 40 | val CLASSIFICATION_BAR_HEIGHT = 8.dp
 41 | val CLASSIFICATION_BAR_MAX_WIDTH = 200.dp
 42 | 
 43 | /**
 44 |  * Composable function to display classification results.
 45 |  *
 46 |  * This function renders a list of classifications, each with its label, score, and a visual score
 47 |  * bar.
 48 |  */
 49 | @Composable
 50 | fun MessageBodyClassification(
 51 |   message: ChatMessageClassification,
 52 |   modifier: Modifier = Modifier,
 53 |   oneLineLabel: Boolean = false,
 54 | ) {
 55 |   Column(modifier = modifier.padding(12.dp)) {
 56 |     for (classification in message.classifications) {
 57 |       Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
 58 |         // Classification label.
 59 |         Text(
 60 |           classification.label,
 61 |           maxLines = if (oneLineLabel) 1 else Int.MAX_VALUE,
 62 |           overflow = TextOverflow.Ellipsis,
 63 |           style = MaterialTheme.typography.bodySmall,
 64 |           modifier = Modifier.weight(1f),
 65 |         )
 66 |         // Classification score.
 67 |         Text(
 68 |           "%.2f".format(classification.score),
 69 |           style = MaterialTheme.typography.bodySmall,
 70 |           modifier = Modifier.align(Alignment.Bottom),
 71 |         )
 72 |       }
 73 |       Spacer(modifier = Modifier.height(2.dp))
 74 |       // Score bar.
 75 |       Box {
 76 |         Box(
 77 |           modifier =
 78 |             Modifier.fillMaxWidth()
 79 |               .height(CLASSIFICATION_BAR_HEIGHT)
 80 |               .clip(CircleShape)
 81 |               .background(MaterialTheme.colorScheme.surfaceDim)
 82 |         )
 83 |         Box(
 84 |           modifier =
 85 |             Modifier.fillMaxWidth(classification.score)
 86 |               .height(CLASSIFICATION_BAR_HEIGHT)
 87 |               .clip(CircleShape)
 88 |               .background(classification.color)
 89 |         )
 90 |       }
 91 |       Spacer(modifier = Modifier.height(6.dp))
 92 |     }
 93 |   }
 94 | }
 95 | 
 96 | // @Preview(showBackground = true)
 97 | // @Composable
 98 | // fun MessageBodyClassificationPreview() {
 99 | //   GalleryTheme {
100 | //     MessageBodyClassification(
101 | //       message =
102 | //         ChatMessageClassification(
103 | //           classifications =
104 | //             listOf(
105 | //               Classification(label = "label1", score = 0.3f, color = Color.Red),
106 | //               Classification(label = "label2", score = 0.7f, color = Color.Blue),
107 | //             ),
108 | //           latencyMs = 12345f,
109 | //         )
110 | //     )
111 | //   }
112 | // }
113 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyImage.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.foundation.Image
20 | import androidx.compose.foundation.layout.height
21 | import androidx.compose.foundation.layout.width
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.layout.ContentScale
25 | import androidx.compose.ui.unit.dp
26 | 
27 | @Composable
28 | fun MessageBodyImage(message: ChatMessageImage, modifier: Modifier = Modifier) {
29 |   val bitmapWidth = message.bitmap.width
30 |   val bitmapHeight = message.bitmap.height
31 |   val imageWidth =
32 |     if (bitmapWidth >= bitmapHeight) 200 else (200f / bitmapHeight * bitmapWidth).toInt()
33 |   val imageHeight =
34 |     if (bitmapHeight >= bitmapWidth) 200 else (200f / bitmapWidth * bitmapHeight).toInt()
35 | 
36 |   Image(
37 |     bitmap = message.imageBitMap,
38 |     contentDescription = "",
39 |     modifier = modifier.height(imageHeight.dp).width(imageWidth.dp),
40 |     contentScale = ContentScale.Fit,
41 |   )
42 | }
43 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyImageWithHistory.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.foundation.Image
20 | import androidx.compose.foundation.gestures.detectHorizontalDragGestures
21 | import androidx.compose.foundation.layout.Column
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.foundation.layout.width
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.runtime.LaunchedEffect
26 | import androidx.compose.runtime.MutableIntState
27 | import androidx.compose.runtime.MutableState
28 | import androidx.compose.runtime.getValue
29 | import androidx.compose.runtime.mutableFloatStateOf
30 | import androidx.compose.runtime.mutableIntStateOf
31 | import androidx.compose.runtime.mutableStateOf
32 | import androidx.compose.runtime.remember
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Modifier
35 | import androidx.compose.ui.input.pointer.pointerInput
36 | import androidx.compose.ui.layout.ContentScale
37 | import androidx.compose.ui.unit.dp
38 | 
39 | /**
40 |  * Composable function to display an image message with history, allowing users to navigate through
41 |  * different versions by sliding on the image.
42 |  */
43 | @Composable
44 | fun MessageBodyImageWithHistory(
45 |   message: ChatMessageImageWithHistory,
46 |   imageHistoryCurIndex: MutableIntState,
47 | ) {
48 |   val prevMessage: MutableState<ChatMessageImageWithHistory?> = remember { mutableStateOf(null) }
49 | 
50 |   LaunchedEffect(message) {
51 |     imageHistoryCurIndex.intValue = message.bitmaps.size - 1
52 |     prevMessage.value = message
53 |   }
54 | 
55 |   Column {
56 |     val curImage = message.bitmaps[imageHistoryCurIndex.intValue]
57 |     val curImageBitmap = message.imageBitMaps[imageHistoryCurIndex.intValue]
58 | 
59 |     val bitmapWidth = curImage.width
60 |     val bitmapHeight = curImage.height
61 |     val imageWidth =
62 |       if (bitmapWidth >= bitmapHeight) 200 else (200f / bitmapHeight * bitmapWidth).toInt()
63 |     val imageHeight =
64 |       if (bitmapHeight >= bitmapWidth) 200 else (200f / bitmapWidth * bitmapHeight).toInt()
65 | 
66 |     var value by remember { mutableFloatStateOf(0f) }
67 |     var savedIndex by remember { mutableIntStateOf(0) }
68 |     Image(
69 |       bitmap = curImageBitmap,
70 |       contentDescription = "",
71 |       modifier =
72 |         Modifier.height(imageHeight.dp).width(imageWidth.dp).pointerInput(Unit) {
73 |           detectHorizontalDragGestures(
74 |             onDragStart = {
75 |               value = 0f
76 |               savedIndex = imageHistoryCurIndex.intValue
77 |             }
78 |           ) { _, dragAmount ->
79 |             value += (dragAmount / 20f) // Adjust sensitivity here
80 |             imageHistoryCurIndex.intValue = (savedIndex + value).toInt()
81 |             if (imageHistoryCurIndex.intValue < 0) {
82 |               imageHistoryCurIndex.intValue = 0
83 |             } else if (imageHistoryCurIndex.intValue > message.bitmaps.size - 1) {
84 |               imageHistoryCurIndex.intValue = message.bitmaps.size - 1
85 |             }
86 |           }
87 |         },
88 |       contentScale = ContentScale.Fit,
89 |     )
90 |   }
91 | }
92 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyInfo.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | import androidx.compose.foundation.background
22 | import androidx.compose.foundation.layout.Arrangement
23 | import androidx.compose.foundation.layout.Box
24 | import androidx.compose.foundation.layout.Row
25 | import androidx.compose.foundation.layout.fillMaxWidth
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.foundation.shape.RoundedCornerShape
28 | import androidx.compose.material3.MaterialTheme
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.draw.clip
32 | import androidx.compose.ui.unit.dp
33 | import com.google.ai.edge.gallery.ui.common.MarkdownText
34 | import com.google.ai.edge.gallery.ui.theme.customColors
35 | 
36 | /**
37 |  * Composable function to display informational message content within a chat.
38 |  *
39 |  * Supports markdown.
40 |  */
41 | @Composable
42 | fun MessageBodyInfo(message: ChatMessageInfo, smallFontSize: Boolean = true) {
43 |   Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
44 |     Box(
45 |       modifier =
46 |         Modifier.clip(RoundedCornerShape(16.dp))
47 |           .background(MaterialTheme.customColors.agentBubbleBgColor)
48 |     ) {
49 |       MarkdownText(
50 |         text = message.content,
51 |         modifier = Modifier.padding(12.dp),
52 |         smallFontSize = smallFontSize,
53 |       )
54 |     }
55 |   }
56 | }
57 | 
58 | // @Preview(showBackground = true)
59 | // @Composable
60 | // fun MessageBodyInfoPreview() {
61 | //   GalleryTheme {
62 | //     Row(modifier = Modifier.padding(16.dp)) {
63 | //       MessageBodyInfo(message = ChatMessageInfo(content = "This is a model"))
64 | //     }
65 | //   }
66 | // }
67 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyLoading.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.chat
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.animation.core.Animatable
 22 | import androidx.compose.animation.core.tween
 23 | import androidx.compose.foundation.Image
 24 | import androidx.compose.foundation.layout.Box
 25 | import androidx.compose.foundation.layout.size
 26 | import androidx.compose.runtime.Composable
 27 | import androidx.compose.runtime.LaunchedEffect
 28 | import androidx.compose.runtime.mutableIntStateOf
 29 | import androidx.compose.runtime.remember
 30 | import androidx.compose.ui.Alignment
 31 | import androidx.compose.ui.Modifier
 32 | import androidx.compose.ui.graphics.ColorFilter
 33 | import androidx.compose.ui.graphics.graphicsLayer
 34 | import androidx.compose.ui.layout.ContentScale
 35 | import androidx.compose.ui.res.painterResource
 36 | import androidx.compose.ui.unit.dp
 37 | import com.google.ai.edge.gallery.R
 38 | import com.google.ai.edge.gallery.ui.common.getTaskIconColor
 39 | import kotlinx.coroutines.delay
 40 | import kotlinx.coroutines.launch
 41 | 
 42 | private val IMAGE_RESOURCES =
 43 |   listOf(R.drawable.pantegon, R.drawable.double_circle, R.drawable.circle, R.drawable.four_circle)
 44 | 
 45 | private const val ANIMATION_DURATION = 300
 46 | private const val ANIMATION_DURATION2 = 300
 47 | private const val PAUSE_DURATION = 200
 48 | private const val PAUSE_DURATION2 = 0
 49 | 
 50 | /** Composable function to display a loading indicator. */
 51 | @Composable
 52 | fun MessageBodyLoading() {
 53 |   val progress = remember { Animatable(0f) }
 54 |   val alphaAnim = remember { Animatable(0f) }
 55 |   val activeImageIndex = remember { mutableIntStateOf(0) }
 56 | 
 57 |   LaunchedEffect(Unit) { // Run this once
 58 |     while (true) {
 59 |       var progressJob = launch {
 60 |         progress.animateTo(
 61 |           targetValue = 1f,
 62 |           animationSpec =
 63 |             tween(
 64 |               durationMillis = ANIMATION_DURATION,
 65 |               easing = multiBounceEasing(bounces = 3, decay = 0.02f),
 66 |             ),
 67 |         )
 68 |       }
 69 |       var alphaJob = launch {
 70 |         alphaAnim.animateTo(
 71 |           targetValue = 1f,
 72 |           animationSpec = tween(durationMillis = ANIMATION_DURATION / 2),
 73 |         )
 74 |       }
 75 |       progressJob.join()
 76 |       alphaJob.join()
 77 |       delay((PAUSE_DURATION).toLong())
 78 | 
 79 |       progressJob = launch {
 80 |         progress.animateTo(
 81 |           targetValue = 0f,
 82 |           animationSpec =
 83 |             tween(
 84 |               durationMillis = ANIMATION_DURATION2,
 85 |               easing = multiBounceEasing(bounces = 3, decay = 0.02f),
 86 |             ),
 87 |         )
 88 |       }
 89 |       alphaJob = launch {
 90 |         alphaAnim.animateTo(
 91 |           targetValue = 0f,
 92 |           animationSpec = tween(durationMillis = ANIMATION_DURATION2 / 2),
 93 |         )
 94 |       }
 95 | 
 96 |       progressJob.join()
 97 |       alphaJob.join()
 98 |       delay(PAUSE_DURATION2.toLong())
 99 | 
100 |       activeImageIndex.intValue = (activeImageIndex.intValue + 1) % IMAGE_RESOURCES.size
101 |     }
102 |   }
103 | 
104 |   Box(contentAlignment = Alignment.Center) {
105 |     for ((index, imageResource) in IMAGE_RESOURCES.withIndex()) {
106 |       Image(
107 |         painter = painterResource(id = imageResource),
108 |         contentDescription = "",
109 |         contentScale = ContentScale.Fit,
110 |         colorFilter = ColorFilter.tint(getTaskIconColor(index = index)),
111 |         modifier =
112 |           Modifier.graphicsLayer {
113 |               scaleX = progress.value * 0.2f + 0.8f
114 |               scaleY = progress.value * 0.2f + 0.8f
115 |               rotationZ = progress.value * 100
116 |               alpha = if (index != activeImageIndex.intValue) 0f else alphaAnim.value
117 |             }
118 |             .size(24.dp),
119 |       )
120 |     }
121 |   }
122 | }
123 | 
124 | // @Preview(showBackground = true)
125 | // @Composable
126 | // fun MessageBodyLoadingPreview() {
127 | //   GalleryTheme { Row(modifier = Modifier.padding(16.dp)) { MessageBodyLoading() } }
128 | // }
129 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyText.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
20 | // import androidx.compose.ui.tooling.preview.Preview
21 | 
22 | import androidx.compose.foundation.layout.padding
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.text.font.FontWeight
29 | import androidx.compose.ui.unit.dp
30 | import com.google.ai.edge.gallery.ui.common.MarkdownText
31 | 
32 | /** Composable function to display the text content of a ChatMessageText. */
33 | @Composable
34 | fun MessageBodyText(message: ChatMessageText) {
35 |   if (message.side == ChatSide.USER) {
36 |     Text(
37 |       message.content,
38 |       style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium),
39 |       color = Color.White,
40 |       modifier = Modifier.padding(12.dp),
41 |     )
42 |   } else if (message.side == ChatSide.AGENT) {
43 |     if (message.isMarkdown) {
44 |       MarkdownText(text = message.content, modifier = Modifier.padding(12.dp))
45 |     } else {
46 |       Text(
47 |         message.content,
48 |         style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Medium),
49 |         color = MaterialTheme.colorScheme.onSurface,
50 |         modifier = Modifier.padding(12.dp),
51 |       )
52 |     }
53 |   }
54 | }
55 | 
56 | // @Preview(showBackground = true)
57 | // @Composable
58 | // fun MessageBodyTextPreview() {
59 | //   GalleryTheme {
60 | //     Column {
61 | //       Row(modifier = Modifier.padding(16.dp).background(MaterialTheme.colorScheme.primary)) {
62 | //         MessageBodyText(ChatMessageText(content = "Hello world", side = ChatSide.USER))
63 | //       }
64 | //       Row(
65 | //         modifier = Modifier.padding(16.dp).background(MaterialTheme.colorScheme.surfaceContainer)
66 | //       ) {
67 | //         MessageBodyText(ChatMessageText(content = "yes hello world", side = ChatSide.AGENT))
68 | //       }
69 | //     }
70 | //   }
71 | // }
72 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyWarning.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | import androidx.compose.foundation.background
22 | import androidx.compose.foundation.layout.Arrangement
23 | import androidx.compose.foundation.layout.Box
24 | import androidx.compose.foundation.layout.Row
25 | import androidx.compose.foundation.layout.fillMaxWidth
26 | import androidx.compose.foundation.layout.padding
27 | import androidx.compose.foundation.shape.RoundedCornerShape
28 | import androidx.compose.material3.MaterialTheme
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.draw.clip
32 | import androidx.compose.ui.unit.dp
33 | import com.google.ai.edge.gallery.ui.common.MarkdownText
34 | 
35 | /**
36 |  * Composable function to display warning message content within a chat.
37 |  *
38 |  * Supports markdown.
39 |  */
40 | @Composable
41 | fun MessageBodyWarning(message: ChatMessageWarning) {
42 |   Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
43 |     Box(
44 |       modifier =
45 |         Modifier.clip(RoundedCornerShape(16.dp))
46 |           .background(MaterialTheme.colorScheme.tertiaryContainer)
47 |     ) {
48 |       MarkdownText(
49 |         text = message.content,
50 |         modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp),
51 |         smallFontSize = true,
52 |       )
53 |     }
54 |   }
55 | }
56 | 
57 | // @Preview(showBackground = true)
58 | // @Composable
59 | // fun MessageBodyWarningPreview() {
60 | //   GalleryTheme {
61 | //     Row(modifier = Modifier.padding(16.dp)) {
62 | //       MessageBodyWarning(message = ChatMessageWarning(content = "This is a warning"))
63 | //     }
64 | //   }
65 | // }
66 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBubbleShape.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.ui.geometry.CornerRadius
20 | import androidx.compose.ui.geometry.RoundRect
21 | import androidx.compose.ui.geometry.Size
22 | import androidx.compose.ui.graphics.Outline
23 | import androidx.compose.ui.graphics.Path
24 | import androidx.compose.ui.graphics.Shape
25 | import androidx.compose.ui.unit.Density
26 | import androidx.compose.ui.unit.Dp
27 | import androidx.compose.ui.unit.LayoutDirection
28 | 
29 | /**
30 |  * Custom Shape for creating message bubble outlines with configurable corner radii.
31 |  *
32 |  * This class defines a custom Shape that generates a rounded rectangle outline, suitable for
33 |  * message bubbles. It allows specifying a uniform corner radius for most corners, but also provides
34 |  * the option to have a hard (non-rounded) corner on either the left or right side.
35 |  */
36 | class MessageBubbleShape(
37 |   private val radius: Dp,
38 |   private val hardCornerAtLeftOrRight: Boolean = false,
39 | ) : Shape {
40 |   override fun createOutline(
41 |     size: Size,
42 |     layoutDirection: LayoutDirection,
43 |     density: Density,
44 |   ): Outline {
45 |     val radiusPx = with(density) { radius.toPx() }
46 |     val path =
47 |       Path().apply {
48 |         addRoundRect(
49 |           RoundRect(
50 |             left = 0f,
51 |             top = 0f,
52 |             right = size.width,
53 |             bottom = size.height,
54 |             topLeftCornerRadius =
55 |               if (hardCornerAtLeftOrRight) CornerRadius(0f, 0f)
56 |               else CornerRadius(radiusPx, radiusPx),
57 |             topRightCornerRadius =
58 |               if (hardCornerAtLeftOrRight) CornerRadius(radiusPx, radiusPx)
59 |               else CornerRadius(0f, 0f), // No rounding here
60 |             bottomLeftCornerRadius = CornerRadius(radiusPx, radiusPx),
61 |             bottomRightCornerRadius = CornerRadius(radiusPx, radiusPx),
62 |           )
63 |         )
64 |       }
65 |     return Outline.Generic(path)
66 |   }
67 | }
68 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageLatency.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | 
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.Text
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.draw.alpha
27 | import com.google.ai.edge.gallery.ui.common.humanReadableDuration
28 | 
29 | /** Composable function to display the latency of a chat message, if available. */
30 | @Composable
31 | fun LatencyText(message: ChatMessage) {
32 |   if (message.latencyMs >= 0) {
33 |     Text(
34 |       message.latencyMs.humanReadableDuration(),
35 |       modifier = Modifier.alpha(0.5f),
36 |       style = MaterialTheme.typography.labelSmall,
37 |     )
38 |   }
39 | }
40 | 
41 | // @Preview(showBackground = true)
42 | // @Composable
43 | // fun LatencyTextPreview() {
44 | //   GalleryTheme {
45 | //     Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp))
46 | // {
47 | //       for (latencyMs in listOf(123f, 1234f, 123456f, 7234567f)) {
48 | //         LatencyText(
49 | //           message =
50 | //             ChatMessage(latencyMs = latencyMs, type = ChatMessageType.TEXT, side =
51 | // ChatSide.AGENT)
52 | //         )
53 | //       }
54 | //     }
55 | //   }
56 | // }
57 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelDownloadStatusInfoPanel.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.chat
 18 | 
 19 | import androidx.compose.animation.AnimatedVisibility
 20 | import androidx.compose.animation.fadeIn
 21 | import androidx.compose.animation.fadeOut
 22 | import androidx.compose.animation.scaleIn
 23 | import androidx.compose.animation.scaleOut
 24 | import androidx.compose.foundation.layout.Arrangement
 25 | import androidx.compose.foundation.layout.Box
 26 | import androidx.compose.foundation.layout.Column
 27 | import androidx.compose.foundation.layout.fillMaxSize
 28 | import androidx.compose.runtime.Composable
 29 | import androidx.compose.runtime.LaunchedEffect
 30 | import androidx.compose.runtime.collectAsState
 31 | import androidx.compose.runtime.getValue
 32 | import androidx.compose.runtime.mutableStateOf
 33 | import androidx.compose.runtime.remember
 34 | import androidx.compose.runtime.setValue
 35 | import androidx.compose.ui.Alignment
 36 | import androidx.compose.ui.Modifier
 37 | import com.google.ai.edge.gallery.data.Model
 38 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType
 39 | import com.google.ai.edge.gallery.data.Task
 40 | import com.google.ai.edge.gallery.ui.common.DownloadAndTryButton
 41 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel
 42 | import kotlinx.coroutines.delay
 43 | 
 44 | @Composable
 45 | fun ModelDownloadStatusInfoPanel(
 46 |   model: Model,
 47 |   task: Task,
 48 |   modelManagerViewModel: ModelManagerViewModel,
 49 | ) {
 50 |   val modelManagerUiState by modelManagerViewModel.uiState.collectAsState()
 51 | 
 52 |   // Manages the conditional display of UI elements (download model button and downloading
 53 |   // animation) based on the corresponding download status.
 54 |   //
 55 |   // It uses delayed visibility ensuring they are shown only after a short delay if their
 56 |   // respective conditions remain true. This prevents UI flickering and provides a smoother
 57 |   // user experience.
 58 |   val curStatus = modelManagerUiState.modelDownloadStatus[model.name]
 59 |   var shouldShowDownloadingAnimation by remember { mutableStateOf(false) }
 60 |   var downloadingAnimationConditionMet by remember { mutableStateOf(false) }
 61 |   var shouldShowDownloadModelButton by remember { mutableStateOf(false) }
 62 |   var downloadModelButtonConditionMet by remember { mutableStateOf(false) }
 63 | 
 64 |   downloadingAnimationConditionMet =
 65 |     curStatus?.status == ModelDownloadStatusType.IN_PROGRESS ||
 66 |       curStatus?.status == ModelDownloadStatusType.PARTIALLY_DOWNLOADED ||
 67 |       curStatus?.status == ModelDownloadStatusType.UNZIPPING
 68 |   downloadModelButtonConditionMet =
 69 |     curStatus?.status == ModelDownloadStatusType.FAILED ||
 70 |       curStatus?.status == ModelDownloadStatusType.NOT_DOWNLOADED
 71 | 
 72 |   LaunchedEffect(downloadingAnimationConditionMet) {
 73 |     if (downloadingAnimationConditionMet) {
 74 |       delay(100)
 75 |       shouldShowDownloadingAnimation = true
 76 |     } else {
 77 |       shouldShowDownloadingAnimation = false
 78 |     }
 79 |   }
 80 | 
 81 |   LaunchedEffect(downloadModelButtonConditionMet) {
 82 |     if (downloadModelButtonConditionMet) {
 83 |       delay(700)
 84 |       shouldShowDownloadModelButton = true
 85 |     } else {
 86 |       shouldShowDownloadModelButton = false
 87 |     }
 88 |   }
 89 | 
 90 |   AnimatedVisibility(
 91 |     visible = shouldShowDownloadingAnimation,
 92 |     enter = scaleIn(initialScale = 0.9f) + fadeIn(),
 93 |     exit = scaleOut(targetScale = 0.9f) + fadeOut(),
 94 |   ) {
 95 |     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
 96 |       ModelDownloadingAnimation(
 97 |         model = model,
 98 |         task = task,
 99 |         modelManagerViewModel = modelManagerViewModel,
100 |       )
101 |     }
102 |   }
103 | 
104 |   AnimatedVisibility(visible = shouldShowDownloadModelButton, enter = fadeIn(), exit = fadeOut()) {
105 |     Column(
106 |       modifier = Modifier.fillMaxSize(),
107 |       verticalArrangement = Arrangement.Center,
108 |       horizontalAlignment = Alignment.CenterHorizontally,
109 |     ) {
110 |       DownloadAndTryButton(
111 |         task = task,
112 |         model = model,
113 |         enabled = true,
114 |         needToDownloadFirst = true,
115 |         modelManagerViewModel = modelManagerViewModel,
116 |         onClicked = {},
117 |       )
118 |     }
119 |   }
120 | }
121 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelInitializationStatus.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | 
22 | import androidx.compose.foundation.background
23 | import androidx.compose.foundation.layout.Arrangement
24 | import androidx.compose.foundation.layout.Box
25 | import androidx.compose.foundation.layout.Row
26 | import androidx.compose.foundation.layout.Spacer
27 | import androidx.compose.foundation.layout.fillMaxWidth
28 | import androidx.compose.foundation.layout.padding
29 | import androidx.compose.foundation.layout.size
30 | import androidx.compose.foundation.layout.width
31 | import androidx.compose.foundation.shape.CircleShape
32 | import androidx.compose.material3.CircularProgressIndicator
33 | import androidx.compose.material3.MaterialTheme
34 | import androidx.compose.material3.Text
35 | import androidx.compose.runtime.Composable
36 | import androidx.compose.ui.Alignment
37 | import androidx.compose.ui.Modifier
38 | import androidx.compose.ui.draw.clip
39 | import androidx.compose.ui.res.stringResource
40 | import androidx.compose.ui.unit.dp
41 | import com.google.ai.edge.gallery.R
42 | 
43 | /**
44 |  * Composable function to display a visual indicator for model initialization status.
45 |  *
46 |  * This function renders a row containing a circular progress indicator and a message indicating
47 |  * that the model is currently initializing. It provides a visual cue to the user that the model is
48 |  * in a loading state.
49 |  */
50 | @Composable
51 | fun ModelInitializationStatusChip() {
52 |   Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
53 |     Box(
54 |       modifier =
55 |         Modifier.padding(8.dp)
56 |           .clip(CircleShape)
57 |           .background(MaterialTheme.colorScheme.secondaryContainer)
58 |     ) {
59 |       Row(
60 |         modifier = Modifier.padding(top = 4.dp, bottom = 4.dp, start = 8.dp, end = 8.dp),
61 |         horizontalArrangement = Arrangement.Center,
62 |         verticalAlignment = Alignment.CenterVertically,
63 |       ) {
64 |         // Circular progress indicator.
65 |         CircularProgressIndicator(
66 |           modifier = Modifier.size(14.dp),
67 |           strokeWidth = 2.dp,
68 |           color = MaterialTheme.colorScheme.onSecondaryContainer,
69 |         )
70 | 
71 |         Spacer(modifier = Modifier.width(8.dp))
72 | 
73 |         // Text message.
74 |         Text(
75 |           stringResource(R.string.model_is_initializing_msg),
76 |           style = MaterialTheme.typography.bodySmall,
77 |           color = MaterialTheme.colorScheme.onSecondaryContainer,
78 |         )
79 |       }
80 |     }
81 |   }
82 | }
83 | 
84 | // @Preview(showBackground = true)
85 | // @Composable
86 | // fun ModelInitializationStatusPreview() {
87 | //   GalleryTheme { ModelInitializationStatusChip() }
88 | // }
89 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | // import androidx.compose.ui.tooling.preview.Preview
20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
21 | 
22 | import androidx.compose.foundation.layout.Arrangement
23 | import androidx.compose.foundation.layout.Column
24 | import androidx.compose.foundation.layout.fillMaxSize
25 | import androidx.compose.material3.Button
26 | import androidx.compose.material3.Text
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | 
31 | /**
32 |  * Composable function to display a button to download model if the model has not been downloaded.
33 |  */
34 | @Composable
35 | fun ModelNotDownloaded(modifier: Modifier = Modifier, onClicked: () -> Unit) {
36 |   Column(
37 |     modifier = modifier.fillMaxSize(),
38 |     verticalArrangement = Arrangement.Center,
39 |     horizontalAlignment = Alignment.CenterHorizontally,
40 |   ) {
41 |     Button(onClick = onClicked) { Text("Download & Try it", maxLines = 1) }
42 |   }
43 | }
44 | 
45 | // @Preview(showBackground = true)
46 | // @Composable
47 | // fun Preview() {
48 | //   GalleryTheme { ModelNotDownloaded(onClicked = {}) }
49 | // }
50 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ZoomableBox.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.chat
18 | 
19 | import androidx.compose.foundation.gestures.detectTransformGestures
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.mutableFloatStateOf
24 | import androidx.compose.runtime.mutableStateOf
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.runtime.setValue
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.input.pointer.pointerInput
30 | import androidx.compose.ui.layout.onSizeChanged
31 | import androidx.compose.ui.unit.IntSize
32 | 
33 | @Composable
34 | fun ZoomableBox(
35 |   modifier: Modifier = Modifier,
36 |   minScale: Float = 1f,
37 |   maxScale: Float = 5f,
38 |   content: @Composable ZoomableBoxScope.() -> Unit,
39 | ) {
40 |   var scale by remember { mutableFloatStateOf(1f) }
41 |   var offsetX by remember { mutableFloatStateOf(0f) }
42 |   var offsetY by remember { mutableFloatStateOf(0f) }
43 |   var size by remember { mutableStateOf(IntSize.Zero) }
44 |   Box(
45 |     modifier =
46 |       modifier
47 |         .onSizeChanged { size = it }
48 |         .pointerInput(Unit) {
49 |           detectTransformGestures { _, pan, zoom, _ ->
50 |             scale = maxOf(minScale, minOf(scale * zoom, maxScale))
51 |             val maxX = (size.width * (scale - 1)) / 2
52 |             val minX = -maxX
53 |             offsetX = maxOf(minX, minOf(maxX, offsetX + pan.x))
54 |             val maxY = (size.height * (scale - 1)) / 2
55 |             val minY = -maxY
56 |             offsetY = maxOf(minY, minOf(maxY, offsetY + pan.y))
57 |           }
58 |         },
59 |     contentAlignment = Alignment.TopEnd,
60 |   ) {
61 |     val scope = ZoomableBoxScopeImpl(scale, offsetX, offsetY)
62 |     scope.content()
63 |   }
64 | }
65 | 
66 | interface ZoomableBoxScope {
67 |   val scale: Float
68 |   val offsetX: Float
69 |   val offsetY: Float
70 | }
71 | 
72 | private data class ZoomableBoxScopeImpl(
73 |   override val scale: Float,
74 |   override val offsetX: Float,
75 |   override val offsetY: Float,
76 | ) : ZoomableBoxScope
77 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/ConfirmDeleteModelDialog.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.common.modelitem
18 | 
19 | import androidx.compose.material3.AlertDialog
20 | import androidx.compose.material3.Button
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.ai.edge.gallery.R
26 | import com.google.ai.edge.gallery.data.Model
27 | 
28 | /** Composable function to display a confirmation dialog for deleting a model. */
29 | @Composable
30 | fun ConfirmDeleteModelDialog(model: Model, onConfirm: () -> Unit, onDismiss: () -> Unit) {
31 |   AlertDialog(
32 |     onDismissRequest = onDismiss,
33 |     title = { Text(stringResource(R.string.confirm_delete_model_dialog_title)) },
34 |     text = {
35 |       Text(stringResource(R.string.confirm_delete_model_dialog_content).format(model.name))
36 |     },
37 |     confirmButton = { Button(onClick = onConfirm) { Text(stringResource(R.string.ok)) } },
38 |     dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
39 |   )
40 | }
41 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/ModelItemActionButton.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.modelitem
 18 | 
 19 | import android.content.Context
 20 | import androidx.compose.foundation.layout.Row
 21 | import androidx.compose.foundation.layout.padding
 22 | import androidx.compose.foundation.layout.size
 23 | import androidx.compose.material.icons.Icons
 24 | import androidx.compose.material.icons.rounded.Cancel
 25 | import androidx.compose.material.icons.rounded.Delete
 26 | import androidx.compose.material.icons.rounded.FileDownload
 27 | import androidx.compose.material3.CircularProgressIndicator
 28 | import androidx.compose.material3.Icon
 29 | import androidx.compose.material3.IconButton
 30 | import androidx.compose.runtime.Composable
 31 | import androidx.compose.runtime.getValue
 32 | import androidx.compose.runtime.mutableStateOf
 33 | import androidx.compose.runtime.remember
 34 | import androidx.compose.runtime.setValue
 35 | import androidx.compose.ui.Alignment
 36 | import androidx.compose.ui.Modifier
 37 | import androidx.compose.ui.unit.dp
 38 | import com.google.ai.edge.gallery.data.Model
 39 | import com.google.ai.edge.gallery.data.ModelDownloadStatus
 40 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType
 41 | import com.google.ai.edge.gallery.data.Task
 42 | import com.google.ai.edge.gallery.ui.common.getTaskIconColor
 43 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel
 44 | 
 45 | /**
 46 |  * Composable function to display action buttons for a model item, based on its download status.
 47 |  *
 48 |  * This function renders the appropriate action button (download, delete, cancel) based on the
 49 |  * provided ModelDownloadStatus. It also handles notification permission requests for downloading
 50 |  * and displays a confirmation dialog for deleting models.
 51 |  */
 52 | @Composable
 53 | fun ModelItemActionButton(
 54 |   context: Context,
 55 |   model: Model,
 56 |   task: Task,
 57 |   modelManagerViewModel: ModelManagerViewModel,
 58 |   downloadStatus: ModelDownloadStatus?,
 59 |   onDownloadClicked: (Model) -> Unit,
 60 |   modifier: Modifier = Modifier,
 61 |   showDeleteButton: Boolean = true,
 62 |   showDownloadButton: Boolean = true,
 63 | ) {
 64 |   var showConfirmDeleteDialog by remember { mutableStateOf(false) }
 65 | 
 66 |   Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
 67 |     when (downloadStatus?.status) {
 68 |       // Button to start the download.
 69 |       ModelDownloadStatusType.NOT_DOWNLOADED,
 70 |       ModelDownloadStatusType.FAILED ->
 71 |         if (showDownloadButton) {
 72 |           IconButton(onClick = { onDownloadClicked(model) }) {
 73 |             Icon(Icons.Rounded.FileDownload, contentDescription = "", tint = getTaskIconColor(task))
 74 |           }
 75 |         }
 76 | 
 77 |       // Button to delete the download.
 78 |       ModelDownloadStatusType.SUCCEEDED -> {
 79 |         if (showDeleteButton) {
 80 |           IconButton(onClick = { showConfirmDeleteDialog = true }) {
 81 |             Icon(Icons.Rounded.Delete, contentDescription = "", tint = getTaskIconColor(task))
 82 |           }
 83 |         }
 84 |       }
 85 | 
 86 |       // Show spinner when the model is partially downloaded because it might some time for
 87 |       // background task to be started by Android.
 88 |       ModelDownloadStatusType.PARTIALLY_DOWNLOADED -> {
 89 |         CircularProgressIndicator(modifier = Modifier.padding(end = 12.dp).size(24.dp))
 90 |       }
 91 | 
 92 |       // Button to cancel the download when it is in progress.
 93 |       ModelDownloadStatusType.IN_PROGRESS,
 94 |       ModelDownloadStatusType.UNZIPPING ->
 95 |         IconButton(
 96 |           onClick = { modelManagerViewModel.cancelDownloadModel(task = task, model = model) }
 97 |         ) {
 98 |           Icon(Icons.Rounded.Cancel, contentDescription = "", tint = getTaskIconColor(task))
 99 |         }
100 | 
101 |       else -> {}
102 |     }
103 |   }
104 | 
105 |   if (showConfirmDeleteDialog) {
106 |     ConfirmDeleteModelDialog(
107 |       model = model,
108 |       onConfirm = {
109 |         modelManagerViewModel.deleteModel(task = task, model = model)
110 |         showConfirmDeleteDialog = false
111 |       },
112 |       onDismiss = { showConfirmDeleteDialog = false },
113 |     )
114 |   }
115 | }
116 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/StatusIcon.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.common.modelitem
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.foundation.layout.Arrangement
 22 | import androidx.compose.foundation.layout.Row
 23 | import androidx.compose.foundation.layout.size
 24 | import androidx.compose.material.icons.Icons
 25 | import androidx.compose.material.icons.automirrored.outlined.HelpOutline
 26 | import androidx.compose.material.icons.filled.DownloadForOffline
 27 | import androidx.compose.material.icons.rounded.Downloading
 28 | import androidx.compose.material.icons.rounded.Error
 29 | import androidx.compose.material3.Icon
 30 | import androidx.compose.material3.MaterialTheme
 31 | import androidx.compose.runtime.Composable
 32 | import androidx.compose.ui.Alignment
 33 | import androidx.compose.ui.Modifier
 34 | import androidx.compose.ui.graphics.Color
 35 | import androidx.compose.ui.unit.dp
 36 | import com.google.ai.edge.gallery.data.ModelDownloadStatus
 37 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType
 38 | import com.google.ai.edge.gallery.ui.theme.customColors
 39 | 
 40 | private val SIZE = 18.dp
 41 | 
 42 | /** Composable function to display an icon representing the download status of a model. */
 43 | @Composable
 44 | fun StatusIcon(downloadStatus: ModelDownloadStatus?, modifier: Modifier = Modifier) {
 45 |   Row(
 46 |     verticalAlignment = Alignment.CenterVertically,
 47 |     horizontalArrangement = Arrangement.Center,
 48 |     modifier = modifier,
 49 |   ) {
 50 |     when (downloadStatus?.status) {
 51 |       ModelDownloadStatusType.NOT_DOWNLOADED ->
 52 |         Icon(
 53 |           Icons.AutoMirrored.Outlined.HelpOutline,
 54 |           tint = Color(0xFFCCCCCC),
 55 |           contentDescription = "",
 56 |           modifier = Modifier.size(SIZE),
 57 |         )
 58 | 
 59 |       ModelDownloadStatusType.SUCCEEDED -> {
 60 |         Icon(
 61 |           Icons.Filled.DownloadForOffline,
 62 |           tint = MaterialTheme.customColors.successColor,
 63 |           contentDescription = "",
 64 |           modifier = Modifier.size(SIZE),
 65 |         )
 66 |       }
 67 | 
 68 |       ModelDownloadStatusType.FAILED ->
 69 |         Icon(
 70 |           Icons.Rounded.Error,
 71 |           tint = Color(0xFFAA0000),
 72 |           contentDescription = "",
 73 |           modifier = Modifier.size(SIZE),
 74 |         )
 75 | 
 76 |       ModelDownloadStatusType.IN_PROGRESS ->
 77 |         Icon(Icons.Rounded.Downloading, contentDescription = "", modifier = Modifier.size(SIZE))
 78 | 
 79 |       else -> {}
 80 |     }
 81 |   }
 82 | }
 83 | 
 84 | // @Preview(showBackground = true)
 85 | // @Composable
 86 | // fun StatusIconPreview() {
 87 | //   GalleryTheme {
 88 | //     Column {
 89 | //       for (downloadStatus in
 90 | //         listOf(
 91 | //           ModelDownloadStatus(status = ModelDownloadStatusType.NOT_DOWNLOADED),
 92 | //           ModelDownloadStatus(status = ModelDownloadStatusType.IN_PROGRESS),
 93 | //           ModelDownloadStatus(status = ModelDownloadStatusType.SUCCEEDED),
 94 | //           ModelDownloadStatus(status = ModelDownloadStatusType.FAILED),
 95 | //           ModelDownloadStatus(status = ModelDownloadStatusType.UNZIPPING),
 96 | //           ModelDownloadStatus(status = ModelDownloadStatusType.PARTIALLY_DOWNLOADED),
 97 | //         )) {
 98 | //         StatusIcon(downloadStatus = downloadStatus)
 99 | //       }
100 | //     }
101 | //   }
102 | // }
103 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/icon/Deploy.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.icon
18 | 
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.PathFillType
21 | import androidx.compose.ui.graphics.SolidColor
22 | import androidx.compose.ui.graphics.StrokeCap
23 | import androidx.compose.ui.graphics.StrokeJoin
24 | import androidx.compose.ui.graphics.vector.ImageVector
25 | import androidx.compose.ui.graphics.vector.path
26 | import androidx.compose.ui.unit.dp
27 | 
28 | val Deployed_code: ImageVector
29 |   get() {
30 |     if (internal_Deployed_code != null) {
31 |       return internal_Deployed_code!!
32 |     }
33 |     internal_Deployed_code =
34 |       ImageVector.Builder(
35 |           name = "Deployed_code",
36 |           defaultWidth = 24.dp,
37 |           defaultHeight = 24.dp,
38 |           viewportWidth = 960f,
39 |           viewportHeight = 960f,
40 |         )
41 |         .apply {
42 |           path(
43 |             fill = SolidColor(Color.Black),
44 |             fillAlpha = 1.0f,
45 |             stroke = null,
46 |             strokeAlpha = 1.0f,
47 |             strokeLineWidth = 1.0f,
48 |             strokeLineCap = StrokeCap.Butt,
49 |             strokeLineJoin = StrokeJoin.Miter,
50 |             strokeLineMiter = 1.0f,
51 |             pathFillType = PathFillType.NonZero,
52 |           ) {
53 |             moveTo(440f, 777f)
54 |             verticalLineToRelative(-274f)
55 |             lineTo(200f, 364f)
56 |             verticalLineToRelative(274f)
57 |             close()
58 |             moveToRelative(80f, 0f)
59 |             lineToRelative(240f, -139f)
60 |             verticalLineToRelative(-274f)
61 |             lineTo(520f, 503f)
62 |             close()
63 |             moveToRelative(-40f, -343f)
64 |             lineToRelative(237f, -137f)
65 |             lineToRelative(-237f, -137f)
66 |             lineToRelative(-237f, 137f)
67 |             close()
68 |             moveTo(160f, 708f)
69 |             quadToRelative(-19f, -11f, -29.5f, -29f)
70 |             reflectiveQuadTo(120f, 639f)
71 |             verticalLineToRelative(-318f)
72 |             quadToRelative(0f, -22f, 10.5f, -40f)
73 |             reflectiveQuadToRelative(29.5f, -29f)
74 |             lineToRelative(280f, -161f)
75 |             quadToRelative(19f, -11f, 40f, -11f)
76 |             reflectiveQuadToRelative(40f, 11f)
77 |             lineToRelative(280f, 161f)
78 |             quadToRelative(19f, 11f, 29.5f, 29f)
79 |             reflectiveQuadToRelative(10.5f, 40f)
80 |             verticalLineToRelative(318f)
81 |             quadToRelative(0f, 22f, -10.5f, 40f)
82 |             reflectiveQuadTo(800f, 708f)
83 |             lineTo(520f, 869f)
84 |             quadToRelative(-19f, 11f, -40f, 11f)
85 |             reflectiveQuadToRelative(-40f, -11f)
86 |             close()
87 |             moveToRelative(320f, -228f)
88 |           }
89 |         }
90 |         .build()
91 |     return internal_Deployed_code!!
92 |   }
93 | 
94 | private var internal_Deployed_code: ImageVector? = null
95 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/SingleSelectButton.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.llmsingleturn
18 | 
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.clickable
21 | import androidx.compose.foundation.layout.Arrangement
22 | import androidx.compose.foundation.layout.Box
23 | import androidx.compose.foundation.layout.Row
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.shape.RoundedCornerShape
26 | import androidx.compose.material.icons.Icons
27 | import androidx.compose.material.icons.rounded.ArrowDropDown
28 | import androidx.compose.material3.DropdownMenu
29 | import androidx.compose.material3.DropdownMenuItem
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.material3.MaterialTheme
32 | import androidx.compose.material3.Text
33 | import androidx.compose.runtime.Composable
34 | import androidx.compose.runtime.LaunchedEffect
35 | import androidx.compose.runtime.getValue
36 | import androidx.compose.runtime.mutableStateOf
37 | import androidx.compose.runtime.remember
38 | import androidx.compose.runtime.setValue
39 | import androidx.compose.ui.Alignment
40 | import androidx.compose.ui.Modifier
41 | import androidx.compose.ui.draw.clip
42 | import androidx.compose.ui.unit.dp
43 | 
44 | @Composable
45 | fun SingleSelectButton(
46 |   config: PromptTemplateSingleSelectInputEditor,
47 |   onSelected: (String) -> Unit,
48 | ) {
49 |   var showMenu by remember { mutableStateOf(false) }
50 |   var selectedOption by remember { mutableStateOf(config.defaultOption) }
51 | 
52 |   LaunchedEffect(config) { selectedOption = config.defaultOption }
53 | 
54 |   Box {
55 |     Row(
56 |       verticalAlignment = Alignment.CenterVertically,
57 |       horizontalArrangement = Arrangement.spacedBy(2.dp),
58 |       modifier =
59 |         Modifier.clip(RoundedCornerShape(8.dp))
60 |           .background(MaterialTheme.colorScheme.secondaryContainer)
61 |           .clickable { showMenu = true }
62 |           .padding(vertical = 4.dp, horizontal = 6.dp)
63 |           .padding(start = 8.dp),
64 |     ) {
65 |       Text("${config.label}: $selectedOption", style = MaterialTheme.typography.labelLarge)
66 |       Icon(Icons.Rounded.ArrowDropDown, contentDescription = "")
67 |     }
68 | 
69 |     DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
70 |       // Options
71 |       for (option in config.options) {
72 |         DropdownMenuItem(
73 |           text = { Text(option) },
74 |           onClick = {
75 |             selectedOption = option
76 |             showMenu = false
77 |             onSelected(option)
78 |           },
79 |         )
80 |       }
81 |     }
82 |   }
83 | }
84 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/VerticalSplitView.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.llmsingleturn
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 21 | import androidx.compose.foundation.background
 22 | import androidx.compose.foundation.gestures.detectDragGestures
 23 | import androidx.compose.foundation.layout.Box
 24 | import androidx.compose.foundation.layout.Column
 25 | import androidx.compose.foundation.layout.fillMaxSize
 26 | import androidx.compose.foundation.layout.fillMaxWidth
 27 | import androidx.compose.foundation.layout.height
 28 | import androidx.compose.foundation.layout.width
 29 | import androidx.compose.foundation.shape.CircleShape
 30 | import androidx.compose.material3.MaterialTheme
 31 | import androidx.compose.runtime.Composable
 32 | import androidx.compose.runtime.getValue
 33 | import androidx.compose.runtime.mutableFloatStateOf
 34 | import androidx.compose.runtime.mutableStateOf
 35 | import androidx.compose.runtime.remember
 36 | import androidx.compose.runtime.setValue
 37 | import androidx.compose.ui.Alignment
 38 | import androidx.compose.ui.Modifier
 39 | import androidx.compose.ui.draw.clip
 40 | import androidx.compose.ui.input.pointer.pointerInput
 41 | import androidx.compose.ui.layout.onGloballyPositioned
 42 | import androidx.compose.ui.platform.LocalDensity
 43 | import androidx.compose.ui.unit.Dp
 44 | import androidx.compose.ui.unit.dp
 45 | import com.google.ai.edge.gallery.ui.theme.customColors
 46 | 
 47 | @Composable
 48 | fun VerticalSplitView(
 49 |   topView: @Composable () -> Unit,
 50 |   bottomView: @Composable () -> Unit,
 51 |   modifier: Modifier = Modifier,
 52 |   initialRatio: Float = 0.5f,
 53 |   minTopHeight: Dp = 250.dp,
 54 |   minBottomHeight: Dp = 200.dp,
 55 |   handleThickness: Dp = 20.dp,
 56 | ) {
 57 |   var splitRatio by remember { mutableFloatStateOf(initialRatio) }
 58 |   var columnHeightPx by remember { mutableFloatStateOf(0f) }
 59 |   var columnHeightDp by remember { mutableStateOf(0.dp) }
 60 |   val localDensity = LocalDensity.current
 61 | 
 62 |   Column(
 63 |     modifier =
 64 |       modifier.fillMaxSize().onGloballyPositioned { coordinates ->
 65 |         // Set column height using the LayoutCoordinates
 66 |         columnHeightPx = coordinates.size.height.toFloat()
 67 |         columnHeightDp = with(localDensity) { coordinates.size.height.toDp() }
 68 |       }
 69 |   ) {
 70 |     Box(modifier = Modifier.fillMaxWidth().weight(splitRatio)) { topView() }
 71 | 
 72 |     Box(
 73 |       modifier =
 74 |         Modifier.fillMaxWidth()
 75 |           .height(handleThickness)
 76 |           .background(MaterialTheme.customColors.agentBubbleBgColor)
 77 |           .pointerInput(Unit) {
 78 |             detectDragGestures { change, dragAmount ->
 79 |               val newTopHeightPx = columnHeightPx * splitRatio + dragAmount.y
 80 |               var newTopHeightDp = with(localDensity) { newTopHeightPx.toDp() }
 81 |               if (newTopHeightDp < minTopHeight) {
 82 |                 newTopHeightDp = minTopHeight
 83 |               }
 84 |               if (columnHeightDp - newTopHeightDp < minBottomHeight) {
 85 |                 newTopHeightDp = columnHeightDp - minBottomHeight
 86 |               }
 87 |               splitRatio = newTopHeightDp / columnHeightDp
 88 |               change.consume()
 89 |             }
 90 |           },
 91 |       contentAlignment = Alignment.Center,
 92 |     ) {
 93 |       Box(
 94 |         modifier =
 95 |           Modifier.width(32.dp)
 96 |             .height(4.dp)
 97 |             .clip(CircleShape)
 98 |             .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f))
 99 |       )
100 |     }
101 | 
102 |     Box(modifier = Modifier.fillMaxWidth().weight(1f - splitRatio)) { bottomView() }
103 |   }
104 | }
105 | 
106 | // @Preview(showBackground = true)
107 | // @Composable
108 | // fun VerticalSplitViewPreview() {
109 | //   GalleryTheme { VerticalSplitView(topView = { Text("top") }, bottomView = { Text("bottom") }) }
110 | // }
111 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelManager.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.ui.modelmanager
 18 | 
 19 | // import androidx.compose.ui.tooling.preview.Preview
 20 | // import com.google.ai.edge.gallery.ui.preview.PreviewModelManagerViewModel
 21 | // import com.google.ai.edge.gallery.ui.preview.TASK_TEST1
 22 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme
 23 | 
 24 | import androidx.activity.compose.BackHandler
 25 | import androidx.compose.foundation.layout.fillMaxSize
 26 | import androidx.compose.material3.ExperimentalMaterial3Api
 27 | import androidx.compose.material3.Scaffold
 28 | import androidx.compose.runtime.Composable
 29 | import androidx.compose.runtime.LaunchedEffect
 30 | import androidx.compose.runtime.derivedStateOf
 31 | import androidx.compose.runtime.getValue
 32 | import androidx.compose.runtime.remember
 33 | import androidx.compose.ui.Modifier
 34 | import com.google.ai.edge.gallery.GalleryTopAppBar
 35 | import com.google.ai.edge.gallery.data.AppBarAction
 36 | import com.google.ai.edge.gallery.data.AppBarActionType
 37 | import com.google.ai.edge.gallery.data.Model
 38 | import com.google.ai.edge.gallery.data.Task
 39 | 
 40 | /** A screen to manage models. */
 41 | @OptIn(ExperimentalMaterial3Api::class)
 42 | @Composable
 43 | fun ModelManager(
 44 |   task: Task,
 45 |   viewModel: ModelManagerViewModel,
 46 |   navigateUp: () -> Unit,
 47 |   onModelClicked: (Model) -> Unit,
 48 |   modifier: Modifier = Modifier,
 49 | ) {
 50 |   // Set title based on the task.
 51 |   var title = "${task.type.label} model"
 52 |   if (task.models.size != 1) {
 53 |     title += "s"
 54 |   }
 55 |   // Model count.
 56 |   val modelCount by remember {
 57 |     derivedStateOf {
 58 |       val trigger = task.updateTrigger.value
 59 |       if (trigger >= 0) {
 60 |         task.models.size
 61 |       } else {
 62 |         -1
 63 |       }
 64 |     }
 65 |   }
 66 | 
 67 |   // Navigate up when there are no models left.
 68 |   LaunchedEffect(modelCount) {
 69 |     if (modelCount == 0) {
 70 |       navigateUp()
 71 |     }
 72 |   }
 73 | 
 74 |   // Handle system's edge swipe.
 75 |   BackHandler { navigateUp() }
 76 | 
 77 |   Scaffold(
 78 |     modifier = modifier,
 79 |     topBar = {
 80 |       GalleryTopAppBar(
 81 |         title = title,
 82 |         leftAction = AppBarAction(actionType = AppBarActionType.NAVIGATE_UP, actionFn = navigateUp),
 83 |       )
 84 |     },
 85 |   ) { innerPadding ->
 86 |     ModelList(
 87 |       task = task,
 88 |       modelManagerViewModel = viewModel,
 89 |       contentPadding = innerPadding,
 90 |       onModelClicked = onModelClicked,
 91 |       modifier = Modifier.fillMaxSize(),
 92 |     )
 93 |   }
 94 | }
 95 | 
 96 | // @Preview
 97 | // @Composable
 98 | // fun ModelManagerPreview() {
 99 | //   val context = LocalContext.current
100 | 
101 | //   GalleryTheme {
102 | //     ModelManager(
103 | //       viewModel = PreviewModelManagerViewModel(context = context),
104 | //       onModelClicked = {},
105 | //       task = TASK_TEST1,
106 | //       navigateUp = {},
107 | //     )
108 | //   }
109 | // }
110 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/Color.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.theme
18 | 
19 | import androidx.compose.ui.graphics.Color
20 | 
21 | // val primaryLight = Color(0xFF32628D)
22 | val primaryLight = Color(0xFF1F1F1F)
23 | val onPrimaryLight = Color(0xFFFFFFFF)
24 | val primaryContainerLight = Color(0xFFD0E4FF)
25 | val onPrimaryContainerLight = Color(0xFF144A74)
26 | val secondaryLight = Color(0xFF526070)
27 | val onSecondaryLight = Color(0xFFFFFFFF)
28 | // val secondaryContainerLight = Color(0xFFD6E4F7)
29 | val secondaryContainerLight = Color(0xFFC2E7FF)
30 | val onSecondaryContainerLight = Color(0xFF3B4857)
31 | val tertiaryLight = Color(0xFF775A0B)
32 | val onTertiaryLight = Color(0xFFFFFFFF)
33 | val tertiaryContainerLight = Color(0xFFFFDF9B)
34 | val onTertiaryContainerLight = Color(0xFF5B4300)
35 | val errorLight = Color(0xFF904A43)
36 | val onErrorLight = Color(0xFFFFFFFF)
37 | val errorContainerLight = Color(0xFFFFDAD5)
38 | val onErrorContainerLight = Color(0xFF73342D)
39 | val backgroundLight = Color(0xFFF8F9FF)
40 | val onBackgroundLight = Color(0xFF191C20)
41 | val surfaceLight = Color(0xFFF8F9FF)
42 | val onSurfaceLight = Color(0xFF191C20)
43 | val surfaceVariantLight = Color(0xFFDEE3EB)
44 | val onSurfaceVariantLight = Color(0xFF42474E)
45 | val outlineLight = Color(0xFF73777F)
46 | val outlineVariantLight = Color(0xFFC2C7CF)
47 | val scrimLight = Color(0xFF000000)
48 | val inverseSurfaceLight = Color(0xFF2D3135)
49 | val inverseOnSurfaceLight = Color(0xFFEFF1F6)
50 | val inversePrimaryLight = Color(0xFF9DCAFC)
51 | val surfaceDimLight = Color(0xFFD8DAE0)
52 | val surfaceBrightLight = Color(0xFFF8F9FF)
53 | val surfaceContainerLowestLight = Color(0xFFFFFFFF)
54 | val surfaceContainerLowLight = Color(0xFFF2F3F9)
55 | val surfaceContainerLight = Color(0xFFECEEF4)
56 | val surfaceContainerHighLight = Color(0xFFE6E8EE)
57 | val surfaceContainerHighestLight = Color(0xFFE0E2E8)
58 | 
59 | val primaryDark = Color(0xFF9DCAFC)
60 | val onPrimaryDark = Color(0xFF003355)
61 | val primaryContainerDark = Color(0xFF144A74)
62 | val onPrimaryContainerDark = Color(0xFFD0E4FF)
63 | val secondaryDark = Color(0xFFBAC8DA)
64 | val onSecondaryDark = Color(0xFF243240)
65 | val secondaryContainerDark = Color(0xFF3B4857)
66 | val onSecondaryContainerDark = Color(0xFFD6E4F7)
67 | val tertiaryDark = Color(0xFFE8C26C)
68 | val onTertiaryDark = Color(0xFF3F2E00)
69 | val tertiaryContainerDark = Color(0xFF5B4300)
70 | val onTertiaryContainerDark = Color(0xFFFFDF9B)
71 | val errorDark = Color(0xFFFFB4AB)
72 | val onErrorDark = Color(0xFF561E19)
73 | val errorContainerDark = Color(0xFF73342D)
74 | val onErrorContainerDark = Color(0xFFFFDAD5)
75 | val backgroundDark = Color(0xFF101418)
76 | val onBackgroundDark = Color(0xFFE0E2E8)
77 | val surfaceDark = Color(0xFF101418)
78 | val onSurfaceDark = Color(0xFFE0E2E8)
79 | val surfaceVariantDark = Color(0xFF42474E)
80 | val onSurfaceVariantDark = Color(0xFFC2C7CF)
81 | val outlineDark = Color(0xFF8C9199)
82 | val outlineVariantDark = Color(0xFF42474E)
83 | val scrimDark = Color(0xFF000000)
84 | val inverseSurfaceDark = Color(0xFFE0E2E8)
85 | val inverseOnSurfaceDark = Color(0xFF2D3135)
86 | val inversePrimaryDark = Color(0xFF32628D)
87 | val surfaceDimDark = Color(0xFF101418)
88 | val surfaceBrightDark = Color(0xFF36393E)
89 | val surfaceContainerLowestDark = Color(0xFF0B0E12)
90 | val surfaceContainerLowDark = Color(0xFF191C20)
91 | val surfaceContainerDark = Color(0xFF1D2024)
92 | val surfaceContainerHighDark = Color(0xFF272A2F)
93 | val surfaceContainerHighestDark = Color(0xFF32353A)
94 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/ThemeSettings.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.theme
18 | 
19 | import androidx.compose.runtime.mutableStateOf
20 | import com.google.ai.edge.gallery.proto.Theme
21 | 
22 | object ThemeSettings {
23 |   val themeOverride = mutableStateOf<Theme>(Theme.THEME_AUTO)
24 | }
25 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/Type.kt:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | package com.google.ai.edge.gallery.ui.theme
18 | 
19 | import androidx.compose.material3.Typography
20 | import androidx.compose.ui.text.font.Font
21 | import androidx.compose.ui.text.font.FontFamily
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.unit.sp
24 | import com.google.ai.edge.gallery.R
25 | 
26 | val nunitoFontFamily =
27 |   FontFamily(
28 |     Font(R.font.nunito_regular, FontWeight.Normal),
29 |     Font(R.font.nunito_extralight, FontWeight.ExtraLight),
30 |     Font(R.font.nunito_light, FontWeight.Light),
31 |     Font(R.font.nunito_medium, FontWeight.Medium),
32 |     Font(R.font.nunito_semibold, FontWeight.SemiBold),
33 |     Font(R.font.nunito_bold, FontWeight.Bold),
34 |     Font(R.font.nunito_extrabold, FontWeight.ExtraBold),
35 |     Font(R.font.nunito_black, FontWeight.Black),
36 |   )
37 | 
38 | val baseline = Typography()
39 | 
40 | val AppTypography =
41 |   Typography(
42 |     displayLarge = baseline.displayLarge.copy(fontFamily = nunitoFontFamily),
43 |     displayMedium = baseline.displayMedium.copy(fontFamily = nunitoFontFamily),
44 |     displaySmall = baseline.displaySmall.copy(fontFamily = nunitoFontFamily),
45 |     headlineLarge = baseline.headlineLarge.copy(fontFamily = nunitoFontFamily),
46 |     headlineMedium = baseline.headlineMedium.copy(fontFamily = nunitoFontFamily),
47 |     headlineSmall = baseline.headlineSmall.copy(fontFamily = nunitoFontFamily),
48 |     titleLarge = baseline.titleLarge.copy(fontFamily = nunitoFontFamily),
49 |     titleMedium = baseline.titleMedium.copy(fontFamily = nunitoFontFamily),
50 |     titleSmall = baseline.titleSmall.copy(fontFamily = nunitoFontFamily),
51 |     bodyLarge = baseline.bodyLarge.copy(fontFamily = nunitoFontFamily),
52 |     bodyMedium = baseline.bodyMedium.copy(fontFamily = nunitoFontFamily),
53 |     bodySmall = baseline.bodySmall.copy(fontFamily = nunitoFontFamily),
54 |     labelLarge = baseline.labelLarge.copy(fontFamily = nunitoFontFamily),
55 |     labelMedium = baseline.labelMedium.copy(fontFamily = nunitoFontFamily),
56 |     labelSmall = baseline.labelSmall.copy(fontFamily = nunitoFontFamily),
57 |   )
58 | 
59 | val titleMediumNarrow =
60 |   baseline.titleMedium.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp)
61 | 
62 | val titleSmaller =
63 |   baseline.titleSmall.copy(
64 |     fontFamily = nunitoFontFamily,
65 |     fontSize = 12.sp,
66 |     fontWeight = FontWeight.Bold,
67 |   )
68 | 
69 | val labelSmallNarrow =
70 |   baseline.labelSmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp)
71 | 
72 | val labelSmallNarrowMedium =
73 |   baseline.labelSmall.copy(
74 |     fontFamily = nunitoFontFamily,
75 |     fontWeight = FontWeight.Medium,
76 |     letterSpacing = 0.0.sp,
77 |   )
78 | 
79 | val bodySmallNarrow = baseline.bodySmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp)
80 | 
81 | val bodySmallSemiBold =
82 |   baseline.bodySmall.copy(fontFamily = nunitoFontFamily, fontWeight = FontWeight.SemiBold)
83 | 
84 | val bodyMediumSemiBold =
85 |   baseline.bodyMedium.copy(fontFamily = nunitoFontFamily, fontWeight = FontWeight.SemiBold)
86 | 
87 | val bodySmallMediumNarrow =
88 |   baseline.bodySmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp, fontSize = 14.sp)
89 | 
90 | val bodySmallMediumNarrowBold =
91 |   baseline.bodySmall.copy(
92 |     fontFamily = nunitoFontFamily,
93 |     letterSpacing = 0.0.sp,
94 |     fontSize = 14.sp,
95 |     fontWeight = FontWeight.Bold,
96 |   )
97 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/proto/settings.proto:
--------------------------------------------------------------------------------
 1 | /* Copyright 2025 Google LLC
 2 | 
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 |     http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | 
16 | syntax = "proto3";
17 | 
18 | package com.google.ai.edge.gallery.proto;
19 | 
20 | option java_package = "com.google.ai.edge.gallery.proto";
21 | option java_multiple_files = true;
22 | 
23 | enum Theme {
24 |   THEME_UNSPECIFIED = 0;
25 | 
26 |   // Force to use light theme.
27 |   THEME_LIGHT = 1;
28 | 
29 |   // Force to use dark theme.
30 |   THEME_DARK = 2;
31 | 
32 |   // Use the system them setting on user's phone.
33 |   THEME_AUTO = 3;
34 | }
35 | 
36 | message AccessTokenData {
37 |   string access_token = 1;
38 |   string refresh_token = 2;
39 |   int64 expires_at_ms = 3;
40 | }
41 | 
42 | message ImportedModel {
43 |   string file_name = 1;
44 |   int64 file_size = 2;
45 | 
46 |   oneof config {
47 |     LlmConfig llm_config = 3;
48 |   }
49 | }
50 | 
51 | message LlmConfig {
52 |   repeated string compatible_accelerators = 1;
53 |   int32 default_max_tokens = 2;
54 |   int32 default_topk = 3;
55 |   float default_topp = 4;
56 |   float default_temperature = 5;
57 |   bool support_image = 6;
58 |   bool support_audio = 7;
59 | }
60 | 
61 | message Settings {
62 |   Theme theme = 1;
63 |   AccessTokenData access_token_data = 2;
64 |   repeated string text_input_history = 3;
65 |   repeated ImportedModel imported_model = 4;
66 |   bool is_tos_accepted = 5;
67 | }
68 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/chat_spark.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
18 |     android:width="38dp"
19 |     android:height="38dp"
20 |     android:viewportWidth="38"
21 |     android:viewportHeight="38">
22 |     <group>
23 |         <path
24 |             android:fillColor="#FF1967D2"
25 |             android:pathData="M9.32 21.5h15.3v-1.7c-0.44-0.1-0.87-0.24-1.28-0.4-0.41-0.17-0.82-0.37-1.2-0.57H9.32v2.67Zm0-4.89h10.1c-0.34-0.39-0.65-0.8-0.93-1.24-0.29-0.44-0.54-0.92-0.74-1.44H9.32v2.68Zm0-4.9h7.53V9.09H9.32v2.64Zm17.86 6.42c0-2.3-0.81-4.26-2.44-5.87-1.6-1.63-3.56-2.44-5.87-2.44 2.3 0 4.26-0.8 5.87-2.41 1.63-1.63 2.44-3.6 2.44-5.9 0 2.3 0.8 4.27 2.41 5.9 1.63 1.6 3.6 2.4 5.9 2.4-2.3 0-4.27 0.82-5.9 2.45-1.6 1.6-2.4 3.56-2.4 5.87Zm-21.4 1.04V25v2.49V5.5v2.1 2.22 7.34 2.99-0.24-0.74ZM3.12 33.9V5.5c0-0.72 0.26-1.34 0.77-1.86 0.55-0.54 1.18-0.81 1.9-0.81h13.83c-0.36 0.38-0.7 0.8-1 1.24-0.32 0.44-0.58 0.92-0.78 1.43H5.79V27.5L8.16 25H31.5v-5.83c0.51-0.2 1-0.46 1.43-0.77 0.44-0.31 0.86-0.65 1.24-1.01V25c0 0.72-0.27 1.36-0.81 1.9-0.52 0.52-1.14 0.78-1.86 0.78H9.32l-6.21 6.21Z"/>
26 |     </group>
27 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/circle.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
18 |     android:viewportWidth="63"
19 |     android:viewportHeight="63"
20 |     android:width="63dp"
21 |     android:height="63dp">
22 |     <path
23 |         android:pathData="M63 31.5C63 14.103 48.897 0 31.5 0C14.103 0 0 14.103 0 31.5C0 48.897 14.103 63 31.5 63C48.897 63 63 48.897 63 31.5Z"
24 |         android:fillColor="#FFFFFF"
25 |         android:fillAlpha="1" />
26 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/double_circle.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
18 |     android:viewportWidth="62"
19 |     android:viewportHeight="62"
20 |     android:width="62dp"
21 |     android:height="62dp">
22 |     <path
23 |         android:pathData="M7.78628 54.2125C-2.59542 43.8291 -2.59542 26.9941 7.78628 16.6107L16.6114 7.78754C26.9931 -2.59584 43.8287 -2.59584 54.2137 7.78754C64.5954 18.1709 64.5954 35.0059 54.2137 45.3893L45.3886 54.2125C35.0069 64.5958 18.1713 64.5958 7.78628 54.2125Z"
24 |         android:fillColor="#FFFFFF"
25 |         android:fillAlpha="1" />
26 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/four_circle.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
18 |     android:viewportWidth="59"
19 |     android:viewportHeight="59"
20 |     android:width="59dp"
21 |     android:height="59dp">
22 |     <path
23 |         android:pathData="M1.3077 21.3393C-4.19593 8.66644 8.66702 -4.19605 21.3396 1.30784L23.4364 2.21791C27.3032 3.89848 31.6966 3.89848 35.5666 2.21791L37.6602 1.30784C50.3359 -4.19605 63.1957 8.66644 57.6921 21.3393L56.7817 23.4344C55.1036 27.3037 55.1036 31.6964 56.7817 35.5654L57.6921 37.6609C63.1957 50.3337 50.3359 63.1962 37.6602 57.692L35.5666 56.782C31.6966 55.1017 27.3032 55.1017 23.4364 56.782L21.3396 57.692C8.66702 63.1962 -4.19593 50.3337 1.3077 37.6609L2.21809 35.5654C3.89932 31.6964 3.89932 27.3037 2.21809 23.4344L1.3077 21.3393Z"
24 |         android:fillColor="#FFFFFF"
25 |         android:fillAlpha="1" />
26 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
18 |     xmlns:aapt="http://schemas.android.com/aapt"
19 |     android:width="108dp"
20 |     android:height="108dp"
21 |     android:viewportWidth="108"
22 |     android:viewportHeight="108">
23 |     <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
24 |         <aapt:attr name="android:fillColor">
25 |             <gradient
26 |                 android:endX="85.84757"
27 |                 android:endY="92.4963"
28 |                 android:startX="42.9492"
29 |                 android:startY="49.59793"
30 |                 android:type="linear">
31 |                 <item
32 |                     android:color="#44000000"
33 |                     android:offset="0.0" />
34 |                 <item
35 |                     android:color="#00000000"
36 |                     android:offset="1.0" />
37 |             </gradient>
38 |         </aapt:attr>
39 |     </path>
40 |     <path
41 |         android:fillColor="#FFFFFF"
42 |         android:fillType="nonZero"
43 |         android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
44 |         android:strokeWidth="1"
45 |         android:strokeColor="#00000000" />
46 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/image_spark.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
18 | android:width="38dp"
19 | android:height="38dp"
20 | android:viewportWidth="38"
21 | android:viewportHeight="38">
22 | <group>
23 |     <path
24 |         android:fillColor="#FF34A853"
25 |         android:pathData="M9.24 26.2h10.1l5-5-2.05-2.69-4.9 6.45-3.56-4.81-4.59 6.05Zm-1.9 6.14c-0.73 0-1.36-0.26-1.9-0.78-0.52-0.54-0.78-1.17-0.78-1.9V16.53h1.28 1.4v13.13h11.73v2.68H7.34Zm22.6-16.7V7.06H16.82v-1.4-1.28h13.12c0.73 0 1.35 0.27 1.87 0.81 0.54 0.52 0.81 1.14 0.81 1.87v8.58h-1.36-1.32Zm-8.2 16.7v-4.78l8.59-8.54c0.23-0.23 0.5-0.4 0.78-0.5 0.28-0.1 0.57-0.16 0.85-0.16 0.31 0 0.61 0.06 0.9 0.2 0.28 0.1 0.54 0.27 0.77 0.5l1.44 1.44c0.23 0.23 0.4 0.49 0.5 0.77 0.1 0.29 0.16 0.57 0.16 0.86 0 0.28-0.07 0.58-0.2 0.89-0.1 0.28-0.27 0.54-0.5 0.78l-8.5 8.54h-4.78ZM33.4 22.13l-1.44-1.44 1.44 1.44ZM24.08 30h1.47l4.7-4.74-1.43-1.43-4.74 4.7V30Zm5.47-5.48l-0.73-0.7 1.43 1.44-0.7-0.74Zm-20.97-9.4c0-1.88-0.66-3.49-1.98-4.81C5.28 8.97 3.68 8.3 1.8 8.3c1.89 0 3.5-0.66 4.81-1.98C7.92 5 8.58 3.39 8.58 1.5c0 1.9 0.66 3.5 1.98 4.82 1.35 1.32 2.97 1.98 4.86 1.98-1.9 0-3.51 0.67-4.86 2.02-1.32 1.32-1.98 2.93-1.98 4.82Z"/>
26 | </group>
27 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/logo.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
18 |     android:width="196dp"
19 |     android:height="199dp"
20 |     android:viewportWidth="196"
21 |     android:viewportHeight="199">
22 |     <path
23 |         android:fillColor="#FFF6B704"
24 |         android:pathData="M37.46 2.58c4.46-3.44 10.66-3.44 15.12 0l11.2 8.64c0.87 0.66 1.82 1.21 2.82 1.63l13.06 5.43c5.2 2.15 8.3 7.55 7.56 13.14L85.37 45.5c-0.14 1.08-0.14 2.18 0 3.26l1.85 14.07c0.74 5.59-2.36 10.98-7.56 13.15L66.6 81.39c-1 0.42-1.95 0.96-2.81 1.63l-11.2 8.64c-4.47 3.44-10.67 3.44-15.13 0l-11.2-8.64c-0.87-0.67-1.82-1.21-2.82-1.63l-13.06-5.42c-5.2-2.17-8.3-7.56-7.56-13.15l1.85-14.07c0.14-1.08 0.14-2.18 0-3.26L2.82 31.42c-0.74-5.6 2.37-10.99 7.56-13.14l13.06-5.43c1-0.42 1.95-0.97 2.81-1.63l11.21-8.64Z"/>
25 |     <path
26 |         android:fillColor="#FFE54335"
27 |         android:pathData="M118.52 77.54c-14.84-14.85-14.84-38.93 0-53.78l12.6-12.62c14.85-14.85 38.9-14.85 53.75 0 14.84 14.85 14.84 38.93 0 53.78l-12.61 12.62c-14.84 14.85-38.9 14.85-53.74 0Z"/>
28 |     <path
29 |         android:fillColor="#FF34A353"
30 |         android:pathData="M90.04 153.95c0-24.89-20.15-45.06-45.02-45.06C20.16 108.9 0 129.06 0 153.95 0 178.83 20.16 199 45.02 199c24.87 0 45.02-20.17 45.02-45.05Z"/>
31 |     <path
32 |         android:fillColor="#FF4280EF"
33 |         android:pathData="M109.25 145.13c-7.86-18.12 10.52-36.52 28.64-28.64l3 1.3c5.52 2.4 11.8 2.4 17.33 0l3-1.3c18.1-7.88 36.49 10.52 28.62 28.64l-1.3 3c-2.4 5.54-2.4 11.82 0 17.35l1.3 3c7.87 18.13-10.51 36.52-28.63 28.65l-2.99-1.3c-5.53-2.4-11.81-2.4-17.34 0l-3 1.3c-18.1 7.87-36.5-10.53-28.63-28.65l1.3-3c2.41-5.53 2.41-11.81 0-17.35l-1.3-3Z"/>
34 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/pantegon.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
18 |     android:viewportWidth="59"
19 |     android:viewportHeight="63"
20 |     android:width="59dp"
21 |     android:height="63dp">
22 |     <path
23 |         android:pathData="M24.2282 1.72389C27.3373 -0.574629 31.6627 -0.574629 34.7718 1.72389L42.5861 7.50056C43.188 7.94527 43.8479 8.31221 44.5486 8.59132L53.6533 12.2177C57.2755 13.6601 59.4383 17.266 58.9251 21.007L57.6351 30.41C57.5359 31.1342 57.5359 31.8674 57.6351 32.5916L58.9251 41.9953C59.4383 45.7342 57.2755 49.3412 53.6533 50.7853L44.5486 54.4096C43.8479 54.6908 43.188 55.0553 42.5861 55.4997L34.7718 61.2764C31.6627 63.5745 27.3373 63.5745 24.2282 61.2764L16.4139 55.4997C15.812 55.0553 15.1521 54.6908 14.4513 54.4096L5.34671 50.7853C1.72447 49.3412 -0.438263 45.7342 0.0749229 41.9953L1.36492 32.5916C1.46446 31.8674 1.46446 31.1342 1.36492 30.41L0.0749229 21.007C-0.438263 17.266 1.72447 13.6601 5.34671 12.2177L14.4513 8.59132C15.1521 8.31221 15.812 7.94527 16.4139 7.50056L24.2282 1.72389Z"
24 |         android:fillColor="#FFFFFF"
25 |         android:fillAlpha="1" />
26 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/drawable/text_spark.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android"
18 | android:width="37dp"
19 | android:height="36dp"
20 | android:viewportWidth="37"
21 | android:viewportHeight="36">
22 | <group>
23 |     <path
24 |         android:fillColor="#FFE37400"
25 |         android:pathData="M19.8 33.25l-6.34-6.37 1.87-1.86 4.46 4.47 9.13-9.1 1.86 1.91L19.8 33.25ZM2.7 24.1l7.7-20.31h3.03l7.68 20.3h-2.99l-1.98-5.35H7.6L5.66 24.1H2.7Zm5.8-7.89h6.75L11.95 7h-0.08L8.5 16.2Zm18.28 1.29c0-2.3-0.81-4.26-2.44-5.87-1.6-1.63-3.56-2.44-5.87-2.44 2.3 0 4.26-0.8 5.87-2.41 1.63-1.63 2.44-3.6 2.44-5.9 0 2.3 0.8 4.27 2.41 5.9 1.63 1.6 3.6 2.4 5.9 2.4-2.3 0-4.27 0.82-5.9 2.45-1.6 1.6-2.4 3.56-2.4 5.87Z"/>
26 | </group>
27 | </vector>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_black.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_bold.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_extrabold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_extrabold.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_extralight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_extralight.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_light.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_medium.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_regular.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/font/nunito_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_semibold.ttf


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
18 |   <background android:drawable="@mipmap/ic_launcher_background"/>
19 |   <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
20 |   <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
21 | </adaptive-icon>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_background.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_background.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | 
18 | <resources xmlns:tools="http://schemas.android.com/tools">
19 |   <style name="Theme.Gallery.OssLicenses" parent="Theme.AppCompat">
20 |     <item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
21 |   </style>
22 | </resources>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | 
18 | 
19 | <resources>
20 |     <dimen name="model_selector_height">54dp</dimen>
21 |     <dimen name="chat_bubble_corner_radius">24dp</dimen>
22 | </resources>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | 
18 | <resources>
19 |     <color name="ic_launcher_background">#ffffff</color>
20 | </resources>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
 1 | <!--
 2 |  Copyright 2025 Google LLC
 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 | <resources>
18 |     <!-- TODO(jingjin): Add translation support when i18n is ready. -->
19 |     <string name="app_name" translatable="false">Google AI Edge Gallery</string>
20 |     <string name="model_manager" translatable="false">Model Manager</string>
21 |     <string name="downloaded_size" translatable="false">%1$s downloaded</string>
22 |     <string name="cancel" translatable="false">Cancel</string>
23 |     <string name="ok" translatable="false">OK</string>
24 |     <string name="close" translatable="false">Close</string>
25 |     <string name="confirm_delete_model_dialog_title" translatable="false">Delete download</string>
26 |     <string name="confirm_delete_model_dialog_content" translatable="false">Are you sure you want to delete the downloaded model \"%s\"?</string>
27 |     <string name="notification_title_success" translatable="false">Model download succeeded</string>
28 |     <string name="notification_content_success" translatable="false">Model \"%s\" has been downloaded</string>
29 |     <string name="notification_title_fail" translatable="false">Model download failed</string>
30 |     <string name="notification_content_fail" translatable="false">Failed to download model \"%s\"</string>
31 |     <string name="chat_textinput_placeholder" translatable="false">Type message…</string>
32 |     <string name="chat_you" translatable="false">You</string>
33 |     <string name="chat_llm_agent_name" translatable="false">LLM</string>
34 |     <string name="chat_generic_agent_name" translatable="false">Model</string>
35 |     <string name="chat_generic_result_name" translatable="false">Result</string>
36 |     <string name="model_not_downloaded_msg" translatable="false">Model not downloaded yet</string>
37 |     <string name="model_is_initializing_msg" translatable="false">Initializing model…</string>
38 |     <string name="settings_dialog_tos_title" translatable="false">Terms of services</string>
39 |     <string name="text_input_placeholder_text_classification" translatable="false">Type movie review to classify…</string>
40 |     <string name="text_image_generation_text_field_placeholder" translatable="false">Type prompt…</string>
41 |     <string name="text_input_placeholder_llm_chat" translatable="false">Type prompt…</string>
42 |     <string name="tos_sheet_title_app_name" translatable="false">Google AI Edge Gallery App</string>
43 |     <string name="tos_sheet_title_tos" translatable="false">Terms of Service</string>
44 |     <string name="tos_sheet_view_full_tos" translatable="false">View the full Terms of Service</string>
45 |     <string name="tos_sheet_view_accept_button_label" translatable="false">Accept and Continue</string>
46 |     <string name="run_again" translatable="false">Run again</string>
47 |     <string name="benchmark" translatable="false">Run benchmark</string>
48 |     <string name="warming_up" translatable="false">warming up…</string>
49 |     <string name="running" translatable="false">running</string>
50 | </resources>
51 | 


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | 
18 | <resources xmlns:tools="http://schemas.android.com/tools">
19 |     <style name="Theme.Gallery" parent="android:Theme.Material.Light.NoActionBar" />
20 |     <style name="Theme.Gallery.SplashScreen" parent="Theme.SplashScreen">
21 |         <item name="windowSplashScreenBackground">#2A2A34</item>
22 |         <item name="postSplashScreenTheme">@style/Theme.Gallery</item>
23 |     </style>
24 |     <style name="Theme.Gallery.OssLicenses" parent="Theme.AppCompat.Light.DarkActionBar">
25 |         <item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
26 |     </style>
27 | </resources>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?><!--
 2 |  Copyright 2025 Google LLC
 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 |    Sample backup rules file; uncomment and customize as necessary.
17 |    See https://developer.android.com/guide/topics/data/autobackup
18 |    for details.
19 |    Note: This file is ignored for devices older that API 31
20 |    See https://developer.android.com/about/versions/12/backup-restore
21 | -->
22 | <full-backup-content>
23 |     <!--
24 |    <include domain="sharedpref" path="."/>
25 |    <exclude domain="sharedpref" path="device.xml"/>
26 | -->
27 | </full-backup-content>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?><!--
 2 |  Copyright 2025 Google LLC
 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 |    Sample data extraction rules file; uncomment and customize as necessary.
17 |    See https://developer.android.com/about/versions/12/backup-restore#xml-changes
18 |    for details.
19 | -->
20 | <data-extraction-rules>
21 |     <cloud-backup>
22 |         <!-- TODO: Use <include> and <exclude> to control what is backed up.
23 |         <include .../>
24 |         <exclude .../>
25 |         -->
26 |     </cloud-backup>
27 |     <!--
28 |     <device-transfer>
29 |         <include .../>
30 |         <exclude .../>
31 |     </device-transfer>
32 |     -->
33 | </data-extraction-rules>


--------------------------------------------------------------------------------
/Android/src/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!--
 3 |  Copyright 2025 Google LLC
 4 | 
 5 |  Licensed under the Apache License, Version 2.0 (the "License");
 6 |  you may not use this file except in compliance with the License.
 7 |  You may obtain a copy of the License at
 8 | 
 9 |      http://www.apache.org/licenses/LICENSE-2.0
10 | 
11 |  Unless required by applicable law or agreed to in writing, software
12 |  distributed under the License is distributed on an "AS IS" BASIS,
13 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  See the License for the specific language governing permissions and
15 |  limitations under the License.
16 | -->
17 | 
18 | <paths>
19 |     <cache-path
20 |         name="cache_pictures"
21 |         path="/" />
22 | </paths>


--------------------------------------------------------------------------------
/Android/src/app/src/test/java/com/google/ai/edge/gallery/data/ModelAllowlistTest.kt:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright 2025 Google LLC
  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 | package com.google.ai.edge.gallery.data
 18 | 
 19 | import org.junit.Assert.assertEquals
 20 | import org.junit.Assert.assertFalse
 21 | import org.junit.Assert.assertTrue
 22 | import org.junit.Test
 23 | import org.junit.runner.RunWith
 24 | import org.junit.runners.JUnit4
 25 | 
 26 | @RunWith(JUnit4::class)
 27 | class ModelAllowlistTest {
 28 |   @Test
 29 |   fun toModel_success() {
 30 |     val modelName = "test_model"
 31 |     val modelId = "test_model_id"
 32 |     val modelFile = "test_model_file"
 33 |     val description = "test description"
 34 |     val sizeInBytes = 100L
 35 |     val version = "20250623"
 36 |     val topK = 10
 37 |     val topP = 0.5f
 38 |     val temperature = 0.1f
 39 |     val maxTokens = 1000
 40 |     val accelerators = "gpu,cpu"
 41 |     val taskTypes = listOf("llm_chat", "ask_image")
 42 |     val estimatedPeakMemoryInBytes = 300L
 43 | 
 44 |     val allowedModel =
 45 |       AllowedModel(
 46 |         name = modelName,
 47 |         modelId = modelId,
 48 |         modelFile = modelFile,
 49 |         description = description,
 50 |         sizeInBytes = sizeInBytes,
 51 |         version = version,
 52 |         defaultConfig =
 53 |           DefaultConfig(
 54 |             topK = topK,
 55 |             topP = topP,
 56 |             temperature = temperature,
 57 |             maxTokens = maxTokens,
 58 |             accelerators = accelerators,
 59 |           ),
 60 |         taskTypes = taskTypes,
 61 |         llmSupportImage = true,
 62 |         llmSupportAudio = true,
 63 |         estimatedPeakMemoryInBytes = estimatedPeakMemoryInBytes,
 64 |       )
 65 |     val model = allowedModel.toModel()
 66 | 
 67 |     // Check that basic fields are set correctly.
 68 |     assertEquals(model.name, modelName)
 69 |     assertEquals(model.version, version)
 70 |     assertEquals(model.info, description)
 71 |     assertEquals(
 72 |       model.url,
 73 |       "https://huggingface.co/test_model_id/resolve/main/test_model_file?download=true",
 74 |     )
 75 |     assertEquals(model.sizeInBytes, sizeInBytes)
 76 |     assertEquals(model.estimatedPeakMemoryInBytes, estimatedPeakMemoryInBytes)
 77 |     assertEquals(model.downloadFileName, modelFile)
 78 |     assertFalse(model.showBenchmarkButton)
 79 |     assertFalse(model.showRunAgainButton)
 80 |     assertTrue(model.llmSupportImage)
 81 |     assertTrue(model.llmSupportAudio)
 82 | 
 83 |     // Check that configs are set correctly.
 84 |     assertEquals(model.configs.size, 5)
 85 | 
 86 |     // A label for showing max tokens (non-changeable).
 87 |     assertTrue(model.configs[0] is LabelConfig)
 88 |     assertEquals((model.configs[0] as LabelConfig).defaultValue, "$maxTokens")
 89 | 
 90 |     // A slider for topK.
 91 |     assertTrue(model.configs[1] is NumberSliderConfig)
 92 |     assertEquals((model.configs[1] as NumberSliderConfig).defaultValue, topK.toFloat())
 93 | 
 94 |     // A slider for topP.
 95 |     assertTrue(model.configs[2] is NumberSliderConfig)
 96 |     assertEquals((model.configs[2] as NumberSliderConfig).defaultValue, topP)
 97 | 
 98 |     // A slider for temperature.
 99 |     assertTrue(model.configs[3] is NumberSliderConfig)
100 |     assertEquals((model.configs[3] as NumberSliderConfig).defaultValue, temperature)
101 | 
102 |     // A segmented button for accelerators.
103 |     assertTrue(model.configs[4] is SegmentedButtonConfig)
104 |     assertEquals((model.configs[4] as SegmentedButtonConfig).defaultValue, "GPU")
105 |     assertEquals((model.configs[4] as SegmentedButtonConfig).options, listOf("GPU", "CPU"))
106 |   }
107 | }
108 | 


--------------------------------------------------------------------------------
/Android/src/build.gradle.kts:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
18 | plugins {
19 |   alias(libs.plugins.android.application) apply false
20 |   alias(libs.plugins.google.services) apply false
21 |   alias(libs.plugins.kotlin.android) apply false
22 |   alias(libs.plugins.kotlin.compose) apply false
23 |   alias(libs.plugins.hilt.application) apply false
24 | }
25 | 


--------------------------------------------------------------------------------
/Android/src/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.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true


--------------------------------------------------------------------------------
/Android/src/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/gradle/wrapper/gradle-wrapper.jar


--------------------------------------------------------------------------------
/Android/src/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 02 09:29:13 PST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 | 


--------------------------------------------------------------------------------
/Android/src/gradlew.bat:
--------------------------------------------------------------------------------
 1 | @rem
 2 | @rem Copyright 2015 the original author or authors.
 3 | @rem
 4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
 5 | @rem you may not use this file except in compliance with the License.
 6 | @rem You may obtain a copy of the License at
 7 | @rem
 8 | @rem      https://www.apache.org/licenses/LICENSE-2.0
 9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | 
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem  Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 | 
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 | 
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 | 
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 | 
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 | 
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 | 
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 | 
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 | 
51 | goto fail
52 | 
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 | 
57 | if exist "%JAVA_EXE%" goto execute
58 | 
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 | 
65 | goto fail
66 | 
67 | :execute
68 | @rem Setup the command line
69 | 
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 | 
72 | 
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 | 
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 | 
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 | 
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 | 
89 | :omega
90 | 


--------------------------------------------------------------------------------
/Android/src/settings.gradle.kts:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Copyright 2025 Google LLC
 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 | pluginManagement {
18 |   repositories {
19 |     google {
20 |       content {
21 |         includeGroupByRegex("com\\.android.*")
22 |         includeGroupByRegex("com\\.google.*")
23 |         includeGroupByRegex("androidx.*")
24 |       }
25 |     }
26 |     mavenCentral()
27 |     gradlePluginPortal()
28 |   }
29 |   resolutionStrategy {
30 |     eachPlugin {
31 |       if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") {
32 |         useModule("com.google.android.gms:oss-licenses-plugin:0.10.6")
33 |       }
34 |     }
35 |   }
36 | }
37 | 
38 | dependencyResolutionManagement {
39 |   repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
40 |   repositories {
41 |     //        mavenLocal()
42 |     google()
43 |     mavenCentral()
44 |   }
45 | }
46 | 
47 | rootProject.name = "AI Edge Gallery"
48 | 
49 | include(":app")
50 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | The repository is not currently ready for code contributions. We will
2 | make a separate announcement when we are ready for OSS users to make
3 | contributions to it.
4 | 
5 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Google AI Edge Gallery ✨
 2 | 
 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
 4 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/google-ai-edge/gallery)](https://github.com/google-ai-edge/gallery/releases)
 5 | 
 6 | **Explore, Experience, and Evaluate the Future of On-Device Generative AI with Google AI Edge.**
 7 | 
 8 | The Google AI Edge Gallery is an experimental app that puts the power of cutting-edge Generative AI models directly into your hands, running entirely on your Android *(available now)* and iOS *(coming soon)* devices. Dive into a world of creative and practical AI use cases, all running locally, without needing an internet connection once the model is loaded. Experiment with different models, chat, ask questions with images, explore prompts, and more!
 9 | 
10 | **Overview**
11 | <img width="1532" alt="Overview" src="https://github.com/user-attachments/assets/4f2702d7-91a0-4eb3-aa76-58bc8e7089c6" />
12 | 
13 | **Ask Image**
14 | <img width="1532" alt="Ask Image" src="https://github.com/user-attachments/assets/e2b5b41b-fed0-4a7c-9547-2abb1c10962c" />
15 | 
16 | **Prompt Lab**
17 | <img width="1532" alt="Prompt Lab" src="https://github.com/user-attachments/assets/22e459d0-0365-4a92-8570-fb59d4d1e320" />
18 | 
19 | **AI Chat**
20 | <img width="1532" alt="AI Chat" src="https://github.com/user-attachments/assets/edaa4f89-237a-4b84-b647-b3c4631f09dc" />
21 | 
22 | ## ✨ Core Features
23 | 
24 | *   **📱 Run Locally, Fully Offline:** Experience the magic of GenAI without an internet connection. All processing happens directly on your device.
25 | *   **🤖 Choose Your Model:** Easily switch between different models from Hugging Face and compare their performance.
26 | *   **🖼️ Ask Image:** Upload an image and ask questions about it. Get descriptions, solve problems, or identify objects.
27 | *   **✍️ Prompt Lab:** Summarize, rewrite, generate code, or use freeform prompts to explore single-turn LLM use cases.
28 | *   **💬 AI Chat:** Engage in multi-turn conversations.
29 | *   **📊 Performance Insights:** Real-time benchmarks (TTFT, decode speed, latency).
30 | *   **🧩 Bring Your Own Model:** Test your local LiteRT `.task` models.
31 | *   **🔗 Developer Resources:** Quick links to model cards and source code.
32 | 
33 | ## 🏁 Get Started in Minutes!
34 | 
35 | 1.  **Download the App:** Grab the [**latest APK**](https://github.com/google-ai-edge/gallery/releases/latest/download/ai-edge-gallery.apk).
36 | 2.  **Install & Explore:** For detailed installation instructions (including for corporate devices) and a full user guide, head over to our [**Project Wiki**](https://github.com/google-ai-edge/gallery/wiki)!
37 | 
38 | ## 🛠️ Technology Highlights
39 | 
40 | *   **Google AI Edge:** Core APIs and tools for on-device ML.
41 | *   **LiteRT:** Lightweight runtime for optimized model execution.
42 | *   **LLM Inference API:** Powering on-device Large Language Models.
43 | *   **Hugging Face Integration:** For model discovery and download.
44 | 
45 | ## 🤝 Feedback
46 | 
47 | This is an **experimental Alpha release**, and your input is crucial!
48 | 
49 | *   🐞 **Found a bug?** [Report it here!](https://github.com/google-ai-edge/gallery/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D)
50 | *   💡 **Have an idea?** [Suggest a feature!](https://github.com/google-ai-edge/gallery/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BFEATURE%5D)
51 | 
52 | ## 📄 License
53 | 
54 | Licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details.
55 | 
56 | ## 🔗 Useful Links
57 | 
58 | *   [**Project Wiki (Detailed Guides)**](https://github.com/google-ai-edge/gallery/wiki)
59 | *   [Hugging Face LiteRT Community](https://huggingface.co/litert-community)
60 | *   [LLM Inference guide for Android](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/android)
61 | *   [Google AI Edge Documentation](https://ai.google.dev/edge)
62 | 


--------------------------------------------------------------------------------
/model_allowlist.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "models": [
 3 |     {
 4 |       "name": "Gemma-3n-E2B-it-int4",
 5 |       "modelId": "google/gemma-3n-E2B-it-litert-preview",
 6 |       "modelFile": "gemma-3n-E2B-it-int4.task",
 7 |       "description": "Preview version of [Gemma 3n E2B](https://ai.google.dev/gemma/docs/gemma-3n) ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference). The current checkpoint only supports text and vision input, with 4096 context length.",
 8 |       "sizeInBytes": 3136226711,
 9 |       "estimatedPeakMemoryInBytes": 5905580032,
10 |       "version": "20250520",
11 |       "llmSupportImage": true,
12 |       "defaultConfig": {
13 |         "topK": 64,
14 |         "topP": 0.95,
15 |         "temperature": 1.0,
16 |         "maxTokens": 4096,
17 |         "accelerators": "cpu,gpu"
18 |       },
19 |       "taskTypes": ["llm_chat", "llm_prompt_lab", "llm_ask_image"]
20 |     },
21 |     {
22 |       "name": "Gemma-3n-E4B-it-int4",
23 |       "modelId": "google/gemma-3n-E4B-it-litert-preview",
24 |       "modelFile": "gemma-3n-E4B-it-int4.task",
25 |       "description": "Preview version of [Gemma 3n E4B](https://ai.google.dev/gemma/docs/gemma-3n) ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference). The current checkpoint only supports text and vision input, with 4096 context length.",
26 |       "sizeInBytes": 4405655031,
27 |       "estimatedPeakMemoryInBytes": 6979321856,
28 |       "version": "20250520",
29 |       "llmSupportImage": true,
30 |       "defaultConfig": {
31 |         "topK": 64,
32 |         "topP": 0.95,
33 |         "temperature": 1.0,
34 |         "maxTokens": 4096,
35 |         "accelerators": "cpu,gpu"
36 |       },
37 |       "taskTypes": ["llm_chat", "llm_prompt_lab", "llm_ask_image"]
38 |     },
39 |     {
40 |       "name": "Gemma3-1B-IT q4",
41 |       "modelId": "litert-community/Gemma3-1B-IT",
42 |       "modelFile": "Gemma3-1B-IT_multi-prefill-seq_q4_ekv2048.task",
43 |       "description": "A variant of [google/Gemma-3-1B-IT](https://huggingface.co/google/Gemma-3-1B-IT) with 4-bit quantization ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference)",
44 |       "sizeInBytes": 554661246,
45 |       "estimatedPeakMemoryInBytes": 2147483648,
46 |       "version": "20250514",
47 |       "defaultConfig": {
48 |         "topK": 64,
49 |         "topP": 0.95,
50 |         "temperature": 1.0,
51 |         "maxTokens": 1024,
52 |         "accelerators": "gpu,cpu"
53 |       },
54 |       "taskTypes": ["llm_chat", "llm_prompt_lab"]
55 |     },
56 |     {
57 |       "name": "Qwen2.5-1.5B-Instruct q8",
58 |       "modelId": "litert-community/Qwen2.5-1.5B-Instruct",
59 |       "modelFile": "Qwen2.5-1.5B-Instruct_multi-prefill-seq_q8_ekv1280.task",
60 |       "description": "A variant of [Qwen/Qwen2.5-1.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct) with 8-bit quantization ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference)",
61 |       "sizeInBytes": 1625493432,
62 |       "estimatedPeakMemoryInBytes": 2684354560,
63 |       "version": "20250514",
64 |       "defaultConfig": {
65 |         "topK": 40,
66 |         "topP": 0.95,
67 |         "temperature": 1.0,
68 |         "maxTokens": 1024,
69 |         "accelerators": "cpu"
70 |       },
71 |       "taskTypes": ["llm_chat", "llm_prompt_lab"]
72 |     }
73 |   ]
74 | }
75 | 


--------------------------------------------------------------------------------