├── .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 ├── -force ├── .gitignore ├── benchmark-rules.pro ├── build.gradle.kts ├── proguard-rules.pro ├── release │ └── output-metadata.json └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── skydoves │ │ │ └── chatgpt │ │ │ ├── ChatGPTApp.kt │ │ │ ├── MainActivity.kt │ │ │ ├── initializer │ │ │ └── AppInitializer.kt │ │ │ ├── navigation │ │ │ ├── ChatGPTNavHost.kt │ │ │ └── ChatGPTNavigation.kt │ │ │ └── ui │ │ │ └── ChatGPTMain.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── openai.jpeg │ │ ├── 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 ├── benchmark ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── chatgpt │ └── benchmark │ ├── BaselineProfileGenerator.kt │ ├── ChannelActions.kt │ ├── GPTScenarios.kt │ ├── MessageActions.kt │ └── Utils.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 │ │ └── chatgpt │ │ ├── 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 │ └── chatgpt │ └── core │ └── data │ ├── chat │ └── ChatModels.kt │ ├── coroutines │ └── WhileSubscribedOrRetained.kt │ ├── di │ └── DataModule.kt │ └── repository │ ├── GPTChannelRepository.kt │ ├── GPTChannelRepositoryImpl.kt │ ├── GPTMessageRepository.kt │ └── GPTMessageRepositoryImpl.kt ├── core-designsystem ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── chatgpt │ └── core │ └── designsystem │ ├── component │ ├── Background.kt │ ├── ChatGPTLoadingIndicator.kt │ └── ChatGPTSmallTopBar.kt │ ├── composition │ └── LocalOnFinishDispatcher.kt │ └── theme │ ├── Background.kt │ ├── Color.kt │ ├── Theme.kt │ └── Typography.kt ├── core-model ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── chatgpt │ └── core │ └── model │ ├── GPTChoice.kt │ ├── GPTMessage.kt │ ├── GPTUsage.kt │ └── network │ ├── GPTChatRequest.kt │ └── GPTChatResponse.kt ├── core-navigation ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── chatgpt │ └── core │ └── navigation │ ├── ChatGPTComposeNavigator.kt │ ├── ChatGPTScreens.kt │ ├── NavigationCommand.kt │ ├── NavigationModule.kt │ └── Navigator.kt ├── core-network ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── skydoves │ │ └── chatgpt │ │ └── core │ │ └── network │ │ ├── ChatGPTDispatchers.kt │ │ ├── GPTInterceptor.kt │ │ ├── di │ │ ├── DispatchersModule.kt │ │ └── NetworkModule.kt │ │ └── service │ │ └── ChatGPTService.kt │ └── res │ └── values │ └── strings.xml ├── core-preferences ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── skydoves │ └── chatgpt │ └── core │ └── preferences │ ├── Preferences.kt │ ├── delegate │ ├── BooleanPreferenceDelegate.kt │ └── StringPreferenceDelegate.kt │ └── di │ └── PreferencesModule.kt ├── feature-chat ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── skydoves │ │ └── chatgpt │ │ └── feature │ │ └── chat │ │ ├── channels │ │ ├── ChatGPTChannels.kt │ │ ├── ChatGPTChannelsViewModel.kt │ │ └── RememberFloatingBalloon.kt │ │ ├── di │ │ ├── ChatEntryPoint.kt │ │ └── ChatModule.kt │ │ ├── initializer │ │ ├── StreamChatInitializer.kt │ │ └── StreamLogInitializer.kt │ │ ├── messages │ │ ├── ChatGPTMessages.kt │ │ └── ChatGPTMessagesViewModel.kt │ │ ├── reactions │ │ └── ChatGPTReactionFactory.kt │ │ ├── theme │ │ └── ChatGPTStreamTheme.kt │ │ └── worker │ │ └── ChatGPTMessageWorker.kt │ └── res │ ├── drawable │ ├── joy.png │ ├── love.png │ ├── smile.png │ ├── thumbsup.png │ └── wink.png │ └── values │ └── strings.xml ├── feature-login ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── skydoves │ │ └── chatgpt │ │ └── feature │ │ └── login │ │ └── ChatGPTLogin.kt │ └── res │ └── values │ └── strings.xml ├── figures ├── figure0.png ├── figure1.png ├── figure2.png ├── figure3.png ├── figure4.png ├── login0.png ├── login1.png ├── login2.png ├── stream0.png ├── stream1.png ├── stream10.png ├── stream11.png ├── stream12.png ├── stream13.png ├── stream14.png ├── stream2.png ├── stream3.png ├── stream4.png ├── stream5.png ├── stream6.png ├── stream7.png ├── stream8.png └── stream9.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/-force: -------------------------------------------------------------------------------- 1 | # This is a configuration file for R8 2 | 3 | -verbose 4 | -allowaccessmodification 5 | -repackageclasses 6 | 7 | # Note that you cannot just include these flags in your own 8 | # configuration file; if you are including this file, optimization 9 | # will be turned off. You'll need to either edit this file, or 10 | # duplicate the contents of this file and remove the include of this 11 | # file from your project's proguard.config path property. 12 | 13 | # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native 14 | -keepclasseswithmembernames class * { 15 | native ; 16 | } 17 | 18 | # We only need to keep ComposeView 19 | -keep public class androidx.compose.ui.platform.ComposeView { 20 | public (android.content.Context, android.util.AttributeSet); 21 | } 22 | 23 | # For enumeration classes 24 | -keepclassmembers enum * { 25 | public static **[] values(); 26 | public static ** valueOf(java.lang.String); 27 | } 28 | 29 | -keep class * implements android.os.Parcelable { 30 | public static final android.os.Parcelable$Creator *; 31 | } 32 | 33 | # AndroidX + support library contains references to newer platform versions. 34 | # Don't warn about those in case this app is linking against an older 35 | # platform version. We know about them, and they are safe. 36 | -dontwarn android.support.** 37 | -dontwarn androidx.** 38 | 39 | -keepattributes SourceFile, 40 | LineNumberTable, 41 | RuntimeVisibleAnnotations, 42 | RuntimeVisibleParameterAnnotations, 43 | RuntimeVisibleTypeAnnotations, 44 | AnnotationDefault 45 | 46 | -renamesourcefileattribute SourceFile 47 | 48 | -dontwarn org.conscrypt.** 49 | -dontwarn org.bouncycastle.** 50 | -dontwarn org.openjsse.** 51 | 52 | # Dagger 53 | -dontwarn com.google.errorprone.annotations.* 54 | 55 | # Retain the generic signature of retrofit2.Call until added to Retrofit. 56 | # Issue: https://github.com/square/retrofit/issues/3580. 57 | # Pull request: https://github.com/square/retrofit/pull/3579. 58 | -keep,allowobfuscation,allowshrinking class retrofit2.Call 59 | 60 | # See https://issuetracker.google.com/issues/265188224 61 | -keep,allowshrinking class * extends androidx.compose.ui.node.ModifierNodeElement {} 62 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/benchmark-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -dontwarn com.google.errorprone.annotations.InlineMe 3 | -------------------------------------------------------------------------------- /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 | @file:Suppress("UnstableApiUsage") 17 | 18 | import java.io.File 19 | import java.io.FileInputStream 20 | import java.util.* 21 | 22 | plugins { 23 | id("skydoves.android.application") 24 | id("skydoves.android.application.compose") 25 | id("skydoves.android.hilt") 26 | id("skydoves.spotless") 27 | id("kotlin-parcelize") 28 | id("dagger.hilt.android.plugin") 29 | id("com.google.devtools.ksp") 30 | id(libs.plugins.google.secrets.get().pluginId) 31 | id(libs.plugins.baseline.profile.get().pluginId) 32 | } 33 | 34 | val keystoreProperties = Properties() 35 | val keystorePropertiesFile = File(rootProject.rootDir, "keystore.properties") 36 | if (keystorePropertiesFile.exists()) { 37 | keystoreProperties.load(FileInputStream(keystorePropertiesFile)) 38 | } 39 | 40 | android { 41 | namespace = "com.skydoves.chatgpt" 42 | compileSdk = Configurations.compileSdk 43 | 44 | defaultConfig { 45 | applicationId = "com.skydoves.chatgpt" 46 | minSdk = Configurations.minSdk 47 | targetSdk = Configurations.targetSdk 48 | versionCode = Configurations.versionCode 49 | versionName = Configurations.versionName 50 | } 51 | 52 | packaging { 53 | resources { 54 | excludes.add("/META-INF/{AL2.0,LGPL2.1}") 55 | } 56 | } 57 | 58 | signingConfigs { 59 | create("release") { 60 | keyAlias = keystoreProperties["releaseKeyAlias"] as String? 61 | keyPassword = keystoreProperties["releaseKeyPassword"] as String? 62 | storeFile = file(keystoreProperties["releaseStoreFile"] ?: "release/release-key.jks") 63 | storePassword = keystoreProperties["releaseStorePassword"] as String? 64 | } 65 | } 66 | 67 | buildTypes { 68 | release { 69 | if (keystorePropertiesFile.exists()) { 70 | signingConfig = signingConfigs["release"] 71 | } 72 | isShrinkResources = true 73 | isMinifyEnabled = true 74 | } 75 | 76 | create("benchmark") { 77 | initWith(buildTypes.getByName("release")) 78 | signingConfig = signingConfigs.getByName("debug") 79 | matchingFallbacks += listOf("release") 80 | isDebuggable = false 81 | proguardFiles("benchmark-rules.pro") 82 | } 83 | } 84 | } 85 | 86 | secrets { 87 | propertiesFileName = "secrets.properties" 88 | defaultPropertiesFileName = "secrets.defaults.properties" 89 | } 90 | 91 | dependencies { 92 | // core modules 93 | implementation(project(":core-designsystem")) 94 | implementation(project(":core-navigation")) 95 | implementation(project(":core-data")) 96 | 97 | // feature modules 98 | implementation(project(":feature-chat")) 99 | implementation(project(":feature-login")) 100 | 101 | // material 102 | implementation(libs.androidx.appcompat) 103 | 104 | // compose 105 | implementation(libs.androidx.activity.compose) 106 | implementation(libs.androidx.compose.runtime) 107 | implementation(libs.androidx.compose.ui.tooling) 108 | implementation(libs.androidx.compose.ui.tooling.preview) 109 | implementation(libs.androidx.compose.constraintlayout) 110 | 111 | // jetpack 112 | implementation(libs.androidx.startup) 113 | implementation(libs.hilt.android) 114 | implementation(libs.androidx.hilt.navigation.compose) 115 | ksp(libs.hilt.compiler) 116 | 117 | // image loading 118 | implementation(libs.landscapist.glide) 119 | 120 | // logger 121 | implementation(libs.stream.log) 122 | 123 | // crash tracer & restorer 124 | implementation(libs.snitcher) 125 | 126 | // firebase 127 | implementation(platform(libs.firebase.bom)) 128 | implementation(libs.firebase.analytics) 129 | implementation(libs.firebase.messaging) 130 | implementation(libs.firebase.crashlytics) 131 | 132 | // baseline profile 133 | baselineProfile(project(":benchmark")) 134 | } 135 | 136 | if (file("google-services.json").exists()) { 137 | apply(plugin = libs.plugins.gms.googleServices.get().pluginId) 138 | apply(plugin = libs.plugins.firebase.crashlytics.get().pluginId) 139 | } 140 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # This is a configuration file for R8 2 | 3 | -verbose 4 | -allowaccessmodification 5 | -repackageclasses 6 | 7 | # Note that you cannot just include these flags in your own 8 | # configuration file; if you are including this file, optimization 9 | # will be turned off. You'll need to either edit this file, or 10 | # duplicate the contents of this file and remove the include of this 11 | # file from your project's proguard.config path property. 12 | 13 | # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native 14 | -keepclasseswithmembernames class * { 15 | native ; 16 | } 17 | 18 | # We only need to keep ComposeView 19 | -keep public class androidx.compose.ui.platform.ComposeView { 20 | public (android.content.Context, android.util.AttributeSet); 21 | } 22 | 23 | # For enumeration classes 24 | -keepclassmembers enum * { 25 | public static **[] values(); 26 | public static ** valueOf(java.lang.String); 27 | } 28 | 29 | -keep class * implements android.os.Parcelable { 30 | public static final android.os.Parcelable$Creator *; 31 | } 32 | 33 | # AndroidX + support library contains references to newer platform versions. 34 | # Don't warn about those in case this app is linking against an older 35 | # platform version. We know about them, and they are safe. 36 | -dontwarn android.support.** 37 | -dontwarn androidx.** 38 | 39 | -keepattributes SourceFile, 40 | LineNumberTable, 41 | RuntimeVisibleAnnotations, 42 | RuntimeVisibleParameterAnnotations, 43 | RuntimeVisibleTypeAnnotations, 44 | AnnotationDefault 45 | 46 | -renamesourcefileattribute SourceFile 47 | 48 | -dontwarn org.conscrypt.** 49 | -dontwarn org.bouncycastle.** 50 | -dontwarn org.openjsse.** 51 | 52 | # Dagger 53 | -dontwarn com.google.errorprone.annotations.* 54 | 55 | # Retain the generic signature of retrofit2.Call until added to Retrofit. 56 | # Issue: https://github.com/square/retrofit/issues/3580. 57 | # Pull request: https://github.com/square/retrofit/pull/3579. 58 | -keep,allowobfuscation,allowshrinking class retrofit2.Call 59 | 60 | # See https://issuetracker.google.com/issues/265188224 61 | -keep,allowshrinking class * extends androidx.compose.ui.node.ModifierNodeElement {} 62 | -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.skydoves.chatgpt", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 6, 15 | "versionName": "1.0.5", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 34 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/ChatGPTApp.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.chatgpt 18 | 19 | import android.app.Application 20 | import dagger.hilt.android.HiltAndroidApp 21 | 22 | @HiltAndroidApp 23 | class ChatGPTApp : Application() { 24 | 25 | override fun onCreate() { 26 | super.onCreate() 27 | 28 | // install Snitcher to trace global exceptions and restore the app. 29 | // https://github.com/skydoves/snitcher 30 | // Snitcher.install(this) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt 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.chatgpt.core.designsystem.composition.LocalOnFinishDispatcher 24 | import com.skydoves.chatgpt.core.designsystem.theme.ChatGPTComposeTheme 25 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 26 | import com.skydoves.chatgpt.ui.ChatGPTMain 27 | import dagger.hilt.android.AndroidEntryPoint 28 | import javax.inject.Inject 29 | 30 | @AndroidEntryPoint 31 | class MainActivity : ComponentActivity() { 32 | 33 | @Inject 34 | internal lateinit var appComposeNavigator: AppComposeNavigator 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | 39 | setContent { 40 | CompositionLocalProvider( 41 | LocalOnFinishDispatcher provides { finish() } 42 | ) { 43 | ChatGPTComposeTheme { ChatGPTMain(composeNavigator = appComposeNavigator) } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/initializer/AppInitializer.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.chatgpt.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import com.skydoves.chatgpt.feature.chat.initializer.StreamChatInitializer 22 | 23 | class AppInitializer : Initializer { 24 | 25 | override fun create(context: Context) = Unit 26 | 27 | override fun dependencies(): List>> = listOf( 28 | StreamChatInitializer::class.java 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/navigation/ChatGPTNavHost.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.chatgpt.navigation 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.navigation.NavHostController 21 | import androidx.navigation.compose.NavHost 22 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 23 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens 24 | 25 | @Composable 26 | fun ChatGPTNavHost( 27 | navHostController: NavHostController, 28 | composeNavigator: AppComposeNavigator 29 | ) { 30 | NavHost( 31 | navController = navHostController, 32 | startDestination = ChatGPTScreens.Login.route 33 | ) { 34 | chatGPTHomeNavigation( 35 | composeNavigator = composeNavigator 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/navigation/ChatGPTNavigation.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.chatgpt.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.hilt.navigation.compose.hiltViewModel 24 | import androidx.navigation.NavGraphBuilder 25 | import androidx.navigation.compose.composable 26 | import com.skydoves.chatgpt.R 27 | import com.skydoves.chatgpt.core.designsystem.component.ChatGPTSmallTopBar 28 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 29 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens 30 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens.Companion.argument_channel_id 31 | import com.skydoves.chatgpt.feature.chat.channels.ChatGPTChannels 32 | import com.skydoves.chatgpt.feature.chat.messages.ChatGPTMessages 33 | import com.skydoves.chatgpt.feature.login.ChatGPTLogin 34 | 35 | fun NavGraphBuilder.chatGPTHomeNavigation( 36 | composeNavigator: AppComposeNavigator 37 | ) { 38 | composable(route = ChatGPTScreens.Login.name) { 39 | ChatGPTLogin(composeNavigator = composeNavigator) 40 | } 41 | 42 | composable(route = ChatGPTScreens.Channels.name) { 43 | Scaffold(topBar = { 44 | ChatGPTSmallTopBar( 45 | title = stringResource(id = R.string.app_name) 46 | ) 47 | }) { padding -> 48 | ChatGPTChannels( 49 | modifier = Modifier.padding(padding), 50 | composeNavigator = composeNavigator 51 | ) 52 | } 53 | } 54 | 55 | composable( 56 | route = ChatGPTScreens.Messages.name, 57 | arguments = ChatGPTScreens.Messages.navArguments 58 | ) { 59 | val channelId = it.arguments?.getString(argument_channel_id) ?: return@composable 60 | ChatGPTMessages( 61 | channelId = channelId, 62 | composeNavigator = composeNavigator, 63 | viewModel = hiltViewModel() 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/skydoves/chatgpt/ui/ChatGPTMain.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.chatgpt.ui 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.LaunchedEffect 21 | import androidx.navigation.compose.rememberNavController 22 | import com.skydoves.chatgpt.core.designsystem.component.ChatGPTBackground 23 | import com.skydoves.chatgpt.core.designsystem.theme.ChatGPTComposeTheme 24 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 25 | import com.skydoves.chatgpt.navigation.ChatGPTNavHost 26 | 27 | @Composable 28 | fun ChatGPTMain( 29 | composeNavigator: AppComposeNavigator 30 | ) { 31 | ChatGPTComposeTheme { 32 | val navHostController = rememberNavController() 33 | 34 | LaunchedEffect(Unit) { 35 | composeNavigator.handleNavigationCommands(navHostController) 36 | } 37 | 38 | ChatGPTBackground { 39 | ChatGPTNavHost(navHostController = navHostController, composeNavigator = composeNavigator) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/openai.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/drawable/openai.jpeg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | @color/stream_primary 20 | @color/stream_primary 21 | @color/white 22 | @color/stream_primary 23 | @color/stream_primary 24 | @color/off_white 25 | @color/stream_primary 26 | @color/stream_primary 27 | @color/white 28 | @color/gray 29 | @color/stream_primary 30 | 31 | #FF005FFF 32 | 33 | 34 | #FFF 35 | #e0e0e0 36 | #88898b 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | chatgpt-android 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /benchmark/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.chatgpt.benchmark" 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 | } -------------------------------------------------------------------------------- /benchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/chatgpt/benchmark/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.chatgpt.benchmark 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 = CHATGPT_ANDROID_PACKAGE_NAME, 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 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/chatgpt/benchmark/ChannelActions.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.chatgpt.benchmark 18 | 19 | import androidx.benchmark.macro.MacrobenchmarkScope 20 | import androidx.test.uiautomator.By 21 | import androidx.test.uiautomator.Until 22 | 23 | fun MacrobenchmarkScope.channelsExplore() = device.apply { 24 | channelsWaitForContent() 25 | channelsScrollDownUp() 26 | } 27 | 28 | fun MacrobenchmarkScope.channelsWaitForContent() = device.apply { 29 | wait(Until.hasObject(By.res("Stream_ChannelsScreen")), STANDARD_TIMEOUT) 30 | } 31 | 32 | fun MacrobenchmarkScope.channelsScrollDownUp() = device.apply { 33 | val channelList = waitAndFindObject(By.res("Stream_Channels"), STANDARD_TIMEOUT) 34 | flingElementDownUp(channelList) 35 | } 36 | 37 | fun MacrobenchmarkScope.navigateFromChannelsToMessages() = device.apply { 38 | waitAndFindObject(By.res("Stream_ChannelItem"), STANDARD_TIMEOUT).click() 39 | waitForIdle() 40 | } 41 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/chatgpt/benchmark/GPTScenarios.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.chatgpt.benchmark 18 | 19 | import androidx.benchmark.macro.MacrobenchmarkScope 20 | 21 | fun MacrobenchmarkScope.chatGPTScenarios() { 22 | pressHome() 23 | startActivityAndWait() 24 | device.waitForIdle() 25 | 26 | // ------------- 27 | // Channels 28 | // ------------- 29 | channelsExplore() 30 | navigateFromChannelsToMessages() 31 | 32 | // ------------- 33 | // Messages 34 | // ------------- 35 | messagesExplore() 36 | } 37 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/chatgpt/benchmark/MessageActions.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.chatgpt.benchmark 18 | 19 | import androidx.benchmark.macro.MacrobenchmarkScope 20 | import androidx.test.uiautomator.By 21 | import androidx.test.uiautomator.Until 22 | 23 | fun MacrobenchmarkScope.messagesExplore() = device.apply { 24 | messagesWaitForContent() 25 | messagesScrollDownUp() 26 | } 27 | 28 | fun MacrobenchmarkScope.messagesWaitForContent() = device.apply { 29 | wait(Until.hasObject(By.res("Stream_MessagesScreen")), STANDARD_TIMEOUT) 30 | } 31 | 32 | fun MacrobenchmarkScope.messagesScrollDownUp() = device.apply { 33 | val channelList = waitAndFindObject(By.res("Stream_Messages"), STANDARD_TIMEOUT) 34 | flingElementDownUp(channelList) 35 | } 36 | -------------------------------------------------------------------------------- /benchmark/src/main/kotlin/com/skydoves/chatgpt/benchmark/Utils.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.chatgpt.benchmark 18 | 19 | import androidx.test.uiautomator.BySelector 20 | import androidx.test.uiautomator.Direction 21 | import androidx.test.uiautomator.UiDevice 22 | import androidx.test.uiautomator.UiObject2 23 | import androidx.test.uiautomator.Until 24 | 25 | internal const val CHATGPT_ANDROID_PACKAGE_NAME = "com.skydoves.chatgpt" 26 | 27 | internal const val FLING_SCREEN_DIVIDER = 5 28 | 29 | internal const val STANDARD_TIMEOUT = 15_000L 30 | 31 | internal fun UiDevice.flingElementDownUp(element: UiObject2) { 32 | // Set some margin from the sides to prevent triggering system navigation 33 | element.setGestureMargin(displayWidth / FLING_SCREEN_DIVIDER) 34 | 35 | element.fling(Direction.DOWN) 36 | waitForIdle() 37 | element.fling(Direction.UP) 38 | } 39 | 40 | internal fun UiDevice.flingElementUpDown(element: UiObject2) { 41 | // Set some margin from the sides to prevent triggering system navigation 42 | element.setGestureMargin(displayWidth / FLING_SCREEN_DIVIDER) 43 | 44 | element.fling(Direction.UP) 45 | waitForIdle() 46 | element.fling(Direction.DOWN) 47 | } 48 | 49 | /** 50 | * Waits until an object with [selector] if visible on screen and returns the object. 51 | * If the element is not available in [timeout], throws [AssertionError] 52 | */ 53 | internal fun UiDevice.waitAndFindObject( 54 | selector: BySelector, 55 | timeout: Long = STANDARD_TIMEOUT 56 | ): UiObject2 { 57 | if (!wait(Until.hasObject(selector), timeout)) { 58 | throw AssertionError("Element not found on screen in ${timeout}ms (selector=$selector)") 59 | } 60 | 61 | return findObject(selector) 62 | } 63 | 64 | internal fun UiDevice.waitForObject(selector: BySelector, timeout: Long = 5_000): UiObject2? { 65 | if (wait(Until.hasObject(selector), timeout)) { 66 | return findObject(selector) 67 | } 68 | return null 69 | } 70 | -------------------------------------------------------------------------------- /build-logic/convention/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | group = "com.skydoves.chatgpt.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.chatgpt.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.chatgpt.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.chatgpt.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.chatgpt.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 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.core.data.chat 18 | 19 | import io.getstream.chat.android.models.User 20 | 21 | val chatGPTUser = User( 22 | id = "70ef052a-da88-4451-af92-99f7ed335a71", 23 | role = "user", 24 | name = "ChatGPT", 25 | image = "https://user-images.githubusercontent.com/24237865/" + 26 | "206655413-fb7c70f6-703e-476b-9ee9-861bfb8bf6f7.jpeg" 27 | ) 28 | 29 | const val commonChannelId: String = "messaging:4d7cd1e8-e6d6-4df3-bfad-babbb9411cce" 30 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.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 | 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/chatgpt/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.chatgpt.core.data.di 18 | 19 | import com.skydoves.chatgpt.core.data.repository.GPTChannelRepository 20 | import com.skydoves.chatgpt.core.data.repository.GPTChannelRepositoryImpl 21 | import com.skydoves.chatgpt.core.data.repository.GPTMessageRepository 22 | import com.skydoves.chatgpt.core.data.repository.GPTMessageRepositoryImpl 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 bindsChatGPTChannelsRepository( 34 | gptChannelRepository: GPTChannelRepositoryImpl 35 | ): GPTChannelRepository 36 | 37 | @Binds 38 | fun bindsChatGPTMessagesRepository( 39 | gptMessageRepository: GPTMessageRepositoryImpl 40 | ): GPTMessageRepository 41 | } 42 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/core/data/repository/GPTChannelRepository.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.chatgpt.core.data.repository 18 | 19 | import io.getstream.chat.android.models.Channel 20 | import io.getstream.chat.android.models.User 21 | import io.getstream.result.Result 22 | import kotlinx.coroutines.flow.Flow 23 | 24 | interface GPTChannelRepository { 25 | 26 | suspend fun createRandomChannel(): Result 27 | 28 | suspend fun joinTheCommonChannel(user: User) 29 | 30 | fun streamUserFlow(): Flow 31 | 32 | fun isBalloonChannelDisplayed(): Boolean 33 | 34 | fun balloonChannelDisplayed() 35 | } 36 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/core/data/repository/GPTChannelRepositoryImpl.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.chatgpt.core.data.repository 18 | 19 | import com.skydoves.chatgpt.core.data.chat.chatGPTUser 20 | import com.skydoves.chatgpt.core.data.chat.commonChannelId 21 | import com.skydoves.chatgpt.core.preferences.Preferences 22 | import io.getstream.chat.android.client.ChatClient 23 | import io.getstream.chat.android.models.Channel 24 | import io.getstream.chat.android.models.User 25 | import io.getstream.result.Result 26 | import io.getstream.result.onSuccessSuspend 27 | import java.util.Random 28 | import java.util.UUID 29 | import javax.inject.Inject 30 | import kotlinx.coroutines.flow.Flow 31 | 32 | internal class GPTChannelRepositoryImpl @Inject constructor( 33 | private val chatClient: ChatClient, 34 | private val preferences: Preferences 35 | ) : GPTChannelRepository { 36 | 37 | override suspend fun createRandomChannel(): Result { 38 | val userId = chatClient.getCurrentUser()?.id ?: "" 39 | return chatClient.createChannel( 40 | channelType = "messaging", 41 | channelId = UUID.randomUUID().toString(), 42 | memberIds = listOf(userId, chatGPTUser.id), 43 | extraData = mapOf("name" to "ChatGPT ${Random().nextInt(99999)}") 44 | ).await() 45 | } 46 | 47 | override suspend fun joinTheCommonChannel(user: User) { 48 | val channelClient = chatClient.channel(commonChannelId) 49 | val result = channelClient.watch().await() 50 | result.onSuccessSuspend { channel -> 51 | val members = channel.members 52 | val isExist = members.firstOrNull { it.user.id == user.id } 53 | if (isExist == null) { 54 | channelClient.addMembers(listOf(user.id)).await() 55 | } 56 | } 57 | } 58 | 59 | override fun streamUserFlow(): Flow = chatClient.clientState.user 60 | 61 | override fun isBalloonChannelDisplayed(): Boolean = preferences.balloonChannelDisplayed 62 | 63 | override fun balloonChannelDisplayed() { 64 | preferences.balloonChannelDisplayed = true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/core/data/repository/GPTMessageRepository.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.chatgpt.core.data.repository 18 | 19 | import com.skydoves.chatgpt.core.model.network.GPTChatRequest 20 | import com.skydoves.chatgpt.core.model.network.GPTChatResponse 21 | import com.skydoves.sandwich.ApiResponse 22 | import kotlinx.coroutines.flow.Flow 23 | 24 | interface GPTMessageRepository { 25 | 26 | suspend fun sendMessage(gptChatRequest: GPTChatRequest): ApiResponse 27 | 28 | fun watchIsChannelMessageEmpty(cid: String): Flow 29 | } 30 | -------------------------------------------------------------------------------- /core-data/src/main/kotlin/com/skydoves/chatgpt/core/data/repository/GPTMessageRepositoryImpl.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.chatgpt.core.data.repository 18 | 19 | import com.skydoves.chatgpt.core.model.network.GPTChatRequest 20 | import com.skydoves.chatgpt.core.model.network.GPTChatResponse 21 | import com.skydoves.chatgpt.core.network.ChatGPTDispatchers 22 | import com.skydoves.chatgpt.core.network.Dispatcher 23 | import com.skydoves.chatgpt.core.network.service.ChatGPTService 24 | import com.skydoves.sandwich.ApiResponse 25 | import io.getstream.chat.android.client.ChatClient 26 | import io.getstream.result.onSuccessSuspend 27 | import javax.inject.Inject 28 | import kotlinx.coroutines.CoroutineDispatcher 29 | import kotlinx.coroutines.flow.Flow 30 | import kotlinx.coroutines.flow.flow 31 | import kotlinx.coroutines.flow.flowOn 32 | 33 | internal class GPTMessageRepositoryImpl @Inject constructor( 34 | @Dispatcher(ChatGPTDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, 35 | private val chatClient: ChatClient, 36 | private val chatGptService: ChatGPTService 37 | ) : GPTMessageRepository { 38 | 39 | override suspend fun sendMessage(gptChatRequest: GPTChatRequest): ApiResponse { 40 | return chatGptService.sendMessage(gptChatRequest) 41 | } 42 | 43 | override fun watchIsChannelMessageEmpty(cid: String): Flow = flow { 44 | val result = chatClient.channel(cid).watch().await() 45 | result.onSuccessSuspend { channel -> 46 | val messages = channel.messages 47 | emit(messages.isEmpty()) 48 | } 49 | }.flowOn(ioDispatcher) 50 | } 51 | -------------------------------------------------------------------------------- /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.chatgpt.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 | -------------------------------------------------------------------------------- /core-designsystem/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /core-designsystem/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.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.chatgpt.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 ChatGPTBackground( 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/chatgpt/core/designsystem/component/ChatGPTLoadingIndicator.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.chatgpt.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.chatgpt.core.designsystem.theme.STREAM_PRIMARY 25 | 26 | @Composable 27 | fun BoxScope.ChatGPTLoadingIndicator() { 28 | CircularProgressIndicator( 29 | modifier = Modifier.align(Alignment.Center), 30 | color = STREAM_PRIMARY 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /core-designsystem/src/main/kotlin/com/skydoves/chatgpt/core/designsystem/component/ChatGPTSmallTopBar.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.chatgpt.core.designsystem.component 18 | 19 | import androidx.compose.foundation.layout.fillMaxWidth 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.Text 22 | import androidx.compose.material3.TopAppBar 23 | import androidx.compose.material3.TopAppBarDefaults 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.tooling.preview.Preview 27 | import com.skydoves.chatgpt.core.designsystem.theme.ChatGPTComposeTheme 28 | 29 | @Composable 30 | fun ChatGPTSmallTopBar(title: String) { 31 | TopAppBar( 32 | modifier = Modifier.fillMaxWidth(), 33 | title = { 34 | Text( 35 | text = title, 36 | color = MaterialTheme.colorScheme.tertiary, 37 | style = MaterialTheme.typography.titleLarge 38 | ) 39 | }, 40 | colors = TopAppBarDefaults.mediumTopAppBarColors( 41 | containerColor = MaterialTheme.colorScheme.primary 42 | ) 43 | ) 44 | } 45 | 46 | @Preview 47 | @Composable 48 | private fun ChatGPTSmallTopBarPreview() { 49 | ChatGPTComposeTheme { 50 | ChatGPTSmallTopBar("ChatGPT Android") 51 | } 52 | } 53 | 54 | @Preview 55 | @Composable 56 | private fun ChatGPTSmallTopBarDarkPreview() { 57 | ChatGPTComposeTheme(darkTheme = true) { 58 | ChatGPTSmallTopBar("ChatGPT Android") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core-designsystem/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.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/chatgpt/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.chatgpt.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/chatgpt/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.chatgpt.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/chatgpt/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.chatgpt.core.designsystem.theme 18 | 19 | import androidx.compose.foundation.isSystemInDarkTheme 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.darkColorScheme 22 | import androidx.compose.material3.lightColorScheme 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.CompositionLocalProvider 25 | import androidx.compose.ui.graphics.Color 26 | 27 | private val DarkChatGPTColorScheme = darkColorScheme( 28 | primary = STREAM_PRIMARY, 29 | primaryContainer = STREAM_PRIMARY, 30 | secondary = STREAM_PRIMARY, 31 | background = STREAM_PRIMARY, 32 | tertiary = WHITE200, 33 | onTertiary = GRAY200 34 | ) 35 | 36 | private val LightChatGPTColorScheme = lightColorScheme( 37 | primary = STREAM_PRIMARY, 38 | primaryContainer = STREAM_PRIMARY, 39 | secondary = STREAM_PRIMARY, 40 | background = WHITE200, 41 | tertiary = WHITE200, 42 | onTertiary = GRAY200 43 | ) 44 | 45 | /** Light Android background theme */ 46 | private val LightAndroidBackgroundTheme = BackgroundTheme(color = Color.White) 47 | 48 | /** Dark Android background theme */ 49 | private val DarkAndroidBackgroundTheme = BackgroundTheme(color = BACKGROUND900) 50 | 51 | @Composable 52 | fun ChatGPTComposeTheme( 53 | darkTheme: Boolean = isSystemInDarkTheme(), 54 | content: @Composable () -> Unit 55 | ) { 56 | val colorScheme = if (darkTheme) DarkChatGPTColorScheme else LightChatGPTColorScheme 57 | val backgroundTheme = if (darkTheme) DarkAndroidBackgroundTheme else LightAndroidBackgroundTheme 58 | 59 | CompositionLocalProvider( 60 | LocalBackgroundTheme provides backgroundTheme 61 | ) { 62 | MaterialTheme( 63 | colorScheme = colorScheme, 64 | typography = Typography, 65 | content = content 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core-designsystem/src/main/kotlin/com/skydoves/chatgpt/core/designsystem/theme/Typography.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.chatgpt.core.designsystem.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | val Typography = Typography( 26 | displayLarge = TextStyle( 27 | fontWeight = FontWeight.W400, 28 | fontSize = 57.sp, 29 | lineHeight = 64.sp, 30 | letterSpacing = (-0.25).sp 31 | ), 32 | displayMedium = TextStyle( 33 | fontWeight = FontWeight.W400, 34 | fontSize = 45.sp, 35 | lineHeight = 52.sp 36 | ), 37 | displaySmall = TextStyle( 38 | fontWeight = FontWeight.W400, 39 | fontSize = 36.sp, 40 | lineHeight = 44.sp 41 | ), 42 | headlineLarge = TextStyle( 43 | fontWeight = FontWeight.W400, 44 | fontSize = 32.sp, 45 | lineHeight = 40.sp 46 | ), 47 | headlineMedium = TextStyle( 48 | fontWeight = FontWeight.W400, 49 | fontSize = 28.sp, 50 | lineHeight = 36.sp 51 | ), 52 | headlineSmall = TextStyle( 53 | fontWeight = FontWeight.W400, 54 | fontSize = 17.sp, 55 | lineHeight = 32.sp 56 | ), 57 | titleLarge = TextStyle( 58 | fontWeight = FontWeight.W700, 59 | fontSize = 20.sp, 60 | lineHeight = 28.sp 61 | ), 62 | titleMedium = TextStyle( 63 | fontWeight = FontWeight.W700, 64 | fontSize = 16.sp, 65 | lineHeight = 24.sp, 66 | letterSpacing = 0.1.sp 67 | ), 68 | titleSmall = TextStyle( 69 | fontWeight = FontWeight.W500, 70 | fontSize = 14.sp, 71 | lineHeight = 20.sp, 72 | letterSpacing = 0.1.sp 73 | ), 74 | bodyLarge = TextStyle( 75 | fontWeight = FontWeight.W400, 76 | fontSize = 16.sp, 77 | lineHeight = 24.sp, 78 | letterSpacing = 0.5.sp 79 | ), 80 | bodyMedium = TextStyle( 81 | fontWeight = FontWeight.W400, 82 | fontSize = 14.sp, 83 | lineHeight = 20.sp, 84 | letterSpacing = 0.25.sp 85 | ), 86 | bodySmall = TextStyle( 87 | fontWeight = FontWeight.W400, 88 | fontSize = 12.sp, 89 | lineHeight = 16.sp, 90 | letterSpacing = 0.4.sp 91 | ), 92 | labelLarge = TextStyle( 93 | fontWeight = FontWeight.W400, 94 | fontSize = 14.sp, 95 | lineHeight = 20.sp, 96 | letterSpacing = 0.1.sp 97 | ), 98 | labelMedium = TextStyle( 99 | fontWeight = FontWeight.W400, 100 | fontSize = 12.sp, 101 | lineHeight = 16.sp, 102 | letterSpacing = 0.5.sp 103 | ), 104 | labelSmall = TextStyle( 105 | fontFamily = FontFamily.Monospace, 106 | fontWeight = FontWeight.W500, 107 | fontSize = 10.sp, 108 | lineHeight = 16.sp 109 | ) 110 | ) 111 | -------------------------------------------------------------------------------- /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.chatgpt.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 | -------------------------------------------------------------------------------- /core-model/src/main/kotlin/com/skydoves/chatgpt/core/model/GPTChoice.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.chatgpt.core.model 18 | 19 | import com.squareup.moshi.Json 20 | import com.squareup.moshi.JsonClass 21 | 22 | @JsonClass(generateAdapter = true) 23 | data class GPTChoice( 24 | @field:Json(name = "index") val index: Int, 25 | @field:Json(name = "message") val message: GPTMessage, 26 | @field:Json(name = "logprobs") val logProbs: String?, 27 | @field:Json(name = "finish_reason") val finishReason: String? 28 | ) 29 | -------------------------------------------------------------------------------- /core-model/src/main/kotlin/com/skydoves/chatgpt/core/model/GPTMessage.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.chatgpt.core.model 18 | 19 | import com.squareup.moshi.Json 20 | import com.squareup.moshi.JsonClass 21 | 22 | @JsonClass(generateAdapter = true) 23 | data class GPTMessage( 24 | @field:Json(name = "role") val role: String, 25 | @field:Json(name = "content") val content: String 26 | ) 27 | -------------------------------------------------------------------------------- /core-model/src/main/kotlin/com/skydoves/chatgpt/core/model/GPTUsage.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.chatgpt.core.model 18 | 19 | import com.squareup.moshi.Json 20 | import com.squareup.moshi.JsonClass 21 | 22 | @JsonClass(generateAdapter = true) 23 | data class GPTUsage( 24 | @field:Json(name = "prompt_tokens") val promptTokens: Int, 25 | @field:Json(name = "completion_tokens") val completionTokens: Int, 26 | @field:Json(name = "total_tokens") val totalTokens: Int 27 | ) 28 | -------------------------------------------------------------------------------- /core-model/src/main/kotlin/com/skydoves/chatgpt/core/model/network/GPTChatRequest.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.chatgpt.core.model.network 18 | 19 | import com.skydoves.chatgpt.core.model.GPTMessage 20 | import com.squareup.moshi.Json 21 | import com.squareup.moshi.JsonClass 22 | 23 | @JsonClass(generateAdapter = true) 24 | data class GPTChatRequest( 25 | @field:Json(name = "model") val model: String, 26 | @field:Json(name = "messages") val messages: List 27 | ) 28 | -------------------------------------------------------------------------------- /core-model/src/main/kotlin/com/skydoves/chatgpt/core/model/network/GPTChatResponse.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.chatgpt.core.model.network 18 | 19 | import com.skydoves.chatgpt.core.model.GPTChoice 20 | import com.skydoves.chatgpt.core.model.GPTUsage 21 | import com.squareup.moshi.Json 22 | import com.squareup.moshi.JsonClass 23 | 24 | @JsonClass(generateAdapter = true) 25 | data class GPTChatResponse( 26 | @field:Json(name = "id") val id: String, 27 | @field:Json(name = "object") val `object`: String, 28 | @field:Json(name = "created") val created: Long, 29 | @field:Json(name = "model") val model: String, 30 | @field:Json(name = "system_fingerprint") val systemFingerprint: String?, 31 | @field:Json(name = "choices") val choices: List, 32 | @field:Json(name = "usage") val usage: GPTUsage 33 | ) 34 | -------------------------------------------------------------------------------- /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.chatgpt.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 | -------------------------------------------------------------------------------- /core-navigation/src/main/kotlin/com/skydoves/chatgpt/core/navigation/ChatGPTComposeNavigator.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.chatgpt.core.navigation 18 | 19 | import androidx.navigation.NavOptionsBuilder 20 | import androidx.navigation.navOptions 21 | import javax.inject.Inject 22 | 23 | class ChatGPTComposeNavigator @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/chatgpt/core/navigation/ChatGPTScreens.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.chatgpt.core.navigation 18 | 19 | import androidx.navigation.NamedNavArgument 20 | import androidx.navigation.NavType 21 | import androidx.navigation.navArgument 22 | 23 | sealed class ChatGPTScreens( 24 | val route: String, 25 | val navArguments: List = emptyList() 26 | ) { 27 | val name: String = route.appendArguments(navArguments) 28 | 29 | // login screen 30 | data object Login : ChatGPTScreens("login") 31 | 32 | // channel screen 33 | data object Channels : ChatGPTScreens("channels") 34 | 35 | // message screen 36 | data object Messages : ChatGPTScreens( 37 | route = "messages", 38 | navArguments = listOf(navArgument(argument_channel_id) { type = NavType.StringType }) 39 | ) { 40 | fun createRoute(channelId: String) = 41 | name.replace("{${navArguments.first().name}}", channelId) 42 | } 43 | 44 | companion object { 45 | const val argument_channel_id = "channelId" 46 | } 47 | } 48 | 49 | private fun String.appendArguments(navArguments: List): String { 50 | val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null } 51 | .takeIf { it.isNotEmpty() } 52 | ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" } 53 | .orEmpty() 54 | val optionalArguments = navArguments.filter { it.argument.defaultValue != null } 55 | .takeIf { it.isNotEmpty() } 56 | ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" } 57 | .orEmpty() 58 | return "$this$mandatoryArguments$optionalArguments" 59 | } 60 | -------------------------------------------------------------------------------- /core-navigation/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.core.navigation 18 | 19 | import androidx.navigation.NavOptions 20 | 21 | sealed class NavigationCommand { 22 | 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/chatgpt/core/navigation/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.chatgpt.core.navigation 18 | 19 | import dagger.Binds 20 | import dagger.Module 21 | import dagger.hilt.InstallIn 22 | import dagger.hilt.components.SingletonComponent 23 | import javax.inject.Singleton 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | internal abstract class NavigationModule { 28 | 29 | @Binds 30 | @Singleton 31 | abstract fun provideComposeNavigator( 32 | chatGPTComposeNavigator: ChatGPTComposeNavigator 33 | ): AppComposeNavigator 34 | } 35 | -------------------------------------------------------------------------------- /core-navigation/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.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-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 | id(libs.plugins.google.secrets.get().pluginId) 22 | } 23 | 24 | android { 25 | namespace = "com.skydoves.chatgpt.core.network" 26 | 27 | buildFeatures { 28 | buildConfig = true 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation(project(":core-model")) 34 | implementation(project(":core-preferences")) 35 | 36 | implementation(libs.androidx.startup) 37 | implementation(libs.stream.log) 38 | 39 | api(libs.okhttp.logging) 40 | api(libs.retrofit.core) 41 | api(libs.retrofit.moshi.converter) 42 | api(libs.sandwich) 43 | } 44 | 45 | secrets { 46 | propertiesFileName = "secrets.properties" 47 | defaultPropertiesFileName = "secrets.defaults.properties" 48 | } -------------------------------------------------------------------------------- /core-network/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /core-network/src/main/kotlin/com/skydoves/chatgpt/core/network/ChatGPTDispatchers.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.chatgpt.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 chatGPTDispatchers: ChatGPTDispatchers) 25 | 26 | enum class ChatGPTDispatchers { 27 | IO 28 | } 29 | -------------------------------------------------------------------------------- /core-network/src/main/kotlin/com/skydoves/chatgpt/core/network/GPTInterceptor.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.chatgpt.core.network 18 | 19 | import javax.inject.Inject 20 | import okhttp3.Interceptor 21 | import okhttp3.Response 22 | 23 | class GPTInterceptor @Inject constructor() : Interceptor { 24 | 25 | override fun intercept(chain: Interceptor.Chain): Response { 26 | val originalRequest = chain.request() 27 | val originalUrl = originalRequest.url 28 | val url = originalUrl.newBuilder().build() 29 | val requestBuilder = originalRequest.newBuilder().url(url).apply { 30 | addHeader("Authorization", "Bearer ${BuildConfig.GPT_API_KEY}") 31 | } 32 | val request = requestBuilder.build() 33 | return chain.proceed(request) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core-network/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.core.network.di 18 | 19 | import com.skydoves.chatgpt.core.network.ChatGPTDispatchers 20 | import com.skydoves.chatgpt.core.network.Dispatcher 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(ChatGPTDispatchers.IO) 34 | fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO 35 | } 36 | -------------------------------------------------------------------------------- /core-network/src/main/kotlin/com/skydoves/chatgpt/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.chatgpt.core.network.di 18 | 19 | import com.skydoves.chatgpt.core.network.BuildConfig 20 | import com.skydoves.chatgpt.core.network.GPTInterceptor 21 | import com.skydoves.chatgpt.core.network.service.ChatGPTService 22 | import com.skydoves.sandwich.retrofit.adapters.ApiResponseCallAdapterFactory 23 | import dagger.Module 24 | import dagger.Provides 25 | import dagger.hilt.InstallIn 26 | import dagger.hilt.components.SingletonComponent 27 | import java.util.concurrent.TimeUnit 28 | import javax.inject.Singleton 29 | import okhttp3.OkHttpClient 30 | import okhttp3.logging.HttpLoggingInterceptor 31 | import retrofit2.Retrofit 32 | import retrofit2.converter.moshi.MoshiConverterFactory 33 | import retrofit2.create 34 | 35 | @Module 36 | @InstallIn(SingletonComponent::class) 37 | internal object NetworkModule { 38 | 39 | @Provides 40 | @Singleton 41 | fun provideOkHttpClient(): OkHttpClient { 42 | return OkHttpClient.Builder() 43 | .addInterceptor(GPTInterceptor()) 44 | .connectTimeout(60, TimeUnit.SECONDS) 45 | .readTimeout(60, TimeUnit.SECONDS) 46 | .writeTimeout(15, TimeUnit.SECONDS) 47 | .apply { 48 | if (BuildConfig.DEBUG) { 49 | this.addNetworkInterceptor( 50 | HttpLoggingInterceptor().apply { 51 | level = HttpLoggingInterceptor.Level.BODY 52 | } 53 | ) 54 | } 55 | } 56 | .build() 57 | } 58 | 59 | @Provides 60 | @Singleton 61 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 62 | return Retrofit.Builder() 63 | .client(okHttpClient) 64 | .baseUrl("https://api.openai.com/") 65 | .addConverterFactory(MoshiConverterFactory.create()) 66 | .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) 67 | .build() 68 | } 69 | 70 | @Provides 71 | @Singleton 72 | fun provideChatGPTService(retrofit: Retrofit): ChatGPTService = retrofit.create() 73 | } 74 | -------------------------------------------------------------------------------- /core-network/src/main/kotlin/com/skydoves/chatgpt/core/network/service/ChatGPTService.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.chatgpt.core.network.service 18 | 19 | import com.skydoves.chatgpt.core.model.network.GPTChatRequest 20 | import com.skydoves.chatgpt.core.model.network.GPTChatResponse 21 | import com.skydoves.sandwich.ApiResponse 22 | import retrofit2.http.Body 23 | import retrofit2.http.POST 24 | 25 | interface ChatGPTService { 26 | 27 | @POST("v1/chat/completions") 28 | suspend fun sendMessage(@Body request: GPTChatRequest): ApiResponse 29 | } 30 | -------------------------------------------------------------------------------- /core-network/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | [Unauthorized] Your session was expired. Please re-launch or re-install this app. 19 | 20 | -------------------------------------------------------------------------------- /core-preferences/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core-preferences/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.chatgpt.core.preferences" 24 | } 25 | 26 | dependencies { 27 | } -------------------------------------------------------------------------------- /core-preferences/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /core-preferences/src/main/kotlin/com/skydoves/chatgpt/core/preferences/Preferences.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.chatgpt.core.preferences 18 | 19 | import android.content.SharedPreferences 20 | import com.skydoves.chatgpt.core.preferences.delegate.booleanPreferences 21 | import com.skydoves.chatgpt.core.preferences.delegate.stringPreferences 22 | import java.util.UUID 23 | import javax.inject.Inject 24 | 25 | class Preferences @Inject constructor( 26 | val sharedPreferences: SharedPreferences 27 | ) { 28 | 29 | val userUUID: String by stringPreferences( 30 | key = KEY_UUID, 31 | defaultValue = UUID.randomUUID().toString() 32 | ) 33 | 34 | var balloonChannelDisplayed: Boolean by booleanPreferences( 35 | key = KEY_BALLOON_CHANNEL_DISPLAYED, 36 | defaultValue = false 37 | ) 38 | 39 | companion object { 40 | private const val KEY_UUID: String = "key_uuid" 41 | private const val KEY_BALLOON_CHANNEL_DISPLAYED = "key_balloon_channel_displayed" 42 | } 43 | } 44 | 45 | val String.Companion.Empty 46 | inline get() = "" 47 | -------------------------------------------------------------------------------- /core-preferences/src/main/kotlin/com/skydoves/chatgpt/core/preferences/delegate/BooleanPreferenceDelegate.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.chatgpt.core.preferences.delegate 18 | 19 | import androidx.core.content.edit 20 | import com.skydoves.chatgpt.core.preferences.Preferences 21 | import kotlin.reflect.KProperty 22 | 23 | fun booleanPreferences( 24 | key: String, 25 | defaultValue: Boolean 26 | ) = BooleanPreferenceDelegate(key, defaultValue) 27 | 28 | class BooleanPreferenceDelegate( 29 | private val key: String, 30 | private val defaultValue: Boolean 31 | ) { 32 | operator fun getValue(preferences: Preferences, property: KProperty<*>): Boolean { 33 | return preferences.sharedPreferences.getBoolean(key, defaultValue) 34 | } 35 | 36 | operator fun setValue(preferences: Preferences, property: KProperty<*>, value: Boolean?) { 37 | if (value != null) { 38 | preferences.sharedPreferences.edit { 39 | putBoolean(key, value) 40 | } 41 | } else { 42 | preferences.sharedPreferences.edit { 43 | remove(key) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core-preferences/src/main/kotlin/com/skydoves/chatgpt/core/preferences/delegate/StringPreferenceDelegate.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.chatgpt.core.preferences.delegate 18 | 19 | import androidx.core.content.edit 20 | import com.skydoves.chatgpt.core.preferences.Preferences 21 | import kotlin.reflect.KProperty 22 | 23 | fun stringPreferences(key: String, defaultValue: String) = 24 | StringPreferenceDelegate(key, defaultValue) 25 | 26 | class StringPreferenceDelegate( 27 | private val key: String, 28 | private val defaultValue: String 29 | ) { 30 | operator fun getValue(preferences: Preferences, property: KProperty<*>): String { 31 | return preferences.sharedPreferences.getString(key, null) ?: let { 32 | setValue(preferences, property, defaultValue) 33 | defaultValue 34 | } 35 | } 36 | 37 | operator fun setValue(preferences: Preferences, property: KProperty<*>, value: String) { 38 | preferences.sharedPreferences.edit { putString(key, value) } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core-preferences/src/main/kotlin/com/skydoves/chatgpt/core/preferences/di/PreferencesModule.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.chatgpt.core.preferences.di 18 | 19 | import android.content.Context 20 | import android.content.Context.MODE_PRIVATE 21 | import com.skydoves.chatgpt.core.preferences.Preferences 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 PreferencesModule { 32 | 33 | @Provides 34 | @Singleton 35 | fun providePreferences(@ApplicationContext context: Context): Preferences { 36 | val sharedPreferences = context.getSharedPreferences("ChatGPT_Stream", MODE_PRIVATE) 37 | return Preferences(sharedPreferences) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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 | import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties 17 | 18 | plugins { 19 | id("skydoves.android.library") 20 | id("skydoves.android.library.compose") 21 | id("skydoves.android.feature") 22 | id("skydoves.android.hilt") 23 | id("skydoves.spotless") 24 | id(libs.plugins.google.secrets.get().pluginId) 25 | } 26 | 27 | android { 28 | namespace = "com.skydoves.chatgpt.feature.chat" 29 | 30 | buildFeatures { 31 | buildConfig = true 32 | } 33 | } 34 | 35 | dependencies { 36 | // Stream chat Compose 37 | api(libs.stream.compose) 38 | api(libs.stream.offline) 39 | 40 | implementation(libs.androidx.lifecycle.runtimeCompose) 41 | implementation(libs.androidx.lifecycle.viewModelCompose) 42 | implementation(libs.androidx.startup) 43 | 44 | implementation(libs.androidx.worker) 45 | implementation(libs.viewmodel.lifecycle) 46 | implementation(libs.hilt.worker) 47 | 48 | implementation(libs.balloon.compose) 49 | } 50 | 51 | secrets { 52 | propertiesFileName = "secrets.properties" 53 | defaultPropertiesFileName = "secrets.defaults.properties" 54 | } -------------------------------------------------------------------------------- /feature-chat/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/channels/ChatGPTChannels.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 | @file:OptIn(ExperimentalComposeUiApi::class) 18 | 19 | package com.skydoves.chatgpt.feature.chat.channels 20 | 21 | import android.widget.Toast 22 | import androidx.compose.foundation.layout.Box 23 | import androidx.compose.foundation.layout.fillMaxSize 24 | import androidx.compose.foundation.layout.fillMaxWidth 25 | import androidx.compose.foundation.layout.padding 26 | import androidx.compose.foundation.layout.size 27 | import androidx.compose.foundation.shape.CircleShape 28 | import androidx.compose.material.icons.Icons 29 | import androidx.compose.material.icons.filled.AddComment 30 | import androidx.compose.material3.FloatingActionButton 31 | import androidx.compose.material3.Icon 32 | import androidx.compose.material3.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.runtime.LaunchedEffect 35 | import androidx.compose.runtime.getValue 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.ExperimentalComposeUiApi 38 | import androidx.compose.ui.Modifier 39 | import androidx.compose.ui.graphics.Color 40 | import androidx.compose.ui.platform.LocalContext 41 | import androidx.compose.ui.semantics.semantics 42 | import androidx.compose.ui.semantics.testTagsAsResourceId 43 | import androidx.compose.ui.text.style.TextAlign 44 | import androidx.compose.ui.unit.dp 45 | import androidx.hilt.navigation.compose.hiltViewModel 46 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 47 | import com.skydoves.balloon.compose.Balloon 48 | import com.skydoves.chatgpt.core.designsystem.component.ChatGPTLoadingIndicator 49 | import com.skydoves.chatgpt.core.designsystem.composition.LocalOnFinishDispatcher 50 | import com.skydoves.chatgpt.core.designsystem.theme.STREAM_PRIMARY 51 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 52 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens 53 | import com.skydoves.chatgpt.feature.chat.R 54 | import com.skydoves.chatgpt.feature.chat.theme.ChatGPTStreamTheme 55 | import io.getstream.chat.android.compose.ui.channels.ChannelsScreen 56 | 57 | @Composable 58 | fun ChatGPTChannels( 59 | modifier: Modifier, 60 | composeNavigator: AppComposeNavigator, 61 | viewModel: ChatGPTChannelsViewModel = hiltViewModel(), 62 | onFinishDispatcher: (() -> Unit)? = LocalOnFinishDispatcher.current 63 | ) { 64 | val uiState by viewModel.channelUiState.collectAsStateWithLifecycle() 65 | 66 | HandleGPTChannelsUiState(uiState = uiState) 67 | 68 | ChatGPTStreamTheme { 69 | Box( 70 | modifier = modifier 71 | .fillMaxSize() 72 | .semantics { testTagsAsResourceId = true } 73 | ) { 74 | ChannelsScreen( 75 | isShowingHeader = false, 76 | onChannelClick = { channel -> 77 | composeNavigator.navigate(ChatGPTScreens.Messages.createRoute(channel.cid)) 78 | }, 79 | onBackPressed = { onFinishDispatcher?.invoke() } 80 | ) 81 | 82 | val isBalloonDisplayed by viewModel.isBalloonDisplayedState.collectAsStateWithLifecycle() 83 | 84 | Balloon( 85 | modifier = Modifier 86 | .align(Alignment.BottomEnd) 87 | .padding(16.dp) 88 | .size(58.dp), 89 | builder = rememberFloatingBalloon(), 90 | balloonContent = { 91 | Text( 92 | modifier = Modifier 93 | .padding(12.dp) 94 | .fillMaxWidth(), 95 | text = "You can add your ChatGPT channel!", 96 | textAlign = TextAlign.Center, 97 | color = Color.White 98 | ) 99 | } 100 | ) { balloonWindow -> 101 | 102 | LaunchedEffect(key1 = Unit) { 103 | if (!isBalloonDisplayed) { 104 | balloonWindow.showAlignTop() 105 | } 106 | 107 | balloonWindow.setOnBalloonDismissListener { 108 | viewModel.balloonChannelDisplayed() 109 | balloonWindow.dismiss() 110 | } 111 | } 112 | 113 | FloatingActionButton( 114 | modifier = Modifier.matchParentSize(), 115 | containerColor = STREAM_PRIMARY, 116 | shape = CircleShape, 117 | onClick = { viewModel.handleEvents(GPTChannelEvent.CreateChannel) } 118 | ) { 119 | Icon( 120 | imageVector = Icons.Filled.AddComment, 121 | contentDescription = null, 122 | tint = Color.White 123 | ) 124 | } 125 | } 126 | 127 | if (uiState == GPTChannelUiState.Loading) { 128 | ChatGPTLoadingIndicator() 129 | } 130 | } 131 | } 132 | } 133 | 134 | @Composable 135 | private fun HandleGPTChannelsUiState( 136 | uiState: GPTChannelUiState 137 | ) { 138 | val context = LocalContext.current 139 | LaunchedEffect(key1 = uiState) { 140 | when (uiState) { 141 | is GPTChannelUiState.Success -> Toast.makeText( 142 | context, 143 | R.string.toast_success_create_channel, 144 | Toast.LENGTH_SHORT 145 | ).show() 146 | 147 | is GPTChannelUiState.Error -> Toast.makeText( 148 | context, 149 | R.string.toast_error, 150 | Toast.LENGTH_SHORT 151 | ).show() 152 | 153 | else -> Unit 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/channels/ChatGPTChannelsViewModel.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.chatgpt.feature.chat.channels 18 | 19 | import androidx.lifecycle.ViewModel 20 | import androidx.lifecycle.viewModelScope 21 | import com.skydoves.chatgpt.core.data.repository.GPTChannelRepository 22 | import dagger.hilt.android.lifecycle.HiltViewModel 23 | import io.getstream.log.streamLog 24 | import io.getstream.result.onSuccessSuspend 25 | import javax.inject.Inject 26 | import kotlinx.coroutines.delay 27 | import kotlinx.coroutines.flow.MutableStateFlow 28 | import kotlinx.coroutines.flow.StateFlow 29 | import kotlinx.coroutines.launch 30 | 31 | @HiltViewModel 32 | class ChatGPTChannelsViewModel @Inject constructor( 33 | private val gptChannelRepository: GPTChannelRepository 34 | ) : ViewModel() { 35 | 36 | private val channelsMutableUiState = 37 | MutableStateFlow(GPTChannelUiState.Nothing) 38 | val channelUiState: StateFlow = channelsMutableUiState 39 | 40 | private val isBalloonDisplayedMutableState = 41 | MutableStateFlow(gptChannelRepository.isBalloonChannelDisplayed()) 42 | val isBalloonDisplayedState: StateFlow = isBalloonDisplayedMutableState 43 | 44 | init { 45 | viewModelScope.launch { 46 | gptChannelRepository.streamUserFlow().collect { user -> 47 | user?.let { 48 | gptChannelRepository.joinTheCommonChannel(it) 49 | } ?: run { 50 | streamLog { 51 | "User is null. Please check the app README.md and ensure " + 52 | "**Disable Auth Checks** is ON in the Dashboard" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | fun handleEvents(gptChannelEvent: GPTChannelEvent) { 60 | when (gptChannelEvent) { 61 | GPTChannelEvent.CreateChannel -> createRandomChannel() 62 | } 63 | } 64 | 65 | private fun createRandomChannel() { 66 | viewModelScope.launch { 67 | channelsMutableUiState.value = GPTChannelUiState.Loading 68 | val result = gptChannelRepository.createRandomChannel() 69 | result.onSuccessSuspend { 70 | channelsMutableUiState.value = GPTChannelUiState.Success(it.id) 71 | delay(100L) 72 | channelsMutableUiState.value = GPTChannelUiState.Nothing 73 | }.onError { 74 | channelsMutableUiState.value = GPTChannelUiState.Error 75 | } 76 | } 77 | } 78 | 79 | fun balloonChannelDisplayed() { 80 | isBalloonDisplayedMutableState.value = true 81 | gptChannelRepository.balloonChannelDisplayed() 82 | } 83 | } 84 | 85 | sealed interface GPTChannelEvent { 86 | object CreateChannel : GPTChannelEvent 87 | } 88 | 89 | sealed interface GPTChannelUiState { 90 | data object Nothing : GPTChannelUiState 91 | 92 | data object Loading : GPTChannelUiState 93 | 94 | data class Success(val channelId: String) : GPTChannelUiState 95 | 96 | data object Error : GPTChannelUiState 97 | } 98 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/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.chatgpt.feature.chat.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.chatgpt.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/src/main/kotlin/com/skydoves/chatgpt/feature/chat/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.chatgpt.feature.chat.di 18 | 19 | import android.content.Context 20 | import com.skydoves.chatgpt.feature.chat.initializer.StreamChatInitializer 21 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker 22 | import dagger.hilt.EntryPoint 23 | import dagger.hilt.InstallIn 24 | import dagger.hilt.android.EntryPointAccessors 25 | import dagger.hilt.components.SingletonComponent 26 | 27 | @EntryPoint 28 | @InstallIn(SingletonComponent::class) 29 | internal interface ChatEntryPoint { 30 | 31 | fun inject(streamChatInitializer: StreamChatInitializer) 32 | 33 | fun inject(chatGPTMessageWorker: ChatGPTMessageWorker) 34 | 35 | companion object { 36 | 37 | fun resolve(context: Context): ChatEntryPoint { 38 | val appContext = context.applicationContext ?: throw IllegalStateException( 39 | "applicationContext was not found in ChatEntryPoint" 40 | ) 41 | return EntryPointAccessors.fromApplication( 42 | appContext, 43 | ChatEntryPoint::class.java 44 | ) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/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.chatgpt.feature.chat.di 18 | 19 | import android.content.Context 20 | import androidx.work.WorkManager 21 | import dagger.Module 22 | import dagger.Provides 23 | import dagger.hilt.InstallIn 24 | import dagger.hilt.android.qualifiers.ApplicationContext 25 | import dagger.hilt.components.SingletonComponent 26 | import io.getstream.chat.android.client.ChatClient 27 | import javax.inject.Singleton 28 | 29 | @Module 30 | @InstallIn(SingletonComponent::class) 31 | internal object ChatModule { 32 | 33 | @Provides 34 | @Singleton 35 | fun provideStreamChatClient() = ChatClient.instance() 36 | 37 | @Provides 38 | @Singleton 39 | fun provideChatWorker(@ApplicationContext context: Context): WorkManager { 40 | return WorkManager.getInstance(context) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/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.chatgpt.feature.chat.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import com.skydoves.chatgpt.core.preferences.Preferences 22 | import com.skydoves.chatgpt.feature.chat.BuildConfig 23 | import com.skydoves.chatgpt.feature.chat.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 preferences: Preferences 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 user = User( 71 | id = preferences.userUUID, 72 | name = "User ${Random.nextInt(10000)}", 73 | image = "https://picsum.photos/id/${Random.nextInt(1000)}/300/300" 74 | ) 75 | 76 | val token = chatClient.devToken(user.id) 77 | chatClient.connectUser(user, token).enqueue(object : Call.Callback { 78 | override fun onResult(result: io.getstream.result.Result) { 79 | if (result.isFailure) { 80 | streamLog { 81 | "Can't connect user. Please check the app README.md and ensure " + 82 | "**Disable Auth Checks** is ON in the Dashboard" 83 | } 84 | } 85 | } 86 | }) 87 | } 88 | 89 | override fun dependencies(): List>> = 90 | listOf(StreamLogInitializer::class.java) 91 | } 92 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/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.chatgpt.feature.chat.initializer 18 | 19 | import android.content.Context 20 | import androidx.startup.Initializer 21 | import com.skydoves.chatgpt.feature.chat.BuildConfig 22 | import io.getstream.log.Priority 23 | import io.getstream.log.android.AndroidStreamLogger 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 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/messages/ChatGPTMessagesViewModel.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.chatgpt.feature.chat.messages 18 | 19 | import androidx.lifecycle.SavedStateHandle 20 | import androidx.lifecycle.ViewModel 21 | import androidx.lifecycle.viewModelScope 22 | import androidx.work.Constraints 23 | import androidx.work.Data 24 | import androidx.work.NetworkType 25 | import androidx.work.OneTimeWorkRequest 26 | import androidx.work.WorkInfo 27 | import androidx.work.WorkManager 28 | import com.skydoves.chatgpt.core.data.chat.commonChannelId 29 | import com.skydoves.chatgpt.core.data.coroutines.WhileSubscribedOrRetained 30 | import com.skydoves.chatgpt.core.data.repository.GPTMessageRepository 31 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens.Companion.argument_channel_id 32 | import com.skydoves.chatgpt.core.preferences.Empty 33 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker 34 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker.Companion.DATA_FAILURE 35 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker.Companion.DATA_MESSAGE_ID 36 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker.Companion.DATA_SUCCESS 37 | import com.skydoves.chatgpt.feature.chat.worker.ChatGPTMessageWorker.Companion.MESSAGE_EXTRA_CHAT_GPT 38 | import dagger.hilt.android.lifecycle.HiltViewModel 39 | import io.getstream.chat.android.client.ChatClient 40 | import io.getstream.chat.android.models.Message 41 | import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState 42 | import io.getstream.chat.android.ui.common.state.messages.list.MessageListItemState 43 | import io.getstream.log.streamLog 44 | import java.util.UUID 45 | import javax.inject.Inject 46 | import kotlinx.coroutines.flow.MutableStateFlow 47 | import kotlinx.coroutines.flow.SharingStarted 48 | import kotlinx.coroutines.flow.StateFlow 49 | import kotlinx.coroutines.flow.collectLatest 50 | import kotlinx.coroutines.flow.filter 51 | import kotlinx.coroutines.flow.map 52 | import kotlinx.coroutines.flow.stateIn 53 | import kotlinx.coroutines.launch 54 | 55 | @HiltViewModel 56 | class ChatGPTMessagesViewModel @Inject constructor( 57 | messageRepository: GPTMessageRepository, 58 | private val chatClient: ChatClient, 59 | private val workManager: WorkManager, 60 | savedStateHandle: SavedStateHandle 61 | ) : ViewModel() { 62 | 63 | private val channelId: String = savedStateHandle.get(argument_channel_id) ?: String.Empty 64 | 65 | private val messageItemSet = MutableStateFlow>(setOf()) 66 | val isLoading: StateFlow = messageItemSet.map { 67 | it.isNotEmpty() 68 | }.stateIn(viewModelScope, WhileSubscribedOrRetained, false) 69 | 70 | private val mutableError: MutableStateFlow = MutableStateFlow(String.Empty) 71 | val errorMessage: StateFlow = mutableError 72 | .filter { it.isNotEmpty() } 73 | .stateIn(viewModelScope, WhileSubscribedOrRetained, String.Empty) 74 | 75 | val isMessageEmpty: StateFlow = 76 | messageRepository.watchIsChannelMessageEmpty(channelId) 77 | .stateIn(viewModelScope, SharingStarted.Lazily, false) 78 | 79 | fun sendStreamChatMessage(text: String) { 80 | if (channelId != commonChannelId) { 81 | viewModelScope.launch { sendStreamMessage(text) } 82 | } 83 | } 84 | 85 | fun sendMessage(text: String, messagesItems: List) { 86 | messageItemSet.value += text 87 | viewModelScope.launch { 88 | val lastGptMessage = messagesItems 89 | .filterIsInstance() 90 | .filter { it.message.extraData[MESSAGE_EXTRA_CHAT_GPT] == true } 91 | .maxByOrNull { it.message.createdAt?.time ?: 0 } 92 | ?.message 93 | val workRequest = buildGPTMessageWorkerRequest(text, lastGptMessage) 94 | workManager.enqueue(workRequest) 95 | 96 | workManager.getWorkInfoByIdFlow(workRequest.id) 97 | .collectLatest { 98 | if (it.state == WorkInfo.State.SUCCEEDED) { 99 | val gptMessageText = it.outputData.getString(DATA_SUCCESS) 100 | val gptMessageId = it.outputData.getString(DATA_MESSAGE_ID) 101 | streamLog { "gpt message worker success: $gptMessageId $gptMessageText" } 102 | messageItemSet.value -= text 103 | } else if (it.state == WorkInfo.State.FAILED) { 104 | val error = it.outputData.getString(DATA_FAILURE) ?: "" 105 | streamLog { "gpt message worker failed: $error" } 106 | messageItemSet.value -= messageItemSet.value 107 | mutableError.value = error 108 | } 109 | } 110 | } 111 | } 112 | 113 | private suspend fun sendStreamMessage(text: String) { 114 | val channelClient = chatClient.channel(channelId) 115 | channelClient.sendMessage( 116 | message = Message( 117 | id = UUID.randomUUID().toString(), 118 | cid = channelClient.cid, 119 | text = text, 120 | extraData = mutableMapOf("ChatGPT" to true) 121 | ) 122 | ).await() 123 | } 124 | 125 | private fun buildGPTMessageWorkerRequest( 126 | text: String, 127 | lastMessage: Message? 128 | ): OneTimeWorkRequest { 129 | val constraints = Constraints.Builder() 130 | .setRequiredNetworkType(NetworkType.CONNECTED) 131 | .build() 132 | 133 | val data = Data.Builder() 134 | .putString(ChatGPTMessageWorker.DATA_TEXT, text) 135 | .putString(ChatGPTMessageWorker.DATA_CHANNEL_ID, channelId) 136 | .putString(ChatGPTMessageWorker.DATA_LAST_MESSAGE, lastMessage?.text.orEmpty()) 137 | .build() 138 | 139 | return OneTimeWorkRequest.Builder(ChatGPTMessageWorker::class.java) 140 | .setConstraints(constraints) 141 | .setInputData(data) 142 | .build() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/reactions/ChatGPTReactionFactory.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.chatgpt.feature.chat.reactions 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.res.painterResource 21 | import com.skydoves.chatgpt.feature.chat.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 ChatGPTReactionFactory( 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 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/theme/ChatGPTStreamTheme.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.chatgpt.feature.chat.theme 18 | 19 | import androidx.compose.foundation.isSystemInDarkTheme 20 | import androidx.compose.runtime.Composable 21 | import com.skydoves.chatgpt.core.designsystem.theme.BACKGROUND900 22 | import com.skydoves.chatgpt.core.designsystem.theme.STREAM_PRIMARY 23 | import com.skydoves.chatgpt.core.designsystem.theme.STREAM_PRIMARY_LIGHT 24 | import com.skydoves.chatgpt.feature.chat.reactions.ChatGPTReactionFactory 25 | import io.getstream.chat.android.compose.ui.theme.ChatTheme 26 | import io.getstream.chat.android.compose.ui.theme.StreamColors 27 | 28 | @Composable 29 | fun ChatGPTStreamTheme( 30 | darkTheme: Boolean = isSystemInDarkTheme(), 31 | content: @Composable () -> Unit 32 | ) { 33 | val streamColors = if (darkTheme) { 34 | StreamColors.defaultDarkColors().copy( 35 | appBackground = BACKGROUND900, 36 | primaryAccent = STREAM_PRIMARY, 37 | ownMessagesBackground = STREAM_PRIMARY 38 | ) 39 | } else { 40 | StreamColors.defaultColors().copy( 41 | primaryAccent = STREAM_PRIMARY, 42 | ownMessagesBackground = STREAM_PRIMARY_LIGHT 43 | ) 44 | } 45 | 46 | ChatTheme( 47 | colors = streamColors, 48 | reactionIconFactory = ChatGPTReactionFactory(), 49 | content = content 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /feature-chat/src/main/kotlin/com/skydoves/chatgpt/feature/chat/worker/ChatGPTMessageWorker.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.chatgpt.feature.chat.worker 18 | 19 | import android.content.Context 20 | import androidx.hilt.work.HiltWorker 21 | import androidx.work.CoroutineWorker 22 | import androidx.work.Data 23 | import androidx.work.WorkerParameters 24 | import com.skydoves.chatgpt.core.data.repository.GPTMessageRepository 25 | import com.skydoves.chatgpt.core.model.GPTMessage 26 | import com.skydoves.chatgpt.core.model.network.GPTChatRequest 27 | import com.skydoves.chatgpt.feature.chat.di.ChatEntryPoint 28 | import com.skydoves.sandwich.getOrThrow 29 | import com.skydoves.sandwich.isSuccess 30 | import com.skydoves.sandwich.messageOrNull 31 | import dagger.assisted.Assisted 32 | import dagger.assisted.AssistedInject 33 | import io.getstream.chat.android.client.ChatClient 34 | import io.getstream.chat.android.models.Message 35 | import io.getstream.log.streamLog 36 | import javax.inject.Inject 37 | 38 | @HiltWorker 39 | internal class ChatGPTMessageWorker @AssistedInject constructor( 40 | @Assisted private val context: Context, 41 | @Assisted private val workerParams: WorkerParameters 42 | ) : CoroutineWorker(context, workerParams) { 43 | 44 | @Inject 45 | internal lateinit var repository: GPTMessageRepository 46 | 47 | @Inject 48 | internal lateinit var chatClient: ChatClient 49 | 50 | override suspend fun doWork(): Result { 51 | ChatEntryPoint.resolve(context).inject(this) 52 | 53 | val text = workerParams.inputData.getString(DATA_TEXT) ?: return Result.failure() 54 | val channelId = workerParams.inputData.getString(DATA_CHANNEL_ID) ?: return Result.failure() 55 | val lastMessage = workerParams.inputData.getString(DATA_LAST_MESSAGE) 56 | 57 | val messages: MutableList = mutableListOf() 58 | if (lastMessage != null) { 59 | messages.add( 60 | GPTMessage( 61 | role = "system", 62 | content = lastMessage 63 | ) 64 | ) 65 | } 66 | messages.add( 67 | GPTMessage( 68 | role = "user", 69 | content = text 70 | ) 71 | ) 72 | 73 | val request = GPTChatRequest( 74 | model = "gpt-3.5-turbo-0125", 75 | messages = messages 76 | ) 77 | val response = repository.sendMessage(request) 78 | return if (response.isSuccess) { 79 | val data = response.getOrThrow() 80 | val messageText = data.choices.firstOrNull()?.message?.content.orEmpty() 81 | val messageId = data.id 82 | sendStreamMessage(messageText, messageId, channelId) 83 | streamLog { "worker success!" } 84 | Result.success( 85 | Data.Builder() 86 | .putString(DATA_SUCCESS, messageText) 87 | .putString(DATA_MESSAGE_ID, messageId) 88 | .build() 89 | ) 90 | } else { 91 | streamLog { "worker failure!" } 92 | Result.failure(Data.Builder().putString(DATA_FAILURE, response.messageOrNull ?: "").build()) 93 | } 94 | } 95 | 96 | private suspend fun sendStreamMessage( 97 | text: String, 98 | messageId: String, 99 | channelId: String 100 | ) { 101 | val channelClient = chatClient.channel(channelId) 102 | channelClient.sendMessage( 103 | message = Message( 104 | id = messageId, 105 | cid = channelClient.cid, 106 | text = text, 107 | extraData = mutableMapOf( 108 | MESSAGE_EXTRA_CHAT_GPT to true 109 | ) 110 | ) 111 | ).await() 112 | } 113 | 114 | companion object { 115 | const val DATA_TEXT = "DATA_TEXT" 116 | const val DATA_CHANNEL_ID = "DATA_CHANNEL_ID" 117 | const val DATA_MESSAGE_ID = "DATA_PARENT_ID" 118 | const val DATA_LAST_MESSAGE = "DATA_LAST_MESSAGE" 119 | const val DATA_SUCCESS = "DATA_SUCCESS" 120 | const val DATA_FAILURE = "DATA_FAILURE" 121 | 122 | const val MESSAGE_EXTRA_CHAT_GPT = "ChatGPT" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /feature-chat/src/main/res/drawable/joy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/feature-chat/src/main/res/drawable/joy.png -------------------------------------------------------------------------------- /feature-chat/src/main/res/drawable/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/feature-chat/src/main/res/drawable/love.png -------------------------------------------------------------------------------- /feature-chat/src/main/res/drawable/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/feature-chat/src/main/res/drawable/smile.png -------------------------------------------------------------------------------- /feature-chat/src/main/res/drawable/thumbsup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/feature-chat/src/main/res/drawable/thumbsup.png -------------------------------------------------------------------------------- /feature-chat/src/main/res/drawable/wink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/feature-chat/src/main/res/drawable/wink.png -------------------------------------------------------------------------------- /feature-chat/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Stream 19 | A random ChatGPT channel is created! 20 | Hi, I\'m OpenAI\'s ChatGPT with Stream SDK. Ask me anything! 21 | For more details, check out OpenAI API guidelines: https://platform.openai.com/docs/guides/rate-limits?context=tier-free 22 | 23 | -------------------------------------------------------------------------------- /feature-login/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature-login/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.chatgpt.feature.login" 26 | } 27 | 28 | dependencies { 29 | implementation(libs.androidx.lifecycle.runtimeCompose) 30 | implementation(libs.androidx.lifecycle.viewModelCompose) 31 | } 32 | -------------------------------------------------------------------------------- /feature-login/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /feature-login/src/main/kotlin/com/skydoves/chatgpt/feature/login/ChatGPTLogin.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.chatgpt.feature.login 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.material3.CircularProgressIndicator 24 | import androidx.compose.material3.Text 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.runtime.LaunchedEffect 27 | import androidx.compose.runtime.getValue 28 | import androidx.compose.ui.Alignment 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.unit.dp 32 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 33 | import com.skydoves.chatgpt.core.designsystem.theme.BACKGROUND900 34 | import com.skydoves.chatgpt.core.designsystem.theme.WHITE200 35 | import com.skydoves.chatgpt.core.navigation.AppComposeNavigator 36 | import com.skydoves.chatgpt.core.navigation.ChatGPTScreens 37 | import io.getstream.chat.android.client.ChatClient 38 | import io.getstream.chat.android.models.InitializationState 39 | 40 | @Composable 41 | fun ChatGPTLogin( 42 | composeNavigator: AppComposeNavigator 43 | ) { 44 | val initializationState 45 | by ChatClient.instance().clientState.initializationState.collectAsStateWithLifecycle() 46 | 47 | LaunchedEffect(key1 = initializationState) { 48 | if (initializationState == InitializationState.COMPLETE) { 49 | composeNavigator.navigateAndClearBackStack(ChatGPTScreens.Channels.name) 50 | } 51 | } 52 | 53 | Box( 54 | modifier = Modifier 55 | .fillMaxSize() 56 | .background(BACKGROUND900) 57 | ) { 58 | if (initializationState == InitializationState.INITIALIZING) { 59 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) 60 | } else if (initializationState == InitializationState.NOT_INITIALIZED) { 61 | Text( 62 | modifier = Modifier.padding(14.dp), 63 | text = stringResource(id = R.string.error_chat_sdk_initialization), 64 | color = WHITE200 65 | ) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /feature-login/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Oops, failed to load Stream Chat SDK.\nPlease check out the guidelines on the GitHub repository. 19 | 20 | -------------------------------------------------------------------------------- /figures/figure0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/figure0.png -------------------------------------------------------------------------------- /figures/figure1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/figure1.png -------------------------------------------------------------------------------- /figures/figure2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/figure2.png -------------------------------------------------------------------------------- /figures/figure3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/figure3.png -------------------------------------------------------------------------------- /figures/figure4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/figure4.png -------------------------------------------------------------------------------- /figures/login0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/login0.png -------------------------------------------------------------------------------- /figures/login1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/login1.png -------------------------------------------------------------------------------- /figures/login2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/login2.png -------------------------------------------------------------------------------- /figures/stream0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream0.png -------------------------------------------------------------------------------- /figures/stream1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream1.png -------------------------------------------------------------------------------- /figures/stream10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream10.png -------------------------------------------------------------------------------- /figures/stream11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream11.png -------------------------------------------------------------------------------- /figures/stream12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream12.png -------------------------------------------------------------------------------- /figures/stream13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream13.png -------------------------------------------------------------------------------- /figures/stream14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream14.png -------------------------------------------------------------------------------- /figures/stream2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream2.png -------------------------------------------------------------------------------- /figures/stream3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream3.png -------------------------------------------------------------------------------- /figures/stream4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream4.png -------------------------------------------------------------------------------- /figures/stream5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream5.png -------------------------------------------------------------------------------- /figures/stream6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream6.png -------------------------------------------------------------------------------- /figures/stream7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream7.png -------------------------------------------------------------------------------- /figures/stream8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream8.png -------------------------------------------------------------------------------- /figures/stream9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/figures/stream9.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Designed and developed by 2022 skydoves (Jaewoong Eum) 2 | # 3 | #Licensed under the Apache License, Version 2.0 (the "License"); 4 | #you may not use this file except in compliance with the License. 5 | #You may obtain a copy of the License at 6 | # 7 | #http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | #Unless required by applicable law or agreed to in writing, software 10 | #distributed under the License is distributed on an "AS IS" BASIS, 11 | #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | #See the License for the specific language governing permissions and 13 | #limitations under the License. 14 | 15 | # Project-wide Gradle settings. 16 | # IDE (e.g. Android Studio) users: 17 | # Gradle settings configured through the IDE *will override* 18 | # any settings specified in this file. 19 | # For more details on how to configure your build environment visit 20 | # http://www.gradle.org/docs/current/userguide/build_environment.html 21 | # Specifies the JVM arguments used for the daemon process. 22 | # The setting is particularly useful for tweaking memory settings. 23 | 24 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory 25 | org.gradle.jvmargs=-Xms1024m -Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true 26 | 27 | # https://docs.gradle.org/current/userguide/build_cache.html 28 | org.gradle.caching=true 29 | 30 | # When configured, Gradle will run in incubating parallel mode. 31 | # This option should only be used with decoupled projects. More details, visit 32 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 33 | org.gradle.parallel=true 34 | 35 | # Configure only necessary projects, useful with multimodule projects 36 | org.gradle.configureondemand=true 37 | 38 | # AndroidX Migration https://developer.android.com/jetpack/androidx/migrate 39 | android.useAndroidX=true 40 | 41 | # Automatically convert third-party libraries to use AndroidX 42 | android.enableJetifier=true 43 | 44 | # Enables namespacing of each library's R class so that its R class includes only the 45 | # resources declared in the library itself and none from the library's dependencies, 46 | # thereby reducing the size of the R class for that library 47 | android.nonTransitiveRClass=true 48 | 49 | # Disabled R8 full mode 50 | android.enableR8.fullMode=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/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.7-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 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. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 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/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/previews/preview0.png -------------------------------------------------------------------------------- /previews/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/previews/preview1.png -------------------------------------------------------------------------------- /previews/preview2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mannmehta0611/chatbot/51e86315fb0164622d01bc83330a0f3dad2d6853/previews/preview2.gif -------------------------------------------------------------------------------- /secrets.defaults.properties: -------------------------------------------------------------------------------- 1 | STREAM_API_KEY=aaaaaaaaaa 2 | GPT_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 = "chatgpt-android" 20 | include(":app") 21 | include(":core-model") 22 | include(":core-network") 23 | include(":core-preferences") 24 | include(":core-data") 25 | include(":core-designsystem") 26 | include(":core-navigation") 27 | include(":feature-chat") 28 | include(":feature-login") 29 | include(":benchmark") -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------