├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── android.yml │ └── baseline-profile.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── skydoves │ │ │ └── gemini │ │ │ ├── GeminiApp.kt │ │ │ ├── MainActivity.kt │ │ │ ├── di │ │ │ ├── ChatEntryPoint.kt │ │ │ └── ChatModule.kt │ │ │ ├── initializer │ │ │ ├── StreamChatInitializer.kt │ │ │ └── StreamLogInitializer.kt │ │ │ ├── navigation │ │ │ ├── GeminiNavHost.kt │ │ │ └── GeminiNavigation.kt │ │ │ └── ui │ │ │ └── GeminiMain.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── release │ └── generated │ └── baselineProfiles │ ├── baseline-prof.txt │ └── startup-prof.txt ├── baseline-profile ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── skydoves │ └── gemini │ └── baselineprofile │ └── BaselineProfileGenerator.kt ├── build-logic ├── convention │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── AndroidApplicationComposeConventionPlugin.kt │ │ ├── AndroidApplicationConventionPlugin.kt │ │ ├── AndroidFeatureConventionPlugin.kt │ │ ├── AndroidHiltConventionPlugin.kt │ │ ├── AndroidLibraryComposeConventionPlugin.kt │ │ ├── AndroidLibraryConventionPlugin.kt │ │ ├── SpotlessConventionPlugin.kt │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ ├── AndroidCompose.kt │ │ └── KotlinAndroid.kt ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── settings.gradle.kts ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Configurations.kt ├── core ├── data │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── core │ │ └── data │ │ ├── chat │ │ └── ChatModels.kt │ │ ├── coroutines │ │ ├── Flow.kt │ │ └── WhileSubscribedOrRetained.kt │ │ ├── di │ │ └── DataModule.kt │ │ ├── repository │ │ ├── ChannelRepository.kt │ │ ├── ChannelRepositoryImpl.kt │ │ ├── ChatRepository.kt │ │ └── ChatRepositoryImpl.kt │ │ └── utils │ │ └── Strings.kt ├── database │ ├── .gitignore │ ├── build.gradle.kts │ ├── schemas │ │ └── com.skydoves.gemini.core.database.GeminiDatabase │ │ │ └── 1.json │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── core │ │ └── database │ │ ├── GeminiChannelEntity.kt │ │ ├── GeminiDao.kt │ │ ├── GeminiDatabase.kt │ │ ├── GeminiModelConverter.kt │ │ └── di │ │ └── DatabaseModule.kt ├── datastore │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── core │ │ └── datastore │ │ ├── DataStore.kt │ │ ├── PreferenceDataStore.kt │ │ └── di │ │ └── DataStoreModule.kt ├── designsystem │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── com │ │ │ └── skydoves │ │ │ └── gemini │ │ │ └── core │ │ │ └── designsystem │ │ │ ├── chat │ │ │ └── GeminiReactionFactory.kt │ │ │ ├── component │ │ │ ├── Background.kt │ │ │ ├── GeminiSmallTopBar.kt │ │ │ └── LoadingIndicator.kt │ │ │ ├── composition │ │ │ └── LocalOnFinishDispatcher.kt │ │ │ └── theme │ │ │ ├── Background.kt │ │ │ ├── Color.kt │ │ │ └── Theme.kt │ │ └── res │ │ ├── drawable │ │ ├── joy.png │ │ ├── love.png │ │ ├── smile.png │ │ ├── thumbsup.png │ │ └── wink.png │ │ └── values │ │ └── strings.xml ├── model │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── core │ │ └── model │ │ ├── GeminiChannel.kt │ │ └── GeminiModel.kt ├── navigation │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── core │ │ └── navigation │ │ ├── GeminiComposeNavigator.kt │ │ ├── GeminiScreens.kt │ │ ├── NavigationCommand.kt │ │ ├── Navigator.kt │ │ └── di │ │ └── NavigationModule.kt └── network │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── gemini │ └── core │ └── network │ ├── Dispatchers.kt │ ├── di │ ├── DispatchersModule.kt │ └── NetworkModule.kt │ └── service │ └── ChannelService.kt ├── feature ├── channels │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── skydoves │ │ └── gemini │ │ └── feature │ │ └── channels │ │ ├── ChannelViewModel.kt │ │ ├── GeminiChannels.kt │ │ └── RememberFloatingBalloon.kt └── chat │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── gemini │ └── feature │ └── chat │ ├── ChatViewModel.kt │ ├── GeminiChat.kt │ └── extension │ └── GeminiExtension.kt ├── figures ├── figure0.png ├── figure1.png ├── figure2.png ├── figure3.png ├── gemini0.png ├── gemini1.png ├── gemini2.png ├── stream0.png ├── stream1.png ├── stream2.png ├── stream3.png ├── stream4.png ├── stream5.png └── stream6.png ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── previews ├── preview0.png ├── preview1.png └── preview2.gif ├── secrets.defaults.properties ├── settings.gradle.kts └── spotless ├── copyright.kt ├── copyright.kts ├── copyright.xml └── spotless.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | # Most of the standard properties are supported 4 | indent_size=2 5 | max_line_length=100 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | # Not adding in this PR, but I'd like to try adding a global owner set with the entire team. 8 | # One interpretation of their docs is that global owners are added only if not removed 9 | # by a more local rule. 10 | 11 | # Order is important. The last matching pattern has the most precedence. 12 | # The folders are ordered as follows: 13 | 14 | # In each subsection folders are ordered first by depth, then alphabetically. 15 | # This should make it easy to add new rules without breaking existing ones. 16 | * @skydoves -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: skydoves 2 | custom: ["https://www.paypal.me/skydoves", "https://www.buymeacoffee.com/skydoves"] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | # Allow up to 10 open pull requests for pip dependencies 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### 🎯 Goal 2 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 3 | 4 | ### 🛠 Implementation details 5 | Describe the implementation details for this Pull Request. 6 | 7 | ### ✍️ Explain examples 8 | Explain examples with code for this updates. 9 | 10 | ### Preparing a pull request for review 11 | Ensure your change is properly formatted by running: 12 | 13 | ```gradle 14 | $ ./gradlew spotlessApply 15 | ``` 16 | 17 | Please correct any failures before requesting a review. -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | name: Spotless check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v3.1.0 16 | - name: Set up JDK 17 | uses: actions/setup-java@v3.5.1 18 | with: 19 | distribution: adopt 20 | java-version: 17 21 | - name: spotless 22 | run: ./gradlew spotlessCheck 23 | 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: set up JDK 29 | uses: actions/setup-java@v3 30 | with: 31 | distribution: zulu 32 | java-version: 17 33 | 34 | - uses: gradle/gradle-build-action@v2.7.1 35 | - name: Make Gradle executable 36 | run: chmod +x ./gradlew 37 | 38 | - name: Build with Gradle 39 | run: | 40 | ./gradlew --scan --stacktrace \ 41 | :app:assembleDebug 42 | -------------------------------------------------------------------------------- /.github/workflows/baseline-profile.yml: -------------------------------------------------------------------------------- 1 | # Workflow name 2 | name: baseline-profiles 3 | 4 | # Workflow title 5 | run-name: ${{ github.actor }} requested a workflow 6 | 7 | # This should be a manual trigger so this actions gets executed every time make a new pull request. 8 | # Change this event to what suits your project best. 9 | # Read more at https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows 10 | on: 11 | workflow_dispatch: 12 | 13 | # Environment variables (Optional) 14 | # Small projects might have signingConfigs locally. This could lead to failures on GitHub Actions. 15 | # If that's the case, upload your properties defined locally to GitHub Secrets. 16 | 17 | # On your signingConfigs, you can recover GitHub Secrets using: variable = System.getenv("VARIABLE") 18 | 19 | # Then uncomment this block properly defining your uploaded variables 20 | # env: 21 | # VARIABLE: ${{ secrets.VARIABLE }} 22 | 23 | # Read more at https://docs.github.com/en/actions/security-guides/encrypted-secrets 24 | 25 | # Jobs to executed on GitHub machines 26 | jobs: 27 | 28 | # Job name 29 | generate-baseline-profiles: 30 | 31 | # Operating system where the job gets to be executed 32 | runs-on: macos-latest 33 | 34 | # Job steps 35 | steps: 36 | 37 | # Checks your code out on the machine 38 | - uses: actions/checkout@v3 39 | 40 | # Sets java up 41 | - uses: actions/setup-java@v3 42 | with: 43 | distribution: temurin 44 | java-version: 17 45 | 46 | # Sets gradle up 47 | - name: Setup Gradle 48 | uses: gradle/gradle-build-action@v2 49 | 50 | # Grants execute permission to gradle (safety step) 51 | - name: Grant Permissions to gradlew 52 | run: chmod +x gradlew 53 | 54 | # This allows us to build most of what we need without the emulator running 55 | # and using resources 56 | - name: Build app and benchmark 57 | run: ./gradlew :app:assembleBenchmark 58 | 59 | # Cleans managed device if previously settle and space currently is not available 60 | - name: Clean Managed Devices 61 | run: ./gradlew cleanManagedDevices --unused-only 62 | 63 | # Generates Baseline Profile 64 | - name: Generate Baseline Profile 65 | run: ./gradlew generateBaselineProfile -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile -Pandroid.experimental.testOptions.managedDevices.setupTimeoutMinutes=20 -Dorg.gradle.workers.max=4 66 | 67 | # Create Pull Request 68 | - name: Create Pull Request 69 | uses: peter-evans/create-pull-request@v5 70 | with: 71 | commit-message: "Generate baseline profiles" 72 | title: "Generate baseline profiles" 73 | delete-branch: true 74 | reviewers: skydoves 75 | branch: actions/baseline-profiles -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | secrets.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | /.idea/* 39 | !.idea/codeInsightSettings.xml 40 | app/.idea/ 41 | 42 | # Mac 43 | *.DS_Store 44 | 45 | # Keystore files 46 | *.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Temporary API docs 55 | docs/api 56 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | skydoves2@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 3 | 4 | ## Preparing a pull request for review 5 | Ensure your change is properly formatted by running: 6 | 7 | ```gradle 8 | ./gradlew spotlessApply 9 | ``` 10 | 11 | Please correct any failures before requesting a review. 12 | 13 | ## Code reviews 14 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) for more information on using pull requests. 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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. 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 | import java.io.File 17 | import java.io.FileInputStream 18 | import java.util.* 19 | 20 | plugins { 21 | id("skydoves.android.application") 22 | id("skydoves.android.application.compose") 23 | id("skydoves.android.hilt") 24 | id("skydoves.spotless") 25 | id("kotlin-parcelize") 26 | id("dagger.hilt.android.plugin") 27 | id(libs.plugins.google.secrets.get().pluginId) 28 | id(libs.plugins.baseline.profile.get().pluginId) 29 | } 30 | 31 | val keystoreProperties = Properties() 32 | val keystorePropertiesFile = File(rootProject.rootDir, "keystore.properties") 33 | if (keystorePropertiesFile.exists()) { 34 | keystoreProperties.load(FileInputStream(keystorePropertiesFile)) 35 | } 36 | 37 | android { 38 | namespace = "com.skydoves.gemini" 39 | compileSdk = Configurations.compileSdk 40 | 41 | defaultConfig { 42 | applicationId = "com.skydoves.gemini" 43 | minSdk = Configurations.minSdk 44 | targetSdk = Configurations.targetSdk 45 | versionCode = Configurations.versionCode 46 | versionName = Configurations.versionName 47 | } 48 | 49 | packaging { 50 | resources { 51 | excludes.add("/META-INF/{AL2.0,LGPL2.1}") 52 | } 53 | } 54 | 55 | signingConfigs { 56 | create("release") { 57 | keyAlias = keystoreProperties["releaseKeyAlias"] as String? 58 | keyPassword = keystoreProperties["releaseKeyPassword"] as String? 59 | storeFile = file(keystoreProperties["releaseStoreFile"] ?: "release/release-key.jks") 60 | storePassword = keystoreProperties["releaseStorePassword"] as String? 61 | } 62 | } 63 | 64 | buildTypes { 65 | release { 66 | if (keystorePropertiesFile.exists()) { 67 | signingConfig = signingConfigs["release"] 68 | } 69 | isShrinkResources = true 70 | isMinifyEnabled = true 71 | } 72 | 73 | create("benchmark") { 74 | initWith(buildTypes.getByName("release")) 75 | signingConfig = signingConfigs.getByName("debug") 76 | matchingFallbacks += listOf("release") 77 | isDebuggable = false 78 | proguardFiles("benchmark-rules.pro") 79 | } 80 | } 81 | } 82 | 83 | secrets { 84 | propertiesFileName = "secrets.properties" 85 | defaultPropertiesFileName = "secrets.defaults.properties" 86 | } 87 | 88 | dependencies { 89 | // core modules 90 | implementation(project(":core:designsystem")) 91 | implementation(project(":core:navigation")) 92 | implementation(project(":core:data")) 93 | 94 | // feature modules 95 | implementation(project(":feature:channels")) 96 | implementation(project(":feature:chat")) 97 | 98 | // compose 99 | implementation(libs.androidx.activity.compose) 100 | implementation(libs.androidx.compose.runtime) 101 | implementation(libs.androidx.compose.ui.tooling) 102 | implementation(libs.androidx.compose.ui.tooling.preview) 103 | implementation(libs.androidx.compose.constraintlayout) 104 | 105 | // jetpack 106 | implementation(libs.androidx.startup) 107 | implementation(libs.hilt.android) 108 | implementation(libs.androidx.hilt.navigation.compose) 109 | ksp(libs.hilt.compiler) 110 | 111 | // logger 112 | implementation(libs.stream.log) 113 | 114 | // crash tracer & restorer 115 | implementation(libs.snitcher) 116 | 117 | // firebase 118 | implementation(platform(libs.firebase.bom)) 119 | implementation(libs.firebase.analytics) 120 | implementation(libs.firebase.messaging) 121 | implementation(libs.firebase.crashlytics) 122 | 123 | // baseline profile 124 | baselineProfile(project(":baseline-profile")) 125 | } 126 | 127 | if (file("google-services.json").exists()) { 128 | apply(plugin = libs.plugins.gms.googleServices.get().pluginId) 129 | apply(plugin = libs.plugins.firebase.crashlytics.get().pluginId) 130 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 31 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 51 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/GeminiApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini 18 | 19 | import android.app.Application 20 | import com.skydoves.snitcher.Snitcher 21 | import dagger.hilt.android.HiltAndroidApp 22 | 23 | @HiltAndroidApp 24 | class GeminiApp : Application() { 25 | 26 | override fun onCreate() { 27 | super.onCreate() 28 | 29 | // install Snitcher to trace global exceptions and restore the app. 30 | // https://github.com/skydoves/snitcher 31 | Snitcher.install(this) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini 18 | 19 | import android.os.Bundle 20 | import androidx.activity.ComponentActivity 21 | import androidx.activity.compose.setContent 22 | import androidx.compose.runtime.CompositionLocalProvider 23 | import com.skydoves.gemini.core.designsystem.composition.LocalOnFinishDispatcher 24 | import com.skydoves.gemini.core.navigation.AppComposeNavigator 25 | import com.skydoves.gemini.ui.GeminiMain 26 | import dagger.hilt.android.AndroidEntryPoint 27 | import javax.inject.Inject 28 | 29 | @AndroidEntryPoint 30 | class MainActivity : ComponentActivity() { 31 | 32 | @Inject 33 | internal lateinit var appComposeNavigator: AppComposeNavigator 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | 38 | setContent { 39 | CompositionLocalProvider( 40 | LocalOnFinishDispatcher provides { finish() } 41 | ) { 42 | GeminiMain(composeNavigator = appComposeNavigator) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/di/ChatEntryPoint.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.di 18 | 19 | import android.content.Context 20 | import com.skydoves.gemini.initializer.StreamChatInitializer 21 | import dagger.hilt.EntryPoint 22 | import dagger.hilt.InstallIn 23 | import dagger.hilt.android.EntryPointAccessors 24 | import dagger.hilt.components.SingletonComponent 25 | 26 | @EntryPoint 27 | @InstallIn(SingletonComponent::class) 28 | internal interface ChatEntryPoint { 29 | 30 | fun inject(streamChatInitializer: StreamChatInitializer) 31 | 32 | companion object { 33 | fun resolve(context: Context): ChatEntryPoint { 34 | val appContext = context.applicationContext ?: throw IllegalStateException( 35 | "applicationContext was not found in ChatEntryPoint" 36 | ) 37 | return EntryPointAccessors.fromApplication( 38 | appContext, 39 | ChatEntryPoint::class.java 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/di/ChatModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.di 18 | 19 | import dagger.Module 20 | import dagger.Provides 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import io.getstream.chat.android.client.ChatClient 24 | import javax.inject.Singleton 25 | 26 | @Module 27 | @InstallIn(SingletonComponent::class) 28 | internal object ChatModule { 29 | 30 | @Provides 31 | @Singleton 32 | fun provideStreamChatClient() = ChatClient.instance() 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/initializer/StreamChatInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import com.skydoves.gemini.BuildConfig 22 | import com.skydoves.gemini.core.datastore.PreferenceDataStore 23 | import com.skydoves.gemini.di.ChatEntryPoint 24 | import io.getstream.chat.android.client.ChatClient 25 | import io.getstream.chat.android.client.logger.ChatLogLevel 26 | import io.getstream.chat.android.models.ConnectionData 27 | import io.getstream.chat.android.models.User 28 | import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory 29 | import io.getstream.chat.android.state.plugin.config.StatePluginConfig 30 | import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory 31 | import io.getstream.log.streamLog 32 | import io.getstream.result.call.Call 33 | import javax.inject.Inject 34 | import kotlin.random.Random 35 | 36 | /** 37 | * StreamChatInitializer initializes all Stream Client components. 38 | */ 39 | class StreamChatInitializer : Initializer { 40 | 41 | @Inject 42 | internal lateinit var preferenceDataStore: PreferenceDataStore 43 | 44 | override fun create(context: Context) { 45 | ChatEntryPoint.resolve(context).inject(this) 46 | 47 | streamLog { "StreamChatInitializer is initialized" } 48 | 49 | /** 50 | * initialize a global instance of the [ChatClient]. 51 | * The ChatClient is the main entry point for all low-level operations on chat. 52 | * e.g, connect/disconnect user to the server, send/update/pin message, etc. 53 | */ 54 | val logLevel = if (BuildConfig.DEBUG) ChatLogLevel.ALL else ChatLogLevel.NOTHING 55 | val offlinePluginFactory = StreamOfflinePluginFactory( 56 | appContext = context 57 | ) 58 | val statePluginFactory = StreamStatePluginFactory( 59 | config = StatePluginConfig( 60 | backgroundSyncEnabled = true, 61 | userPresence = true 62 | ), 63 | appContext = context 64 | ) 65 | val chatClient = ChatClient.Builder(BuildConfig.STREAM_API_KEY, context) 66 | .withPlugins(offlinePluginFactory, statePluginFactory) 67 | .logLevel(logLevel) 68 | .build() 69 | 70 | val number = preferenceDataStore.userNumber.value ?: let { 71 | val random = Random.nextInt(10000) 72 | preferenceDataStore.updateUserNumber(random) 73 | random 74 | } 75 | val user = User( 76 | id = "stream$number", 77 | name = "User$number", 78 | image = "https://picsum.photos/id/${Random.nextInt(1000)}/300/300" 79 | ) 80 | 81 | val token = chatClient.devToken(user.id) 82 | chatClient.connectUser(user, token).enqueue(object : Call.Callback { 83 | override fun onResult(result: io.getstream.result.Result) { 84 | if (result.isFailure) { 85 | streamLog { 86 | "Can't connect user. Please check the app README.md and ensure " + 87 | "**Disable Auth Checks** is ON in the Dashboard" 88 | } 89 | } 90 | } 91 | }) 92 | } 93 | 94 | override fun dependencies(): List>> = 95 | listOf(StreamLogInitializer::class.java) 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/initializer/StreamLogInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import io.getstream.log.Priority 22 | import io.getstream.log.android.AndroidStreamLogger 23 | import io.getstream.log.android.BuildConfig 24 | import io.getstream.log.streamLog 25 | 26 | class StreamLogInitializer : Initializer { 27 | 28 | override fun create(context: Context) { 29 | if (BuildConfig.DEBUG) { 30 | AndroidStreamLogger.install(minPriority = Priority.DEBUG) 31 | streamLog { "StreamLogInitializer is initialized" } 32 | } 33 | } 34 | 35 | override fun dependencies(): List>> = emptyList() 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/navigation/GeminiNavHost.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.navigation 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.navigation.NavHostController 21 | import androidx.navigation.compose.NavHost 22 | import com.skydoves.gemini.core.navigation.AppComposeNavigator 23 | import com.skydoves.gemini.core.navigation.GeminiScreens 24 | 25 | @Composable 26 | fun GeminiNavHost( 27 | navHostController: NavHostController, 28 | composeNavigator: AppComposeNavigator 29 | ) { 30 | NavHost( 31 | navController = navHostController, 32 | startDestination = GeminiScreens.Channels.route 33 | ) { 34 | geminiHomeNavigation( 35 | composeNavigator = composeNavigator 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/navigation/GeminiNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.navigation 18 | 19 | import androidx.compose.foundation.layout.padding 20 | import androidx.compose.material3.Scaffold 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.navigation.NavGraphBuilder 24 | import androidx.navigation.compose.composable 25 | import com.skydoves.gemini.R 26 | import com.skydoves.gemini.core.designsystem.component.GeminiSmallTopBar 27 | import com.skydoves.gemini.core.navigation.AppComposeNavigator 28 | import com.skydoves.gemini.core.navigation.GeminiScreens 29 | import com.skydoves.gemini.core.navigation.GeminiScreens.Companion.argument_channel_id 30 | import com.skydoves.gemini.feature.channels.GeminiChannels 31 | import com.skydoves.gemini.feature.chat.GeminiChat 32 | 33 | fun NavGraphBuilder.geminiHomeNavigation( 34 | composeNavigator: AppComposeNavigator 35 | ) { 36 | composable(route = GeminiScreens.Channels.name) { 37 | Scaffold(topBar = { 38 | GeminiSmallTopBar( 39 | title = stringResource(id = R.string.app_name) 40 | ) 41 | }) { padding -> 42 | GeminiChannels( 43 | modifier = Modifier.padding(padding), 44 | composeNavigator = composeNavigator 45 | ) 46 | } 47 | } 48 | 49 | composable( 50 | route = GeminiScreens.Messages.name, 51 | arguments = GeminiScreens.Messages.navArguments 52 | ) { 53 | val channelId = it.arguments?.getString(argument_channel_id) ?: return@composable 54 | GeminiChat( 55 | channelId = channelId, 56 | composeNavigator = composeNavigator 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/gemini/ui/GeminiMain.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Designed and developed by 2024 skydoves (Jaewoong Eum) 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.skydoves.gemini.ui 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.LaunchedEffect 21 | import androidx.navigation.compose.rememberNavController 22 | import com.skydoves.gemini.core.designsystem.component.GeminiBackground 23 | import com.skydoves.gemini.core.designsystem.theme.GeminiComposeTheme 24 | import com.skydoves.gemini.core.navigation.AppComposeNavigator 25 | import com.skydoves.gemini.navigation.GeminiNavHost 26 | 27 | @Composable 28 | fun GeminiMain( 29 | composeNavigator: AppComposeNavigator 30 | ) { 31 | GeminiComposeTheme { 32 | val navHostController = rememberNavController() 33 | 34 | LaunchedEffect(Unit) { 35 | composeNavigator.handleNavigationCommands(navHostController) 36 | } 37 | 38 | GeminiBackground { 39 | GeminiNavHost(navHostController = navHostController, composeNavigator = composeNavigator) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 186 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FF000000 19 | #FFFFFFFF 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | gemini-android 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | 24 |