├── .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 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/baseline-profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/baseline-profile/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 | plugins {
17 | id("skydoves.spotless")
18 | id("com.android.test")
19 | id("org.jetbrains.kotlin.android")
20 | id(libs.plugins.baseline.profile.get().pluginId)
21 | }
22 |
23 | android {
24 | namespace = "com.skydoves.gemini.baselineprofile"
25 | compileSdk = Configurations.compileSdk
26 |
27 | defaultConfig {
28 | minSdk = 24
29 | targetSdk = Configurations.targetSdk
30 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_17
35 | targetCompatibility = JavaVersion.VERSION_17
36 | }
37 |
38 | targetProjectPath = ":app"
39 |
40 | testOptions.managedDevices.devices {
41 | maybeCreate("pixel6api31").apply {
42 | device = "Pixel 6"
43 | apiLevel = 31
44 | systemImageSource = "aosp"
45 | }
46 | }
47 | }
48 |
49 | // This is the plugin configuration. Everything is optional. Defaults are in the
50 | // comments. In this example, you use the GMD added earlier and disable connected devices.
51 | baselineProfile {
52 |
53 | // This specifies the managed devices to use that you run the tests on. The default
54 | // is none.
55 | managedDevices += "pixel6api31"
56 |
57 | // This enables using connected devices to generate profiles. The default is true.
58 | // When using connected devices, they must be rooted or API 33 and higher.
59 | useConnectedDevices = false
60 | }
61 |
62 | dependencies {
63 | implementation(libs.androidx.test.runner)
64 | implementation(libs.androidx.test.uiautomator)
65 | implementation(libs.androidx.benchmark.macro)
66 | implementation(libs.androidx.profileinstaller)
67 | }
--------------------------------------------------------------------------------
/baseline-profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/baseline-profile/src/main/java/com/skydoves/gemini/baselineprofile/BaselineProfileGenerator.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.baselineprofile
18 |
19 | import android.os.Build
20 | import androidx.annotation.RequiresApi
21 | import androidx.benchmark.macro.junit4.BaselineProfileRule
22 | import org.junit.Rule
23 | import org.junit.Test
24 |
25 | /**
26 | * Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`.
27 | */
28 | @RequiresApi(Build.VERSION_CODES.P)
29 | class BaselineProfileGenerator {
30 | @get:Rule
31 | val baselineProfileRule = BaselineProfileRule()
32 |
33 | @Test
34 | fun startup() =
35 | baselineProfileRule.collect(
36 | packageName = "com.skydoves.gemini",
37 | includeInStartupProfile = true
38 | ) {
39 | pressHome()
40 | // This block defines the app's critical user journey. Here we are interested in
41 | // optimizing for app startup. But you can also navigate and scroll
42 | // through your most important UI.
43 | startActivityAndWait()
44 | device.waitForIdle()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/build-logic/convention/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | group = "com.skydoves.gemini.buildlogic"
6 |
7 | java {
8 | sourceCompatibility = JavaVersion.VERSION_17
9 | targetCompatibility = JavaVersion.VERSION_17
10 | }
11 |
12 | dependencies {
13 | compileOnly(libs.android.gradlePlugin)
14 | compileOnly(libs.kotlin.gradlePlugin)
15 | compileOnly(libs.compose.compiler.gradlePlugin)
16 | compileOnly(libs.spotless.gradlePlugin)
17 | }
18 |
19 | gradlePlugin {
20 | plugins {
21 | register("androidApplicationCompose") {
22 | id = "skydoves.android.application.compose"
23 | implementationClass = "AndroidApplicationComposeConventionPlugin"
24 | }
25 | register("androidApplication") {
26 | id = "skydoves.android.application"
27 | implementationClass = "AndroidApplicationConventionPlugin"
28 | }
29 | register("androidLibraryCompose") {
30 | id = "skydoves.android.library.compose"
31 | implementationClass = "AndroidLibraryComposeConventionPlugin"
32 | }
33 | register("androidLibrary") {
34 | id = "skydoves.android.library"
35 | implementationClass = "AndroidLibraryConventionPlugin"
36 | }
37 | register("androidFeature") {
38 | id = "skydoves.android.feature"
39 | implementationClass = "AndroidFeatureConventionPlugin"
40 | }
41 | register("androidHilt") {
42 | id = "skydoves.android.hilt"
43 | implementationClass = "AndroidHiltConventionPlugin"
44 | }
45 | register("spotless") {
46 | id = "skydoves.spotless"
47 | implementationClass = "SpotlessConventionPlugin"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
2 | import com.skydoves.gemini.configureAndroidCompose
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidApplicationComposeConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | pluginManager.apply("com.android.application")
11 | val extension = extensions.getByType()
12 | configureAndroidCompose(extension)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
2 | import com.skydoves.gemini.configureKotlinAndroid
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 |
7 | class AndroidApplicationConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("com.android.application")
12 | apply("org.jetbrains.kotlin.android")
13 | }
14 |
15 | extensions.configure {
16 | configureKotlinAndroid(this)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Plugin
2 | import org.gradle.api.Project
3 | import org.gradle.api.artifacts.VersionCatalogsExtension
4 | import org.gradle.kotlin.dsl.dependencies
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidFeatureConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | pluginManager.apply {
11 | apply("com.android.library")
12 | apply("org.jetbrains.kotlin.android")
13 | }
14 |
15 | val libs = extensions.getByType().named("libs")
16 |
17 | dependencies {
18 | add("implementation", project(":core:designsystem"))
19 | add("implementation", project(":core:navigation"))
20 | add("implementation", project(":core:data"))
21 |
22 | add("implementation", libs.findLibrary("kotlinx.coroutines.android").get())
23 | add("implementation", libs.findLibrary("stream.log").get())
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Plugin
2 | import org.gradle.api.Project
3 | import org.gradle.api.artifacts.VersionCatalogsExtension
4 | import org.gradle.kotlin.dsl.dependencies
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidHiltConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("com.google.devtools.ksp")
12 | }
13 |
14 | val libs = extensions.getByType().named("libs")
15 |
16 | dependencies {
17 | add("implementation", libs.findLibrary("hilt.android").get())
18 | add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get())
19 | add("ksp", libs.findLibrary("hilt.compiler").get())
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import com.skydoves.gemini.configureAndroidCompose
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidLibraryComposeConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | pluginManager.apply("com.android.library")
11 | val extension = extensions.getByType()
12 | configureAndroidCompose(extension)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import com.skydoves.gemini.configureKotlinAndroid
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 |
7 | class AndroidLibraryConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("com.android.library")
12 | apply("org.jetbrains.kotlin.android")
13 | }
14 |
15 | extensions.configure {
16 | configureKotlinAndroid(this)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/SpotlessConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.diffplug.gradle.spotless.SpotlessExtension
2 | import org.gradle.api.Plugin
3 | import org.gradle.api.Project
4 | import org.gradle.kotlin.dsl.configure
5 |
6 | class SpotlessConventionPlugin : Plugin {
7 | override fun apply(target: Project) {
8 | with(target) {
9 | pluginManager.apply("com.diffplug.spotless")
10 |
11 | extensions.configure {
12 | kotlin {
13 | target("**/*.kt")
14 | targetExclude("**/build/**/*.kt")
15 | ktlint()
16 | .setUseExperimental(true)
17 | .userData(mapOf("android" to "true"))
18 | .editorConfigOverride(mapOf("indent_size" to 2, "continuation_indent_size" to 2))
19 | licenseHeaderFile(rootProject.file("$rootDir/spotless/copyright.kt"))
20 | }
21 | format("kts") {
22 | target("**/*.kts")
23 | targetExclude("**/build/**/*.kts")
24 | // Look for the first line that doesn't have a block comment (assumed to be the license)
25 | licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
26 | }
27 | format("xml") {
28 | target("**/*.xml")
29 | targetExclude("**/build/**/*.xml")
30 | // Look for the first XML tag that isn't a comment (
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/chat/ChatModels.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.core.data.chat
18 |
19 | import com.skydoves.gemini.core.model.GeminiChannel
20 | import com.skydoves.gemini.core.model.GeminiModel
21 | import io.getstream.chat.android.models.User
22 |
23 | public val geminiUser = User(
24 | id = "gemini",
25 | role = "admin",
26 | name = "gemini",
27 | image = "https://avatars.githubusercontent.com/u/8597527?s=200&v=4.png"
28 | )
29 |
30 | public const val STREAM_CHANNEL_MODEL: String = "channelModel"
31 |
32 | public const val STREAM_CHANNEL_GEMINI_FLAG = "Gemini"
33 |
34 | public const val STREAM_RANDOM_CHANNEL_KEY = "key"
35 |
36 | public fun createRandomGeminiChannel(number: Int): GeminiChannel {
37 | return GeminiChannel(
38 | id = "messaging:random-chat$number",
39 | name = "Gemini-Chat$number",
40 | key = "random$number",
41 | model = GeminiModel(
42 | name = "gemini-pro",
43 | temperature = 0.5f,
44 | candidateCount = 1,
45 | maxOutputTokens = 500,
46 | topK = 30,
47 | topP = 0.5f
48 | )
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/coroutines/Flow.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.core.data.coroutines
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.viewModelScope
21 | import kotlinx.coroutines.channels.BufferOverflow
22 | import kotlinx.coroutines.flow.Flow
23 | import kotlinx.coroutines.flow.MutableSharedFlow
24 | import kotlinx.coroutines.flow.SharedFlow
25 | import kotlinx.coroutines.flow.SharingStarted
26 | import kotlinx.coroutines.flow.StateFlow
27 | import kotlinx.coroutines.flow.shareIn
28 | import kotlinx.coroutines.flow.stateIn
29 |
30 | context(ViewModel)
31 | public fun Flow.asStateFlow(
32 | initialValue: T,
33 | started: SharingStarted = WhileSubscribedOrRetained
34 | ): StateFlow {
35 | return stateIn(
36 | scope = viewModelScope,
37 | started = SharingStarted.Eagerly,
38 | initialValue = initialValue
39 | )
40 | }
41 |
42 | context(ViewModel)
43 | public fun Flow.asSharedFlow(replay: Int = 0): SharedFlow {
44 | return shareIn(
45 | scope = viewModelScope,
46 | started = WhileSubscribedOrRetained,
47 | replay = replay
48 | )
49 | }
50 |
51 | public fun publishedFlow(
52 | replay: Int = 1,
53 | extraBufferCapacity: Int = 2
54 | ): MutableSharedFlow {
55 | return MutableSharedFlow(
56 | replay = replay,
57 | extraBufferCapacity = extraBufferCapacity,
58 | onBufferOverflow = BufferOverflow.DROP_OLDEST
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/coroutines/WhileSubscribedOrRetained.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.core.data.coroutines
18 |
19 | import android.os.Handler
20 | import android.os.Looper
21 | import android.view.Choreographer
22 | import kotlinx.coroutines.CompletableDeferred
23 | import kotlinx.coroutines.flow.Flow
24 | import kotlinx.coroutines.flow.SharingCommand
25 | import kotlinx.coroutines.flow.SharingStarted
26 | import kotlinx.coroutines.flow.StateFlow
27 | import kotlinx.coroutines.flow.distinctUntilChanged
28 | import kotlinx.coroutines.flow.dropWhile
29 | import kotlinx.coroutines.flow.transformLatest
30 |
31 | /**
32 | * Sharing is started when the first subscriber appears,
33 | * immediately stops when the last subscriber disappears (by default),
34 | * keeping the replay cache forever (by default) even if configuration changes happen.
35 | *
36 | * https://py.hashnode.dev/whilesubscribed5000
37 | */
38 | data object WhileSubscribedOrRetained : SharingStarted {
39 |
40 | private val handler = Handler(Looper.getMainLooper())
41 |
42 | override fun command(subscriptionCount: StateFlow): Flow = subscriptionCount
43 | .transformLatest { count ->
44 | if (count > 0) {
45 | emit(SharingCommand.START)
46 | } else {
47 | val posted = CompletableDeferred()
48 | // This code is perfect. Do not change a thing.
49 | Choreographer.getInstance().postFrameCallback {
50 | handler.postAtFrontOfQueue {
51 | handler.post {
52 | posted.complete(Unit)
53 | }
54 | }
55 | }
56 | posted.await()
57 | emit(SharingCommand.STOP)
58 | }
59 | }
60 | .dropWhile { it != SharingCommand.START }
61 | .distinctUntilChanged()
62 |
63 | override fun toString(): String = "WhileSubscribedOrRetained"
64 | }
65 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/di/DataModule.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.core.data.di
18 |
19 | import com.skydoves.gemini.core.data.repository.ChannelRepository
20 | import com.skydoves.gemini.core.data.repository.ChannelRepositoryImpl
21 | import com.skydoves.gemini.core.data.repository.ChatRepository
22 | import com.skydoves.gemini.core.data.repository.ChatRepositoryImpl
23 | import dagger.Binds
24 | import dagger.Module
25 | import dagger.hilt.InstallIn
26 | import dagger.hilt.components.SingletonComponent
27 |
28 | @Module
29 | @InstallIn(SingletonComponent::class)
30 | internal interface DataModule {
31 |
32 | @Binds
33 | fun bindChannelRepository(
34 | channelRepositoryImpl: ChannelRepositoryImpl
35 | ): ChannelRepository
36 |
37 | @Binds
38 | fun bindChatRepository(
39 | chatRepositoryImpl: ChatRepositoryImpl
40 | ): ChatRepository
41 | }
42 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/repository/ChannelRepository.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.core.data.repository
18 |
19 | import com.skydoves.gemini.core.model.GeminiChannel
20 | import com.skydoves.sandwich.ApiResponse
21 | import io.getstream.chat.android.models.Channel
22 | import io.getstream.chat.android.models.InitializationState
23 | import io.getstream.chat.android.models.User
24 | import io.getstream.result.Result
25 | import kotlinx.coroutines.flow.Flow
26 |
27 | /** The exposed abstraction layer of the [ChannelRepositoryImpl]. */
28 | public interface ChannelRepository {
29 |
30 | suspend fun joinDefaultChannels(user: User): ApiResponse>
31 |
32 | suspend fun createRandomChannel(user: User): Result
33 |
34 | fun streamInitializationFlow(): Flow
35 |
36 | fun streamUserFlow(): Flow
37 |
38 | fun shouldDisplayBalloon(): Flow
39 |
40 | suspend fun markBalloonDisplayed()
41 | }
42 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/repository/ChannelRepositoryImpl.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.core.data.repository
18 |
19 | import com.skydoves.gemini.core.data.chat.STREAM_CHANNEL_MODEL
20 | import com.skydoves.gemini.core.data.chat.STREAM_RANDOM_CHANNEL_KEY
21 | import com.skydoves.gemini.core.data.chat.createRandomGeminiChannel
22 | import com.skydoves.gemini.core.data.chat.geminiUser
23 | import com.skydoves.gemini.core.database.GeminiDao
24 | import com.skydoves.gemini.core.database.toEntity
25 | import com.skydoves.gemini.core.datastore.PreferenceDataStore
26 | import com.skydoves.gemini.core.model.GeminiChannel
27 | import com.skydoves.gemini.core.network.service.ChannelService
28 | import com.skydoves.sandwich.ApiResponse
29 | import com.skydoves.sandwich.suspendOnSuccess
30 | import io.getstream.chat.android.client.ChatClient
31 | import io.getstream.chat.android.models.Channel
32 | import io.getstream.chat.android.models.InitializationState
33 | import io.getstream.chat.android.models.User
34 | import io.getstream.result.Result
35 | import io.getstream.result.onSuccessSuspend
36 | import java.util.Random
37 | import java.util.UUID
38 | import javax.inject.Inject
39 | import kotlinx.coroutines.flow.Flow
40 | import kotlinx.coroutines.flow.map
41 | import kotlinx.coroutines.flow.mapNotNull
42 |
43 | /**
44 | * The implementation of the [ChannelRepository]. This class shouldn't be exposed to out of the `core-data` module.
45 | */
46 | internal class ChannelRepositoryImpl @Inject constructor(
47 | private val chatClient: ChatClient,
48 | private val service: ChannelService,
49 | private val geminiDao: GeminiDao,
50 | private val preferenceDataStore: PreferenceDataStore
51 | ) : ChannelRepository {
52 |
53 | override suspend fun joinDefaultChannels(user: User): ApiResponse> {
54 | val response = service.geminiChannels()
55 | .suspendOnSuccess {
56 | data.forEach { geminiChannel ->
57 | val number = preferenceDataStore.userNumber.value ?: 0
58 | val channelClient = chatClient.channel(geminiChannel.id + number)
59 | channelClient.create(
60 | memberIds = listOf(geminiUser.id, user.id),
61 | extraData = mapOf(
62 | "name" to geminiChannel.name,
63 | "image" to geminiUser.image,
64 | STREAM_CHANNEL_MODEL to geminiChannel,
65 | STREAM_RANDOM_CHANNEL_KEY to geminiChannel.key
66 | )
67 | ).await().onSuccessSuspend {
68 | geminiDao.insertGeminiChannel(geminiChannel.toEntity())
69 | }
70 | }
71 | }
72 | return response
73 | }
74 |
75 | override suspend fun createRandomChannel(user: User): Result {
76 | val number = Random().nextInt(99999)
77 | val geminiChannel = createRandomGeminiChannel(number = number)
78 | return chatClient.createChannel(
79 | channelType = "messaging",
80 | channelId = UUID.randomUUID().toString(),
81 | memberIds = listOf(user.id, geminiUser.id),
82 | extraData = mapOf(
83 | "name" to "Gemini-Chat$number",
84 | "image" to "https://picsum.photos/id/${Random().nextInt(1000)}/300/300",
85 | STREAM_CHANNEL_MODEL to geminiChannel,
86 | STREAM_RANDOM_CHANNEL_KEY to "random$number"
87 | )
88 | ).await().onSuccessSuspend {
89 | geminiDao.insertGeminiChannel(geminiChannel.toEntity())
90 | }
91 | }
92 |
93 | override fun streamInitializationFlow(): Flow =
94 | chatClient.clientState.initializationState
95 |
96 | override fun streamUserFlow(): Flow = chatClient.clientState.user.mapNotNull { it }
97 |
98 | override fun shouldDisplayBalloon(): Flow =
99 | preferenceDataStore.shouldDisplayBalloon.map {
100 | it ?: false
101 | }
102 |
103 | override suspend fun markBalloonDisplayed() {
104 | preferenceDataStore.markBalloonDisplayed()
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/repository/ChatRepository.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.core.data.repository
18 |
19 | import com.google.ai.client.generativeai.Chat
20 | import com.google.ai.client.generativeai.GenerativeModel
21 | import com.google.ai.client.generativeai.type.Content
22 | import com.google.ai.client.generativeai.type.GenerateContentResponse
23 | import com.skydoves.gemini.core.model.GeminiChannel
24 | import kotlinx.coroutines.flow.Flow
25 |
26 | /** The exposed abstraction layer of the [ChatRepositoryImpl]. */
27 | public interface ChatRepository {
28 |
29 | suspend fun summaryContent(
30 | generativeModel: GenerativeModel,
31 | prompt: String
32 | ): GenerateContentResponse
33 |
34 | suspend fun sendMessage(chat: Chat, prompt: String): GenerateContentResponse
35 |
36 | suspend fun photoReasoning(
37 | generativeModel: GenerativeModel,
38 | content: Content
39 | ): GenerateContentResponse
40 |
41 | fun watchIsChannelMessageEmpty(cid: String): Flow
42 |
43 | fun getGeminiChannel(key: String): Flow
44 | }
45 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/repository/ChatRepositoryImpl.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.core.data.repository
18 |
19 | import com.google.ai.client.generativeai.Chat
20 | import com.google.ai.client.generativeai.GenerativeModel
21 | import com.google.ai.client.generativeai.type.Content
22 | import com.google.ai.client.generativeai.type.GenerateContentResponse
23 | import com.skydoves.gemini.core.database.GeminiDao
24 | import com.skydoves.gemini.core.database.toDomain
25 | import com.skydoves.gemini.core.model.GeminiChannel
26 | import com.skydoves.gemini.core.network.Dispatcher
27 | import com.skydoves.gemini.core.network.GeminiDispatchers
28 | import io.getstream.chat.android.client.ChatClient
29 | import io.getstream.result.onSuccessSuspend
30 | import javax.inject.Inject
31 | import kotlinx.coroutines.CoroutineDispatcher
32 | import kotlinx.coroutines.flow.Flow
33 | import kotlinx.coroutines.flow.flow
34 | import kotlinx.coroutines.flow.flowOn
35 |
36 | /** The exposed abstraction layer of the [ChatRepository]. */
37 | internal class ChatRepositoryImpl @Inject constructor(
38 | @Dispatcher(GeminiDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
39 | private val chatClient: ChatClient,
40 | private val geminiDao: GeminiDao
41 | ) : ChatRepository {
42 |
43 | override suspend fun summaryContent(
44 | generativeModel: GenerativeModel,
45 | prompt: String
46 | ): GenerateContentResponse = generativeModel.generateContent(prompt = prompt)
47 |
48 | override suspend fun sendMessage(chat: Chat, prompt: String): GenerateContentResponse {
49 | return chat.sendMessage(prompt = prompt)
50 | }
51 |
52 | override suspend fun photoReasoning(
53 | generativeModel: GenerativeModel,
54 | content: Content
55 | ) = generativeModel.generateContent(content)
56 |
57 | override fun watchIsChannelMessageEmpty(cid: String): Flow = flow {
58 | val result = chatClient.channel(cid).watch().await()
59 | result.onSuccessSuspend { channel ->
60 | val messages = channel.messages
61 | emit(messages.isEmpty())
62 | }
63 | }.flowOn(ioDispatcher)
64 |
65 | override fun getGeminiChannel(key: String): Flow = flow {
66 | val geminiChannel = geminiDao.getGeminiChannel(key = key).toDomain()
67 | emit(geminiChannel)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/core/data/src/main/kotlin/com/skydoves/gemini/core/data/utils/Strings.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.core.data.utils
18 |
19 | val String.Companion.Empty
20 | inline get() = ""
21 |
--------------------------------------------------------------------------------
/core/database/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/database/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.hilt")
19 | id("skydoves.spotless")
20 | }
21 |
22 | android {
23 | namespace = "com.skydoves.gemini.core.database"
24 |
25 | defaultConfig {
26 | ksp {
27 | arg("room.schemaLocation", "$projectDir/schemas")
28 | }
29 | }
30 |
31 | sourceSets.getByName("test") {
32 | assets.srcDir(files("$projectDir/schemas"))
33 | }
34 | }
35 |
36 | dependencies {
37 | // core
38 | implementation(project(":core:model"))
39 |
40 | implementation(libs.androidx.room.runtime)
41 | implementation(libs.androidx.room.ktx)
42 | ksp(libs.androidx.room.compiler)
43 | }
44 |
--------------------------------------------------------------------------------
/core/database/schemas/com.skydoves.gemini.core.database.GeminiDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "5b86e558b3a264372c0591c8510de7c7",
6 | "entities": [
7 | {
8 | "tableName": "GeminiChannelEntity",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `key` TEXT NOT NULL, `name` TEXT NOT NULL, `model` TEXT NOT NULL, PRIMARY KEY(`key`))",
10 | "fields": [
11 | {
12 | "fieldPath": "id",
13 | "columnName": "id",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "key",
19 | "columnName": "key",
20 | "affinity": "TEXT",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "name",
25 | "columnName": "name",
26 | "affinity": "TEXT",
27 | "notNull": true
28 | },
29 | {
30 | "fieldPath": "model",
31 | "columnName": "model",
32 | "affinity": "TEXT",
33 | "notNull": true
34 | }
35 | ],
36 | "primaryKey": {
37 | "autoGenerate": false,
38 | "columnNames": [
39 | "key"
40 | ]
41 | },
42 | "indices": [],
43 | "foreignKeys": []
44 | }
45 | ],
46 | "views": [],
47 | "setupQueries": [
48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5b86e558b3a264372c0591c8510de7c7')"
50 | ]
51 | }
52 | }
--------------------------------------------------------------------------------
/core/database/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/com/skydoves/gemini/core/database/GeminiChannelEntity.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.core.database
18 |
19 | import androidx.room.Entity
20 | import androidx.room.PrimaryKey
21 | import com.skydoves.gemini.core.model.GeminiChannel
22 | import com.skydoves.gemini.core.model.GeminiModel
23 |
24 | @Entity
25 | data class GeminiChannelEntity(
26 | val id: String,
27 | @PrimaryKey val key: String,
28 | val name: String,
29 | val model: GeminiModel
30 | )
31 |
32 | fun GeminiChannel.toEntity(): GeminiChannelEntity {
33 | return GeminiChannelEntity(
34 | id = id,
35 | key = key,
36 | name = name,
37 | model = model
38 | )
39 | }
40 |
41 | fun GeminiChannelEntity.toDomain(): GeminiChannel {
42 | return GeminiChannel(
43 | id = id,
44 | key = key,
45 | name = name,
46 | model = model
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/com/skydoves/gemini/core/database/GeminiDao.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.core.database
18 |
19 | import androidx.room.Dao
20 | import androidx.room.Insert
21 | import androidx.room.OnConflictStrategy
22 | import androidx.room.Query
23 |
24 | @Dao
25 | interface GeminiDao {
26 |
27 | @Insert(onConflict = OnConflictStrategy.REPLACE)
28 | suspend fun insertGeminiChannel(geminiChannel: GeminiChannelEntity)
29 |
30 | @Query("SELECT * FROM GeminiChannelEntity WHERE key = :key")
31 | suspend fun getGeminiChannel(key: String): GeminiChannelEntity
32 | }
33 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/com/skydoves/gemini/core/database/GeminiDatabase.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.core.database
18 |
19 | import androidx.room.Database
20 | import androidx.room.RoomDatabase
21 | import androidx.room.TypeConverters
22 |
23 | @Database(
24 | entities = [GeminiChannelEntity::class],
25 | version = 1,
26 | exportSchema = true
27 | )
28 | @TypeConverters(value = [GeminiModelConverter::class])
29 | abstract class GeminiDatabase : RoomDatabase() {
30 |
31 | abstract fun geminiDao(): GeminiDao
32 | }
33 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/com/skydoves/gemini/core/database/GeminiModelConverter.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.core.database
18 |
19 | import androidx.room.ProvidedTypeConverter
20 | import androidx.room.TypeConverter
21 | import com.skydoves.gemini.core.model.GeminiModel
22 | import com.squareup.moshi.JsonAdapter
23 | import com.squareup.moshi.Moshi
24 | import javax.inject.Inject
25 |
26 | @ProvidedTypeConverter
27 | class GeminiModelConverter @Inject constructor(
28 | private val moshi: Moshi
29 | ) {
30 |
31 | @TypeConverter
32 | fun fromString(value: String): GeminiModel? {
33 | val adapter: JsonAdapter = moshi.adapter(GeminiModel::class.java)
34 | return adapter.fromJson(value)
35 | }
36 |
37 | @TypeConverter
38 | fun fromInfoType(type: GeminiModel?): String {
39 | val adapter: JsonAdapter = moshi.adapter(GeminiModel::class.java)
40 | return adapter.toJson(type)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/core/database/src/main/kotlin/com/skydoves/gemini/core/database/di/DatabaseModule.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.core.database.di
18 |
19 | import android.app.Application
20 | import androidx.room.Room
21 | import com.skydoves.gemini.core.database.GeminiDao
22 | import com.skydoves.gemini.core.database.GeminiDatabase
23 | import com.skydoves.gemini.core.database.GeminiModelConverter
24 | import com.squareup.moshi.Moshi
25 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
26 | import dagger.Module
27 | import dagger.Provides
28 | import dagger.hilt.InstallIn
29 | import dagger.hilt.components.SingletonComponent
30 | import javax.inject.Singleton
31 |
32 | @Module
33 | @InstallIn(SingletonComponent::class)
34 | internal object DatabaseModule {
35 |
36 | @Provides
37 | @Singleton
38 | fun provideMoshi(): Moshi {
39 | return Moshi.Builder()
40 | .addLast(KotlinJsonAdapterFactory())
41 | .build()
42 | }
43 |
44 | @Provides
45 | @Singleton
46 | fun provideAppDatabase(
47 | application: Application,
48 | geminiModelConverter: GeminiModelConverter
49 | ): GeminiDatabase {
50 | return Room
51 | .databaseBuilder(application, GeminiDatabase::class.java, "Gemini.db")
52 | .fallbackToDestructiveMigration()
53 | .addTypeConverter(geminiModelConverter)
54 | .build()
55 | }
56 |
57 | @Provides
58 | @Singleton
59 | fun provideGeminiDao(appDatabase: GeminiDatabase): GeminiDao {
60 | return appDatabase.geminiDao()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/core/datastore/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/datastore/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.hilt")
19 | id("skydoves.spotless")
20 | }
21 |
22 | android {
23 | namespace = "com.skydoves.gemini.core.datastore"
24 | }
25 |
26 | dependencies {
27 | implementation(libs.androidx.datastore)
28 | }
29 |
--------------------------------------------------------------------------------
/core/datastore/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/datastore/src/main/kotlin/com/skydoves/gemini/core/datastore/DataStore.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.core.datastore
18 |
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import androidx.datastore.preferences.core.Preferences
22 | import androidx.datastore.preferences.preferencesDataStore
23 |
24 | internal val Context.dataStore: DataStore by preferencesDataStore("gemini_datastore")
25 |
--------------------------------------------------------------------------------
/core/datastore/src/main/kotlin/com/skydoves/gemini/core/datastore/PreferenceDataStore.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.core.datastore
18 |
19 | import androidx.datastore.core.DataStore
20 | import androidx.datastore.preferences.core.Preferences
21 | import androidx.datastore.preferences.core.booleanPreferencesKey
22 | import androidx.datastore.preferences.core.edit
23 | import androidx.datastore.preferences.core.intPreferencesKey
24 | import kotlinx.coroutines.CoroutineScope
25 | import kotlinx.coroutines.Dispatchers
26 | import kotlinx.coroutines.SupervisorJob
27 | import kotlinx.coroutines.flow.Flow
28 | import kotlinx.coroutines.flow.SharingStarted
29 | import kotlinx.coroutines.flow.StateFlow
30 | import kotlinx.coroutines.flow.map
31 | import kotlinx.coroutines.flow.stateIn
32 | import kotlinx.coroutines.launch
33 |
34 | class PreferenceDataStore(
35 | private val dataStore: DataStore
36 | ) {
37 | private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
38 |
39 | fun updateUserNumber(userNumber: Int) {
40 | scope.launch {
41 | dataStore.edit {
42 | it[USER_NUMBER_KEY] = userNumber
43 | }
44 | }
45 | }
46 |
47 | fun markBalloonDisplayed() {
48 | scope.launch {
49 | dataStore.edit {
50 | it[BALLOON_CHANNELS_KEY] = true
51 | }
52 | }
53 | }
54 |
55 | val userNumber: StateFlow = dataStore.data.map { it[USER_NUMBER_KEY] }
56 | .stateIn(scope, SharingStarted.Eagerly, null)
57 |
58 | val shouldDisplayBalloon: Flow = dataStore.data.map { it[BALLOON_CHANNELS_KEY] }
59 |
60 | internal companion object {
61 | private val USER_NUMBER_KEY = intPreferencesKey("USER_NUMBER_KEY")
62 | private val BALLOON_CHANNELS_KEY = booleanPreferencesKey("BALLOON_CHANNELS_KEY")
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/core/datastore/src/main/kotlin/com/skydoves/gemini/core/datastore/di/DataStoreModule.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.core.datastore.di
18 |
19 | import android.content.Context
20 | import com.skydoves.gemini.core.datastore.PreferenceDataStore
21 | import com.skydoves.gemini.core.datastore.dataStore
22 | import dagger.Module
23 | import dagger.Provides
24 | import dagger.hilt.InstallIn
25 | import dagger.hilt.android.qualifiers.ApplicationContext
26 | import dagger.hilt.components.SingletonComponent
27 | import javax.inject.Singleton
28 |
29 | @Module
30 | @InstallIn(SingletonComponent::class)
31 | internal object DataStoreModule {
32 |
33 | @Provides
34 | @Singleton
35 | fun providePreferenceDataStore(@ApplicationContext context: Context): PreferenceDataStore {
36 | return PreferenceDataStore(context.dataStore)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/core/designsystem/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/designsystem/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.library.compose")
19 | id("skydoves.spotless")
20 | }
21 |
22 | android {
23 | namespace = "com.skydoves.gemini.core.designsystem"
24 | }
25 |
26 | dependencies {
27 | // image loading
28 | api(libs.landscapist.glide)
29 | api(libs.landscapist.animation)
30 | api(libs.landscapist.placeholder)
31 |
32 | api(libs.androidx.compose.runtime)
33 | api(libs.androidx.compose.ui)
34 | api(libs.androidx.compose.ui.tooling)
35 | api(libs.androidx.compose.ui.tooling.preview)
36 | api(libs.androidx.compose.material.iconsExtended)
37 | api(libs.androidx.compose.material)
38 | api(libs.androidx.compose.material3)
39 | api(libs.androidx.compose.foundation)
40 | api(libs.androidx.compose.foundation.layout)
41 | api(libs.androidx.compose.constraintlayout)
42 |
43 | api(libs.stream.compose)
44 | }
45 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/chat/GeminiReactionFactory.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.core.designsystem.chat
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.res.painterResource
21 | import com.skydoves.gemini.core.designsystem.R
22 | import io.getstream.chat.android.compose.ui.util.ReactionDrawable
23 | import io.getstream.chat.android.compose.ui.util.ReactionIcon
24 | import io.getstream.chat.android.compose.ui.util.ReactionIconFactory
25 |
26 | class GeminiReactionFactory(
27 | private val supportedReactions: Map = mapOf(
28 | "good" to ReactionDrawable(
29 | iconResId = R.drawable.thumbsup,
30 | selectedIconResId = R.drawable.thumbsup
31 | ),
32 | "love" to ReactionDrawable(
33 | iconResId = R.drawable.love,
34 | selectedIconResId = R.drawable.love
35 | ),
36 | "smile" to ReactionDrawable(
37 | iconResId = R.drawable.smile,
38 | selectedIconResId = R.drawable.smile
39 | ),
40 | "joy" to ReactionDrawable(
41 | iconResId = R.drawable.joy,
42 | selectedIconResId = R.drawable.joy
43 | ),
44 | "wink" to ReactionDrawable(
45 | iconResId = R.drawable.wink,
46 | selectedIconResId = R.drawable.wink
47 | )
48 | )
49 | ) : ReactionIconFactory {
50 |
51 | @Composable
52 | override fun createReactionIcon(type: String): ReactionIcon {
53 | val reactionDrawable = requireNotNull(supportedReactions[type])
54 | return ReactionIcon(
55 | painter = painterResource(reactionDrawable.iconResId),
56 | selectedPainter = painterResource(reactionDrawable.selectedIconResId)
57 | )
58 | }
59 |
60 | @Composable
61 | override fun createReactionIcons(): Map {
62 | return supportedReactions.mapValues {
63 | createReactionIcon(it.key)
64 | }
65 | }
66 |
67 | override fun isReactionSupported(type: String): Boolean {
68 | return supportedReactions.containsKey(type)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/component/Background.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.core.designsystem.component
18 |
19 | import androidx.compose.foundation.layout.Box
20 | import androidx.compose.foundation.layout.fillMaxSize
21 | import androidx.compose.material3.LocalAbsoluteTonalElevation
22 | import androidx.compose.material3.Surface
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.CompositionLocalProvider
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.graphics.Color
27 | import androidx.compose.ui.unit.Dp
28 | import androidx.compose.ui.unit.dp
29 | import com.skydoves.gemini.core.designsystem.theme.LocalBackgroundTheme
30 |
31 | /**
32 | * The main background for the app.
33 | * Uses [LocalBackgroundTheme] to set the color and tonal elevation of a [Box].
34 | *
35 | * @param modifier Modifier to be applied to the background.
36 | * @param content The background content.
37 | */
38 | @Composable
39 | fun GeminiBackground(
40 | modifier: Modifier = Modifier,
41 | content: @Composable () -> Unit
42 | ) {
43 | val color = LocalBackgroundTheme.current.color
44 | val tonalElevation = LocalBackgroundTheme.current.tonalElevation
45 | Surface(
46 | color = if (color == Color.Unspecified) Color.Transparent else color,
47 | tonalElevation = if (tonalElevation == Dp.Unspecified) 0.dp else tonalElevation,
48 | modifier = modifier.fillMaxSize()
49 | ) {
50 | CompositionLocalProvider(LocalAbsoluteTonalElevation provides 0.dp) {
51 | content()
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/component/GeminiSmallTopBar.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.core.designsystem.component
18 |
19 | import androidx.compose.foundation.layout.fillMaxWidth
20 | import androidx.compose.material3.Text
21 | import androidx.compose.material3.TopAppBar
22 | import androidx.compose.material3.TopAppBarDefaults
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.sp
27 | import com.skydoves.gemini.core.designsystem.theme.GeminiComposeTheme
28 | import com.skydoves.gemini.core.designsystem.theme.WHITE200
29 | import io.getstream.chat.android.compose.ui.theme.ChatTheme
30 |
31 | @Composable
32 | fun GeminiSmallTopBar(title: String) {
33 | TopAppBar(
34 | modifier = Modifier.fillMaxWidth(),
35 | title = {
36 | Text(
37 | text = title,
38 | color = WHITE200,
39 | fontSize = 22.sp
40 | )
41 | },
42 | colors = TopAppBarDefaults.mediumTopAppBarColors(
43 | containerColor = ChatTheme.colors.primaryAccent
44 | )
45 | )
46 | }
47 |
48 | @Preview
49 | @Composable
50 | private fun GeminiSmallTopBarPreview() {
51 | GeminiComposeTheme {
52 | GeminiSmallTopBar("Gemini Android")
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/component/LoadingIndicator.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.core.designsystem.component
18 |
19 | import androidx.compose.foundation.layout.BoxScope
20 | import androidx.compose.material3.CircularProgressIndicator
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Alignment
23 | import androidx.compose.ui.Modifier
24 | import com.skydoves.gemini.core.designsystem.theme.STREAM_PRIMARY
25 |
26 | @Composable
27 | fun BoxScope.LoadingIndicator() {
28 | CircularProgressIndicator(
29 | modifier = Modifier.align(Alignment.Center),
30 | color = STREAM_PRIMARY
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/composition/LocalOnFinishDispatcher.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.core.designsystem.composition
18 |
19 | import androidx.compose.runtime.compositionLocalOf
20 |
21 | val LocalOnFinishDispatcher = compositionLocalOf<(() -> Unit)?> { null }
22 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/theme/Background.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.core.designsystem.theme
18 |
19 | import androidx.compose.runtime.Immutable
20 | import androidx.compose.runtime.staticCompositionLocalOf
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.unit.Dp
23 |
24 | @Immutable
25 | data class BackgroundTheme(
26 | val color: Color = Color.Unspecified,
27 | val tonalElevation: Dp = Dp.Unspecified
28 | )
29 |
30 | val LocalBackgroundTheme = staticCompositionLocalOf { BackgroundTheme() }
31 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/theme/Color.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.core.designsystem.theme
18 |
19 | import androidx.compose.ui.graphics.Color
20 |
21 | val STREAM_PRIMARY = Color(0xFF005FFF)
22 | val STREAM_PRIMARY_LIGHT = Color(0x77528AE8)
23 | val BACKGROUND900 = Color(0xFF212121)
24 | val WHITE200 = Color(0xFFe0e0e0)
25 | val GRAY200 = Color(0xFF88898b)
26 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/kotlin/com/skydoves/gemini/core/designsystem/theme/Theme.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.core.designsystem.theme
18 |
19 | import androidx.compose.foundation.isSystemInDarkTheme
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.CompositionLocalProvider
22 | import androidx.compose.ui.graphics.Color
23 | import com.skydoves.gemini.core.designsystem.chat.GeminiReactionFactory
24 | import io.getstream.chat.android.compose.ui.theme.ChatTheme
25 | import io.getstream.chat.android.compose.ui.theme.StreamColors
26 |
27 | /** Light Android background theme */
28 | private val LightAndroidBackgroundTheme = BackgroundTheme(color = Color.White)
29 |
30 | /** Dark Android background theme */
31 | private val DarkAndroidBackgroundTheme = BackgroundTheme(color = BACKGROUND900)
32 |
33 | @Composable
34 | fun GeminiComposeTheme(
35 | darkTheme: Boolean = isSystemInDarkTheme(),
36 | content: @Composable () -> Unit
37 | ) {
38 | val backgroundTheme = if (darkTheme) DarkAndroidBackgroundTheme else LightAndroidBackgroundTheme
39 |
40 | CompositionLocalProvider(
41 | LocalBackgroundTheme provides backgroundTheme
42 | ) {
43 | val streamColors = if (darkTheme) {
44 | StreamColors.defaultDarkColors().copy(
45 | appBackground = BACKGROUND900,
46 | primaryAccent = STREAM_PRIMARY,
47 | ownMessagesBackground = STREAM_PRIMARY
48 | )
49 | } else {
50 | StreamColors.defaultColors().copy(
51 | primaryAccent = STREAM_PRIMARY,
52 | ownMessagesBackground = STREAM_PRIMARY_LIGHT
53 | )
54 | }
55 |
56 | ChatTheme(
57 | colors = streamColors,
58 | reactionIconFactory = GeminiReactionFactory(),
59 | content = content
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/joy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/core/designsystem/src/main/res/drawable/joy.png
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/love.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/core/designsystem/src/main/res/drawable/love.png
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/smile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/core/designsystem/src/main/res/drawable/smile.png
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/thumbsup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/core/designsystem/src/main/res/drawable/thumbsup.png
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/wink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/core/designsystem/src/main/res/drawable/wink.png
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Stream
19 | A random Gemini channel is created!
20 | Hi, I\'m Google\'s Gemini with Stream SDK. Ask me anything!
21 | You can add your Gemini chat channel!
22 |
23 |
--------------------------------------------------------------------------------
/core/model/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/model/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.spotless")
19 | id("kotlin-parcelize")
20 | id("com.google.devtools.ksp")
21 | }
22 |
23 | android {
24 | namespace = "com.skydoves.gemini.core.model"
25 | }
26 |
27 | dependencies {
28 | api(libs.stream.client)
29 | api(libs.moshi)
30 | ksp(libs.moshi.codegen)
31 | }
32 |
--------------------------------------------------------------------------------
/core/model/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/model/src/main/kotlin/com/skydoves/gemini/core/model/GeminiChannel.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.core.model
18 |
19 | import android.os.Parcelable
20 | import com.squareup.moshi.JsonClass
21 | import kotlinx.parcelize.Parcelize
22 |
23 | @Parcelize
24 | @JsonClass(generateAdapter = true)
25 | data class GeminiChannel(
26 | val id: String,
27 | val key: String,
28 | val name: String,
29 | val model: GeminiModel
30 | ) : Parcelable
31 |
--------------------------------------------------------------------------------
/core/model/src/main/kotlin/com/skydoves/gemini/core/model/GeminiModel.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.core.model
18 |
19 | import android.os.Parcelable
20 | import com.squareup.moshi.JsonClass
21 | import kotlinx.parcelize.Parcelize
22 |
23 | @Parcelize
24 | @JsonClass(generateAdapter = true)
25 | data class GeminiModel(
26 | val name: String,
27 | val temperature: Float,
28 | val candidateCount: Int,
29 | val maxOutputTokens: Int,
30 | val topK: Int,
31 | val topP: Float
32 | ) : Parcelable
33 |
--------------------------------------------------------------------------------
/core/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/navigation/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.library.compose")
19 | id("skydoves.android.hilt")
20 | id("skydoves.spotless")
21 | }
22 |
23 | android {
24 | namespace = "com.skydoves.gemini.core.navigation"
25 | }
26 |
27 | dependencies {
28 | implementation(project(":core:model"))
29 |
30 | implementation(libs.kotlinx.coroutines.android)
31 | api(libs.androidx.navigation.compose)
32 | }
33 |
--------------------------------------------------------------------------------
/core/navigation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/navigation/src/main/kotlin/com/skydoves/gemini/core/navigation/GeminiComposeNavigator.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.core.navigation
18 |
19 | import androidx.navigation.NavOptionsBuilder
20 | import androidx.navigation.navOptions
21 | import javax.inject.Inject
22 |
23 | class GeminiComposeNavigator @Inject constructor() : AppComposeNavigator() {
24 |
25 | override fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) {
26 | val options = optionsBuilder?.let { navOptions(it) }
27 | navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, options))
28 | }
29 |
30 | override fun navigateAndClearBackStack(route: String) {
31 | navigationCommands.tryEmit(
32 | ComposeNavigationCommand.NavigateToRoute(
33 | route,
34 | navOptions {
35 | popUpTo(0)
36 | }
37 | )
38 | )
39 | }
40 |
41 | override fun popUpTo(route: String, inclusive: Boolean) {
42 | navigationCommands.tryEmit(ComposeNavigationCommand.PopUpToRoute(route, inclusive))
43 | }
44 |
45 | override fun navigateBackWithResult(
46 | key: String,
47 | result: T,
48 | route: String?
49 | ) {
50 | navigationCommands.tryEmit(
51 | ComposeNavigationCommand.NavigateUpWithResult(
52 | key = key,
53 | result = result,
54 | route = route
55 | )
56 | )
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/core/navigation/src/main/kotlin/com/skydoves/gemini/core/navigation/GeminiScreens.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.core.navigation
18 |
19 | import androidx.navigation.NamedNavArgument
20 | import androidx.navigation.NavType
21 | import androidx.navigation.navArgument
22 |
23 | sealed class GeminiScreens(
24 | val route: String,
25 | val index: Int? = null,
26 | val navArguments: List = emptyList()
27 | ) {
28 | val name: String = route.appendArguments(navArguments)
29 |
30 | // channel screen
31 | data object Channels : GeminiScreens("channels")
32 |
33 | // message screen
34 | data object Messages : GeminiScreens(
35 | route = "messages",
36 | navArguments = listOf(
37 | navArgument(argument_channel_id) { type = NavType.StringType },
38 | navArgument(argument_channel_key) { type = NavType.StringType }
39 | )
40 | ) {
41 | fun createRoute(channelId: String, channelKey: String) =
42 | name.replace("{${navArguments[0].name}}", channelId)
43 | .replace("{${navArguments[1].name}}", channelKey)
44 | }
45 |
46 | companion object {
47 | const val argument_channel_id = "channelId"
48 | const val argument_channel_key = "channelKey"
49 | }
50 | }
51 |
52 | private fun String.appendArguments(navArguments: List): String {
53 | val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null }
54 | .takeIf { it.isNotEmpty() }
55 | ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" }
56 | .orEmpty()
57 | val optionalArguments = navArguments.filter { it.argument.defaultValue != null }
58 | .takeIf { it.isNotEmpty() }
59 | ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" }
60 | .orEmpty()
61 | return "$this$mandatoryArguments$optionalArguments"
62 | }
63 |
--------------------------------------------------------------------------------
/core/navigation/src/main/kotlin/com/skydoves/gemini/core/navigation/NavigationCommand.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.core.navigation
18 |
19 | import androidx.navigation.NavOptions
20 |
21 | sealed class NavigationCommand {
22 | data object NavigateUp : NavigationCommand()
23 | }
24 |
25 | sealed class ComposeNavigationCommand : NavigationCommand() {
26 | data class NavigateToRoute(val route: String, val options: NavOptions? = null) :
27 | ComposeNavigationCommand()
28 |
29 | data class NavigateUpWithResult(
30 | val key: String,
31 | val result: T,
32 | val route: String? = null
33 | ) : ComposeNavigationCommand()
34 |
35 | data class PopUpToRoute(val route: String, val inclusive: Boolean) : ComposeNavigationCommand()
36 | }
37 |
--------------------------------------------------------------------------------
/core/navigation/src/main/kotlin/com/skydoves/gemini/core/navigation/Navigator.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.core.navigation
18 |
19 | import androidx.navigation.NavController
20 | import androidx.navigation.NavOptionsBuilder
21 | import kotlinx.coroutines.flow.MutableSharedFlow
22 | import kotlinx.coroutines.flow.MutableStateFlow
23 | import kotlinx.coroutines.flow.onCompletion
24 | import kotlinx.coroutines.flow.onSubscription
25 |
26 | abstract class Navigator {
27 | val navigationCommands = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
28 |
29 | // We use a StateFlow here to allow ViewModels to start observing navigation results before the initial composition,
30 | // and still get the navigation result later
31 | val navControllerFlow = MutableStateFlow(null)
32 |
33 | fun navigateUp() {
34 | navigationCommands.tryEmit(NavigationCommand.NavigateUp)
35 | }
36 | }
37 |
38 | abstract class AppComposeNavigator : Navigator() {
39 | abstract fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null)
40 | abstract fun navigateBackWithResult(key: String, result: T, route: String?)
41 |
42 | abstract fun popUpTo(route: String, inclusive: Boolean)
43 | abstract fun navigateAndClearBackStack(route: String)
44 |
45 | suspend fun handleNavigationCommands(navController: NavController) {
46 | navigationCommands
47 | .onSubscription { this@AppComposeNavigator.navControllerFlow.value = navController }
48 | .onCompletion { this@AppComposeNavigator.navControllerFlow.value = null }
49 | .collect { navController.handleComposeNavigationCommand(it) }
50 | }
51 |
52 | private fun NavController.handleComposeNavigationCommand(navigationCommand: NavigationCommand) {
53 | when (navigationCommand) {
54 | is ComposeNavigationCommand.NavigateToRoute -> {
55 | navigate(navigationCommand.route, navigationCommand.options)
56 | }
57 |
58 | NavigationCommand.NavigateUp -> navigateUp()
59 | is ComposeNavigationCommand.PopUpToRoute -> popBackStack(
60 | navigationCommand.route,
61 | navigationCommand.inclusive
62 | )
63 |
64 | is ComposeNavigationCommand.NavigateUpWithResult<*> -> {
65 | navUpWithResult(navigationCommand)
66 | }
67 | }
68 | }
69 |
70 | private fun NavController.navUpWithResult(
71 | navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*>
72 | ) {
73 | val backStackEntry =
74 | navigationCommand.route?.let { getBackStackEntry(it) }
75 | ?: previousBackStackEntry
76 | backStackEntry?.savedStateHandle?.set(
77 | navigationCommand.key,
78 | navigationCommand.result
79 | )
80 |
81 | navigationCommand.route?.let {
82 | popBackStack(it, false)
83 | } ?: run {
84 | navigateUp()
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/core/navigation/src/main/kotlin/com/skydoves/gemini/core/navigation/di/NavigationModule.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.core.navigation.di
18 |
19 | import com.skydoves.gemini.core.navigation.AppComposeNavigator
20 | import com.skydoves.gemini.core.navigation.GeminiComposeNavigator
21 | import dagger.Binds
22 | import dagger.Module
23 | import dagger.hilt.InstallIn
24 | import dagger.hilt.components.SingletonComponent
25 | import javax.inject.Singleton
26 |
27 | @Module
28 | @InstallIn(SingletonComponent::class)
29 | internal abstract class NavigationModule {
30 |
31 | @Binds
32 | @Singleton
33 | abstract fun provideComposeNavigator(
34 | geminiComposeNavigator: GeminiComposeNavigator
35 | ): AppComposeNavigator
36 | }
37 |
--------------------------------------------------------------------------------
/core/network/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/network/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 |
17 | plugins {
18 | id("skydoves.android.library")
19 | id("skydoves.android.hilt")
20 | id("skydoves.spotless")
21 | }
22 |
23 | android {
24 | namespace = "com.skydoves.geminu.core.network"
25 | }
26 |
27 | dependencies {
28 | api(project(":core:model"))
29 |
30 | implementation(libs.androidx.startup)
31 | implementation(libs.stream.log)
32 |
33 | api(libs.okhttp.logging)
34 | api(libs.retrofit.core)
35 | api(libs.retrofit.moshi)
36 | api(libs.sandwich)
37 | }
--------------------------------------------------------------------------------
/core/network/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/network/src/main/kotlin/com/skydoves/gemini/core/network/Dispatchers.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.core.network
18 |
19 | import javax.inject.Qualifier
20 | import kotlin.annotation.AnnotationRetention.RUNTIME
21 |
22 | @Qualifier
23 | @Retention(RUNTIME)
24 | annotation class Dispatcher(val geminiDispatchers: GeminiDispatchers)
25 |
26 | enum class GeminiDispatchers {
27 | IO
28 | }
29 |
--------------------------------------------------------------------------------
/core/network/src/main/kotlin/com/skydoves/gemini/core/network/di/DispatchersModule.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.core.network.di
18 |
19 | import com.skydoves.gemini.core.network.Dispatcher
20 | import com.skydoves.gemini.core.network.GeminiDispatchers
21 | import dagger.Module
22 | import dagger.Provides
23 | import dagger.hilt.InstallIn
24 | import dagger.hilt.components.SingletonComponent
25 | import kotlinx.coroutines.CoroutineDispatcher
26 | import kotlinx.coroutines.Dispatchers
27 |
28 | @Module
29 | @InstallIn(SingletonComponent::class)
30 | internal object DispatchersModule {
31 |
32 | @Provides
33 | @Dispatcher(GeminiDispatchers.IO)
34 | fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
35 | }
36 |
--------------------------------------------------------------------------------
/core/network/src/main/kotlin/com/skydoves/gemini/core/network/di/NetworkModule.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.core.network.di
18 |
19 | import com.skydoves.gemini.core.network.service.ChannelService
20 | import com.skydoves.sandwich.retrofit.adapters.ApiResponseCallAdapterFactory
21 | import dagger.Module
22 | import dagger.Provides
23 | import dagger.hilt.InstallIn
24 | import dagger.hilt.components.SingletonComponent
25 | import io.getstream.log.android.BuildConfig
26 | import java.util.concurrent.TimeUnit
27 | import javax.inject.Singleton
28 | import okhttp3.OkHttpClient
29 | import okhttp3.logging.HttpLoggingInterceptor
30 | import retrofit2.Retrofit
31 | import retrofit2.converter.moshi.MoshiConverterFactory
32 | import retrofit2.create
33 |
34 | @Module
35 | @InstallIn(SingletonComponent::class)
36 | internal object NetworkModule {
37 |
38 | @Provides
39 | @Singleton
40 | fun provideOkHttpClient(): OkHttpClient {
41 | return OkHttpClient.Builder()
42 | .connectTimeout(60, TimeUnit.SECONDS)
43 | .readTimeout(60, TimeUnit.SECONDS)
44 | .writeTimeout(15, TimeUnit.SECONDS)
45 | .apply {
46 | if (BuildConfig.DEBUG) {
47 | this.addNetworkInterceptor(
48 | HttpLoggingInterceptor().apply {
49 | level = HttpLoggingInterceptor.Level.BODY
50 | }
51 | )
52 | }
53 | }
54 | .build()
55 | }
56 |
57 | @Provides
58 | @Singleton
59 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
60 | return Retrofit.Builder()
61 | .client(okHttpClient)
62 | .baseUrl(
63 | "https://gist.githubusercontent.com/skydoves/a572b299a907753587818be56f3d3449/raw/" +
64 | "5c3e9a76e3848d83cc679a372c8b4875bb94b193/"
65 | )
66 | .addConverterFactory(MoshiConverterFactory.create().asLenient())
67 | .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
68 | .build()
69 | }
70 |
71 | @Provides
72 | @Singleton
73 | fun provideChannelService(retrofit: Retrofit): ChannelService = retrofit.create()
74 | }
75 |
--------------------------------------------------------------------------------
/core/network/src/main/kotlin/com/skydoves/gemini/core/network/service/ChannelService.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.core.network.service
18 |
19 | import com.skydoves.gemini.core.model.GeminiChannel
20 | import com.skydoves.sandwich.ApiResponse
21 | import retrofit2.http.GET
22 |
23 | interface ChannelService {
24 |
25 | @GET("GeminiModel.json")
26 | suspend fun geminiChannels(): ApiResponse>
27 | }
28 |
--------------------------------------------------------------------------------
/feature/channels/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/channels/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.library.compose")
19 | id("skydoves.android.feature")
20 | id("skydoves.android.hilt")
21 | id("skydoves.spotless")
22 | }
23 |
24 | android {
25 | namespace = "com.skydoves.gemini.feature.channels"
26 | }
27 |
28 | dependencies {
29 | // Stream chat Compose
30 | api(libs.stream.compose)
31 | api(libs.stream.offline)
32 |
33 | implementation(libs.androidx.lifecycle.runtimeCompose)
34 | implementation(libs.androidx.lifecycle.viewModelCompose)
35 | implementation(libs.androidx.startup)
36 |
37 | implementation(libs.balloon.compose)
38 | }
--------------------------------------------------------------------------------
/feature/channels/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/feature/channels/src/main/kotlin/com/skydoves/gemini/feature/channels/ChannelViewModel.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.feature.channels
18 |
19 | import androidx.lifecycle.ViewModel
20 | import com.skydoves.gemini.core.data.coroutines.asStateFlow
21 | import com.skydoves.gemini.core.data.coroutines.publishedFlow
22 | import com.skydoves.gemini.core.data.repository.ChannelRepository
23 | import com.skydoves.sandwich.isSuccess
24 | import com.skydoves.sandwich.messageOrNull
25 | import dagger.hilt.android.lifecycle.HiltViewModel
26 | import io.getstream.chat.android.models.InitializationState
27 | import javax.inject.Inject
28 | import kotlinx.coroutines.flow.MutableSharedFlow
29 | import kotlinx.coroutines.flow.SharedFlow
30 | import kotlinx.coroutines.flow.combine
31 | import kotlinx.coroutines.flow.flatMapLatest
32 | import kotlinx.coroutines.flow.flowOf
33 | import kotlinx.coroutines.flow.mapLatest
34 |
35 | @HiltViewModel
36 | class ChannelViewModel @Inject constructor(
37 | private val repository: ChannelRepository
38 | ) : ViewModel() {
39 |
40 | private val channelEvent: MutableSharedFlow = publishedFlow()
41 | internal val isInitialized = repository.streamInitializationFlow().mapLatest {
42 | it == InitializationState.COMPLETE
43 | }
44 | internal val preferenceDataStore = repository.shouldDisplayBalloon().asStateFlow(false)
45 |
46 | private val userFlow = repository.streamUserFlow()
47 | internal val channelUiState: SharedFlow =
48 | combine(channelEvent, userFlow) { event, user ->
49 | event to user
50 | }.flatMapLatest { pair ->
51 | val (event, user) = pair
52 | when (event) {
53 | is ChannelEvent.JoinDefaultChannels -> {
54 | val response = repository.joinDefaultChannels(user = user)
55 | if (response.isSuccess) {
56 | flowOf(ChannelUiState.JoinSuccess)
57 | } else {
58 | flowOf(ChannelUiState.Error(response.messageOrNull))
59 | }
60 | }
61 |
62 | is ChannelEvent.CreateRandomChannel -> {
63 | val result = repository.createRandomChannel(user = user)
64 | if (result.isSuccess) {
65 | flowOf(ChannelUiState.CreateSuccess)
66 | } else {
67 | flowOf(ChannelUiState.Error(result.errorOrNull()?.message))
68 | }
69 | }
70 |
71 | is ChannelEvent.MarkBalloonDisplayed -> {
72 | repository.markBalloonDisplayed()
73 | flowOf(ChannelUiState.Idle)
74 | }
75 | }
76 | }.asStateFlow(ChannelUiState.Idle)
77 |
78 | internal fun handleEvent(event: ChannelEvent) {
79 | channelEvent.tryEmit(event)
80 | }
81 | }
82 |
83 | sealed interface ChannelEvent {
84 |
85 | data object JoinDefaultChannels : ChannelEvent
86 |
87 | data object CreateRandomChannel : ChannelEvent
88 |
89 | data object MarkBalloonDisplayed : ChannelEvent
90 | }
91 |
92 | sealed interface ChannelUiState {
93 |
94 | data object Idle : ChannelUiState
95 |
96 | data object JoinSuccess : ChannelUiState
97 |
98 | data object CreateSuccess : ChannelUiState
99 |
100 | data class Error(val reason: String?) : ChannelUiState
101 | }
102 |
--------------------------------------------------------------------------------
/feature/channels/src/main/kotlin/com/skydoves/gemini/feature/channels/GeminiChannels.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.feature.channels
18 |
19 | import android.widget.Toast
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.fillMaxWidth
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.foundation.layout.size
25 | import androidx.compose.foundation.shape.CircleShape
26 | import androidx.compose.material.Text
27 | import androidx.compose.material.icons.Icons
28 | import androidx.compose.material.icons.filled.AddComment
29 | import androidx.compose.material3.FloatingActionButton
30 | import androidx.compose.material3.Icon
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.runtime.DisposableEffect
33 | import androidx.compose.runtime.LaunchedEffect
34 | import androidx.compose.runtime.collectAsState
35 | import androidx.compose.runtime.getValue
36 | import androidx.compose.ui.Alignment
37 | import androidx.compose.ui.Modifier
38 | import androidx.compose.ui.graphics.Color
39 | import androidx.compose.ui.platform.LocalContext
40 | import androidx.compose.ui.res.stringResource
41 | import androidx.compose.ui.text.style.TextAlign
42 | import androidx.compose.ui.unit.dp
43 | import androidx.hilt.navigation.compose.hiltViewModel
44 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
45 | import com.skydoves.balloon.compose.Balloon
46 | import com.skydoves.gemini.core.data.chat.STREAM_RANDOM_CHANNEL_KEY
47 | import com.skydoves.gemini.core.designsystem.component.LoadingIndicator
48 | import com.skydoves.gemini.core.designsystem.composition.LocalOnFinishDispatcher
49 | import com.skydoves.gemini.core.designsystem.theme.GeminiComposeTheme
50 | import com.skydoves.gemini.core.designsystem.theme.STREAM_PRIMARY
51 | import com.skydoves.gemini.core.navigation.AppComposeNavigator
52 | import com.skydoves.gemini.core.navigation.GeminiScreens
53 | import io.getstream.chat.android.compose.ui.channels.ChannelsScreen
54 |
55 | @Composable
56 | fun GeminiChannels(
57 | modifier: Modifier,
58 | composeNavigator: AppComposeNavigator,
59 | viewModel: ChannelViewModel = hiltViewModel()
60 | ) {
61 | val onFinishDispatcher = LocalOnFinishDispatcher.current
62 | val uiState by viewModel.channelUiState.collectAsState(ChannelUiState.Idle)
63 | val isInitialized by viewModel.isInitialized.collectAsStateWithLifecycle(initialValue = false)
64 |
65 | handleUiState(uiState = uiState)
66 |
67 | LaunchedEffect(key1 = isInitialized) {
68 | if (isInitialized && uiState == ChannelUiState.Idle) {
69 | viewModel.handleEvent(ChannelEvent.JoinDefaultChannels)
70 | }
71 | }
72 |
73 | GeminiComposeTheme {
74 | Box(modifier = modifier.fillMaxSize()) {
75 | if (isInitialized) {
76 | ChannelsScreen(
77 | isShowingHeader = false,
78 | onChannelClick = { channel ->
79 | val key = channel.extraData[STREAM_RANDOM_CHANNEL_KEY].toString()
80 | composeNavigator.navigate(
81 | GeminiScreens.Messages.createRoute(
82 | channelId = channel.cid,
83 | channelKey = key
84 | )
85 | )
86 | },
87 | onBackPressed = { onFinishDispatcher?.invoke() }
88 | )
89 |
90 | val shouldDisplayBalloon by viewModel.preferenceDataStore.collectAsState()
91 | Balloon(
92 | modifier = Modifier
93 | .align(Alignment.BottomEnd)
94 | .padding(16.dp)
95 | .size(58.dp),
96 | builder = rememberFloatingBalloon(),
97 | balloonContent = {
98 | Text(
99 | modifier = Modifier
100 | .padding(12.dp)
101 | .fillMaxWidth(),
102 | text = stringResource(
103 | id = com.skydoves.gemini.core.designsystem.R.string.message_balloon
104 | ),
105 | textAlign = TextAlign.Center,
106 | color = Color.White
107 | )
108 | }
109 | ) { balloonWindow ->
110 | DisposableEffect(key1 = Unit) {
111 | if (!shouldDisplayBalloon) {
112 | balloonWindow.showAlignTop()
113 | }
114 |
115 | balloonWindow.setOnBalloonDismissListener {
116 | viewModel.handleEvent(ChannelEvent.MarkBalloonDisplayed)
117 | balloonWindow.dismiss()
118 | }
119 |
120 | onDispose { viewModel.handleEvent(ChannelEvent.MarkBalloonDisplayed) }
121 | }
122 |
123 | FloatingActionButton(
124 | modifier = Modifier.matchParentSize(),
125 | containerColor = STREAM_PRIMARY,
126 | shape = CircleShape,
127 | onClick = { viewModel.handleEvent(ChannelEvent.CreateRandomChannel) }
128 | ) {
129 | Icon(
130 | imageVector = Icons.Filled.AddComment,
131 | contentDescription = null,
132 | tint = Color.White
133 | )
134 | }
135 | }
136 | } else {
137 | LoadingIndicator()
138 | }
139 | }
140 | }
141 | }
142 |
143 | @Composable
144 | private fun handleUiState(
145 | uiState: ChannelUiState
146 | ) {
147 | val context = LocalContext.current
148 | LaunchedEffect(key1 = uiState) {
149 | when (uiState) {
150 | is ChannelUiState.Error -> Toast.makeText(context, uiState.reason, Toast.LENGTH_SHORT).show()
151 | else -> Unit
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/feature/channels/src/main/kotlin/com/skydoves/gemini/feature/channels/RememberFloatingBalloon.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.feature.channels
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.graphics.Color
21 | import com.skydoves.balloon.ArrowOrientation
22 | import com.skydoves.balloon.ArrowPositionRules
23 | import com.skydoves.balloon.BalloonAnimation
24 | import com.skydoves.balloon.BalloonSizeSpec
25 | import com.skydoves.balloon.compose.rememberBalloonBuilder
26 | import com.skydoves.balloon.compose.setBackgroundColor
27 | import com.skydoves.balloon.compose.setTextColor
28 | import com.skydoves.gemini.core.designsystem.theme.STREAM_PRIMARY
29 |
30 | @Composable
31 | internal fun rememberFloatingBalloon() = rememberBalloonBuilder {
32 | setWidthRatio(1.0f)
33 | setHeight(BalloonSizeSpec.WRAP)
34 | setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)
35 | setArrowOrientation(ArrowOrientation.BOTTOM)
36 | setArrowPosition(0.5f)
37 | setArrowSize(10)
38 | setPadding(12)
39 | setMarginHorizontal(12)
40 | setTextSize(15f)
41 | setCornerRadius(8f)
42 | setTextColor(Color.White)
43 | setBackgroundColor(STREAM_PRIMARY)
44 | setBalloonAnimation(BalloonAnimation.ELASTIC)
45 | }
46 |
--------------------------------------------------------------------------------
/feature/chat/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/chat/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 | plugins {
17 | id("skydoves.android.library")
18 | id("skydoves.android.library.compose")
19 | id("skydoves.android.feature")
20 | id("skydoves.android.hilt")
21 | id("skydoves.spotless")
22 | id(libs.plugins.google.secrets.get().pluginId)
23 | }
24 |
25 | android {
26 | namespace = "com.skydoves.gemini.feature.chat"
27 | }
28 |
29 | secrets {
30 | propertiesFileName = "secrets.properties"
31 | defaultPropertiesFileName = "secrets.defaults.properties"
32 | }
33 |
34 | dependencies {
35 | // Stream chat Compose
36 | api(libs.stream.compose)
37 | api(libs.stream.offline)
38 |
39 | implementation(libs.androidx.lifecycle.runtimeCompose)
40 | implementation(libs.androidx.lifecycle.viewModelCompose)
41 | implementation(libs.androidx.startup)
42 |
43 | implementation(libs.balloon.compose)
44 | }
45 |
--------------------------------------------------------------------------------
/feature/chat/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/feature/chat/src/main/kotlin/com/skydoves/gemini/feature/chat/ChatViewModel.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.feature.chat
18 |
19 | import android.graphics.Bitmap
20 | import android.graphics.BitmapFactory
21 | import androidx.lifecycle.SavedStateHandle
22 | import androidx.lifecycle.ViewModel
23 | import androidx.lifecycle.viewModelScope
24 | import com.google.ai.client.generativeai.Chat
25 | import com.google.ai.client.generativeai.GenerativeModel
26 | import com.google.ai.client.generativeai.type.content
27 | import com.skydoves.gemini.core.data.chat.STREAM_CHANNEL_GEMINI_FLAG
28 | import com.skydoves.gemini.core.data.coroutines.asStateFlow
29 | import com.skydoves.gemini.core.data.repository.ChatRepository
30 | import com.skydoves.gemini.core.data.utils.Empty
31 | import com.skydoves.gemini.core.model.GeminiChannel
32 | import com.skydoves.gemini.core.navigation.GeminiScreens.Companion.argument_channel_id
33 | import com.skydoves.gemini.core.navigation.GeminiScreens.Companion.argument_channel_key
34 | import com.skydoves.gemini.feature.chat.extension.toGenerativeModel
35 | import dagger.hilt.android.lifecycle.HiltViewModel
36 | import io.getstream.chat.android.client.ChatClient
37 | import io.getstream.chat.android.models.Message
38 | import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState
39 | import io.getstream.chat.android.ui.common.state.messages.list.MessageListItemState
40 | import io.getstream.log.streamLog
41 | import java.io.FileInputStream
42 | import java.util.UUID
43 | import javax.inject.Inject
44 | import kotlinx.coroutines.flow.Flow
45 | import kotlinx.coroutines.flow.MutableStateFlow
46 | import kotlinx.coroutines.flow.StateFlow
47 | import kotlinx.coroutines.flow.combine
48 | import kotlinx.coroutines.flow.filter
49 | import kotlinx.coroutines.flow.mapLatest
50 | import kotlinx.coroutines.launch
51 |
52 | @HiltViewModel
53 | class ChatViewModel @Inject constructor(
54 | repository: ChatRepository,
55 | chatClient: ChatClient,
56 | savedStateHandle: SavedStateHandle
57 | ) : ViewModel() {
58 |
59 | private val channelId: String = savedStateHandle.get(argument_channel_id) ?: String.Empty
60 | private val channelKey: String =
61 | savedStateHandle.get(argument_channel_key) ?: String.Empty
62 | private val channelClient = chatClient.channel(channelId)
63 |
64 | private val geminiChannelFlow: Flow = repository.getGeminiChannel(channelKey)
65 |
66 | private val generativeModel: StateFlow =
67 | geminiChannelFlow.mapLatest { it.model.toGenerativeModel() }.asStateFlow(null)
68 |
69 | private val generativeChat: StateFlow =
70 | generativeModel.mapLatest { it?.startChat() }.asStateFlow(null)
71 |
72 | private val messageItemSet = MutableStateFlow>(setOf())
73 | val isLoading: StateFlow =
74 | combine(messageItemSet, generativeChat) { messageItemSet, chat ->
75 | messageItemSet.isNotEmpty() || chat == null
76 | }.asStateFlow(false)
77 |
78 | private val mutableError: MutableStateFlow = MutableStateFlow(String.Empty)
79 | val errorMessage: StateFlow = mutableError
80 | .filter { it.isNotEmpty() }
81 | .asStateFlow(String.Empty)
82 |
83 | val isMessageEmpty: StateFlow = repository
84 | .watchIsChannelMessageEmpty(channelId).asStateFlow(false)
85 |
86 | fun addHistories(messages: List) {
87 | val history = generativeChat.value?.history
88 | if (history?.isEmpty() == true) {
89 | messages.filterIsInstance(MessageItemState::class.java).forEach { messageState ->
90 | val content = if (messageState.message.extraData[STREAM_CHANNEL_GEMINI_FLAG] == true) {
91 | content(role = "model") { text(messageState.message.text) }
92 | } else {
93 | content(role = "user") { text(messageState.message.text) }
94 | }
95 | history.add(content)
96 | }
97 | }
98 | }
99 |
100 | fun sendStreamChatMessage(text: String) {
101 | viewModelScope.launch {
102 | channelClient.sendMessage(
103 | message = Message(
104 | id = UUID.randomUUID().toString(),
105 | cid = channelClient.cid,
106 | text = text,
107 | extraData = mutableMapOf(STREAM_CHANNEL_GEMINI_FLAG to true)
108 | )
109 | ).await()
110 | }
111 | }
112 |
113 | fun sendMessage(message: Message) {
114 | val containsAttachment = message.attachments.isNotEmpty()
115 | messageItemSet.value += message.text
116 |
117 | viewModelScope.launch {
118 | try {
119 | val responseText = if (containsAttachment) {
120 | val bitmaps = message.attachments.map {
121 | val original = BitmapFactory.decodeStream(FileInputStream(it.upload))
122 | Bitmap.createScaledBitmap(
123 | original,
124 | (original.width * 0.5f).toInt(),
125 | (original.height * 0.5f).toInt(),
126 | true
127 | )
128 | }
129 | photoReasoning(message, bitmaps)
130 | } else {
131 | sendTextMessage(message.text)
132 | }
133 | streamLog { "gemini response success: $responseText" }
134 | messageItemSet.value -= message.text
135 | } catch (e: Exception) {
136 | val error = e.localizedMessage.orEmpty()
137 | messageItemSet.value -= messageItemSet.value
138 | mutableError.value = error
139 | streamLog { "gemini response failed: $error" }
140 | }
141 | }
142 | }
143 |
144 | private suspend fun sendTextMessage(text: String): String? {
145 | val generativeChat = generativeChat.value ?: return null
146 | val response = generativeChat.sendMessage(text)
147 | val responseText = response.text
148 | if (responseText != null) {
149 | channelClient.sendMessage(
150 | message = Message(
151 | id = UUID.randomUUID().toString(),
152 | cid = channelClient.cid,
153 | text = responseText,
154 | extraData = mutableMapOf(STREAM_CHANNEL_GEMINI_FLAG to true)
155 | )
156 | ).await()
157 | }
158 | return responseText
159 | }
160 |
161 | private suspend fun photoReasoning(message: Message, bitmaps: List): String? {
162 | val text = message.text
163 | val prompt = "Look at the image(s), and then answer the following question: $text"
164 | val generativeModel = generativeModel.value ?: return null
165 | val content = content {
166 | for (bitmap in bitmaps) {
167 | image(bitmap)
168 | }
169 | text(prompt)
170 | }
171 | val response = generativeModel.generateContent(content)
172 | val responseText = response.text
173 | if (responseText != null) {
174 | channelClient.sendMessage(
175 | message = Message(
176 | id = UUID.randomUUID().toString(),
177 | cid = channelClient.cid,
178 | text = responseText,
179 | extraData = mutableMapOf(STREAM_CHANNEL_GEMINI_FLAG to true)
180 | )
181 | ).await()
182 | }
183 | return responseText
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/feature/chat/src/main/kotlin/com/skydoves/gemini/feature/chat/extension/GeminiExtension.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.feature.chat.extension
18 |
19 | import com.google.ai.client.generativeai.GenerativeModel
20 | import com.google.ai.client.generativeai.type.generationConfig
21 | import com.skydoves.gemini.core.model.GeminiModel
22 | import com.skydoves.gemini.feature.chat.BuildConfig
23 |
24 | internal fun GeminiModel.toGenerativeModel(): GenerativeModel {
25 | return GenerativeModel(
26 | modelName = name,
27 | apiKey = BuildConfig.GEMINI_API_KEY,
28 | generationConfig = generationConfig {
29 | this.temperature = this@toGenerativeModel.temperature
30 | this.candidateCount = this@toGenerativeModel.candidateCount
31 | this.topK = this@toGenerativeModel.topK
32 | this.topP = this@toGenerativeModel.topP
33 | }
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/figures/figure0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/figure0.png
--------------------------------------------------------------------------------
/figures/figure1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/figure1.png
--------------------------------------------------------------------------------
/figures/figure2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/figure2.png
--------------------------------------------------------------------------------
/figures/figure3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/figure3.png
--------------------------------------------------------------------------------
/figures/gemini0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/gemini0.png
--------------------------------------------------------------------------------
/figures/gemini1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/gemini1.png
--------------------------------------------------------------------------------
/figures/gemini2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/gemini2.png
--------------------------------------------------------------------------------
/figures/stream0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream0.png
--------------------------------------------------------------------------------
/figures/stream1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream1.png
--------------------------------------------------------------------------------
/figures/stream2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream2.png
--------------------------------------------------------------------------------
/figures/stream3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream3.png
--------------------------------------------------------------------------------
/figures/stream4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream4.png
--------------------------------------------------------------------------------
/figures/stream5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream5.png
--------------------------------------------------------------------------------
/figures/stream6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/figures/stream6.png
--------------------------------------------------------------------------------
/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 |
10 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory
11 | org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true
12 |
13 | # https://docs.gradle.org/current/userguide/build_cache.html
14 | org.gradle.caching=true
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | org.gradle.parallel=true
20 |
21 | # Configure only necessary projects, useful with multimodule projects
22 | org.gradle.configureondemand=true
23 |
24 | # AndroidX Migration https://developer.android.com/jetpack/androidx/migrate
25 | android.useAndroidX=true
26 |
27 | # Enables namespacing of each library's R class so that its R class includes only the
28 | # resources declared in the library itself and none from the library's dependencies,
29 | # thereby reducing the size of the R class for that library
30 | android.nonTransitiveRClass=true
31 | android.defaults.buildfeatures.buildconfig=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/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 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/previews/preview0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/previews/preview0.png
--------------------------------------------------------------------------------
/previews/preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/previews/preview1.png
--------------------------------------------------------------------------------
/previews/preview2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetStream/gemini-android/0720909945304359686b98409b1632ca8ddac9e5/previews/preview2.gif
--------------------------------------------------------------------------------
/secrets.defaults.properties:
--------------------------------------------------------------------------------
1 | STREAM_API_KEY=aaaaaaaaaa
2 | GEMINI_API_KEY=aaaaaaaaaa
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | pluginManagement {
3 | includeBuild("build-logic")
4 | repositories {
5 | gradlePluginPortal()
6 | google()
7 | mavenCentral()
8 | maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
9 | }
10 | }
11 | dependencyResolutionManagement {
12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
13 | repositories {
14 | google()
15 | mavenCentral()
16 | maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
17 | }
18 | }
19 | rootProject.name = "gemini-android"
20 | include(":app")
21 | include(":core:model")
22 | include(":core:designsystem")
23 | include(":core:navigation")
24 | include(":core:network")
25 | include(":core:data")
26 | include(":core:database")
27 | include(":core:datastore")
28 | include(":feature:chat")
29 | include(":feature:channels")
30 | include(":baseline-profile")
--------------------------------------------------------------------------------
/spotless/copyright.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 |
--------------------------------------------------------------------------------
/spotless/copyright.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 |
--------------------------------------------------------------------------------
/spotless/copyright.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/spotless/spotless.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.diffplug.spotless"
2 |
3 | spotless {
4 | kotlin {
5 | target "**/*.kt"
6 | targetExclude "**/build/**/*.kt"
7 | ktlint().setUseExperimental(true).editorConfigOverride(['indent_size': '2', 'continuation_indent_size': '2'])
8 | licenseHeaderFile "$rootDir/spotless/copyright.kt"
9 | trimTrailingWhitespace()
10 | endWithNewline()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------