├── .github └── workflows │ ├── android-ci-test.yml │ ├── android-ci.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── .kotlin └── errors │ ├── errors-1728569097183.log │ ├── errors-1728569168864.log │ ├── errors-1728569240614.log │ ├── errors-1728569359431.log │ ├── errors-1728569511078.log │ ├── errors-1728569545241.log │ ├── errors-1728569587439.log │ ├── errors-1728569603404.log │ ├── errors-1728569897006.log │ ├── errors-1728603696143.log │ └── errors-1728824767720.log ├── README.md ├── README_cn.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── app │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── app │ │ │ ├── App.kt │ │ │ ├── navigation │ │ │ ├── AppNavHost.kt │ │ │ └── Screen.kt │ │ │ └── ui │ │ │ ├── MainActivity.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── github │ └── app │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── feature ├── details │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── github │ │ │ └── details │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── github │ │ │ └── details │ │ │ ├── data │ │ │ ├── model │ │ │ │ ├── RepositoryItemModel.kt │ │ │ │ └── UserResultModel.kt │ │ │ ├── remote │ │ │ │ ├── ApiRepositoriesResponse.kt │ │ │ │ └── DetailsApiService.kt │ │ │ └── repository │ │ │ │ └── GitHubUserDetailsRepository.kt │ │ │ ├── di │ │ │ ├── DetailsModule.kt │ │ │ └── DetailsRepositoryModule.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── Repository.kt │ │ │ │ └── User.kt │ │ │ └── repository │ │ │ │ └── UserDetailsRepository.kt │ │ │ └── ui │ │ │ ├── DetailAppBar.kt │ │ │ ├── DetailContent.kt │ │ │ ├── DetailScreen.kt │ │ │ ├── DetailViewModel.kt │ │ │ ├── ErrorContent.kt │ │ │ ├── Loading.kt │ │ │ ├── RepositoryCard.kt │ │ │ ├── UserConnections.kt │ │ │ └── UserHeader.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── andy │ │ └── github │ │ └── details │ │ └── ExampleUnitTest.kt └── home │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── andy │ │ └── github │ │ └── home │ │ ├── data │ │ └── local │ │ │ └── SearchHistoryDaoTest.kt │ │ ├── di │ │ └── FakeDatabaseModule.kt │ │ └── ui │ │ ├── HomeContentTest.kt │ │ ├── HomeSearchBarTest.kt │ │ └── SearchHistoryTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── andy │ │ │ └── github │ │ │ └── home │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── SearchHistoryDao.kt │ │ │ │ ├── SearchHistoryDatabase.kt │ │ │ │ └── entity │ │ │ │ │ └── SearchEntity.kt │ │ │ ├── model │ │ │ │ └── SimpleUserModel.kt │ │ │ ├── paging │ │ │ │ └── SearchPagingSource.kt │ │ │ ├── remote │ │ │ │ ├── ApiSimpleUserResponse.kt │ │ │ │ └── HomeApiService.kt │ │ │ └── repository │ │ │ │ ├── DatabaseSearchHistoryRepository.kt │ │ │ │ └── SimpleGitHubUserRepository.kt │ │ │ ├── di │ │ │ ├── DatabaseModule.kt │ │ │ ├── HomeModule.kt │ │ │ └── HomeRepositoryModule.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── Search.kt │ │ │ │ └── SimpleUser.kt │ │ │ └── repository │ │ │ │ ├── SearchHistoryRepository.kt │ │ │ │ └── SimpleUserRepository.kt │ │ │ └── ui │ │ │ ├── HomeContent.kt │ │ │ ├── HomeScreen.kt │ │ │ ├── HomeSearchBar.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── SearchHistory.kt │ │ │ ├── SearchHistoryList.kt │ │ │ ├── SearchHistoryViewModel.kt │ │ │ ├── UserItem.kt │ │ │ └── components │ │ │ ├── ErrorContent.kt │ │ │ ├── LoadingItem.kt │ │ │ ├── NoData.kt │ │ │ └── NoMoreDataItem.kt │ └── res │ │ └── drawable │ │ ├── baseline_person_50.xml │ │ └── baseline_person_off_50.xml │ └── test │ └── java │ └── com │ └── andy │ └── github │ └── home │ ├── ExampleUnitTest.kt │ ├── data │ └── FakeSearchHistoryRepository.kt │ └── ui │ └── SearchHistoryViewModelTest.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── common │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── common │ │ │ ├── JsonKt.kt │ │ │ ├── JsonManager.kt │ │ │ ├── NumberFormatter.kt │ │ │ └── di │ │ │ └── DispatchersModule.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── andy │ │ └── common │ │ └── ExampleUnitTest.kt ├── framework │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── framework │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── andy │ │ │ │ └── framework │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── LazyFragment.kt │ │ └── res │ │ │ └── anim │ │ │ ├── slid_in_from_bottom.xml │ │ │ ├── slid_in_from_left.xml │ │ │ ├── slid_in_from_right.xml │ │ │ ├── slid_in_from_top.xml │ │ │ ├── slid_out_to_bottom.xml │ │ │ ├── slid_out_to_left.xml │ │ │ ├── slid_out_to_right.xml │ │ │ └── slid_out_to_top.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── andy │ │ └── framework │ │ └── ExampleUnitTest.kt ├── network │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── network │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── andy │ │ │ └── network │ │ │ ├── ApiResultAdapterFactory.kt │ │ │ ├── ApiResultCall.kt │ │ │ ├── ApiResultCallAdapter.kt │ │ │ ├── AuthorizationHeaderInterceptor.kt │ │ │ ├── common │ │ │ ├── ApiResult.kt │ │ │ └── Constants.kt │ │ │ ├── data │ │ │ └── ApiResult.kt │ │ │ ├── di │ │ │ └── NetworkModule.kt │ │ │ └── domain │ │ │ ├── Result.kt │ │ │ └── ResultKt.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── andy │ │ └── network │ │ └── ExampleUnitTest.kt └── testing │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── andy │ │ └── testing │ │ ├── AppTestRunner.kt │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── andy │ │ └── testing │ │ └── AppTestRunner.kt │ └── test │ └── java │ └── com │ └── andy │ └── testing │ └── ExampleUnitTest.kt └── settings.gradle.kts /.github/workflows/android-ci-test.yml: -------------------------------------------------------------------------------- 1 | name: Generated APK and AAB (Upload Artifact to Github Action) 2 | 3 | env: 4 | # The name of the main module repository 5 | main_project_module: app 6 | # The name of the Play Store 7 | playstore_name: GitHubApp 8 | 9 | on: 10 | pull_request: 11 | branches: 12 | - 'main' 13 | 14 | push: 15 | branches: 16 | - 'main' 17 | - 'dev-testing' 18 | - 'dev-modulization' 19 | - 'feature/*' 20 | # Allows you to run this workflow manually from the Actions tab 21 | workflow_dispatch: 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | # Set Current date as env Variable 30 | - name: Set current date as env variavle 31 | run: | 32 | date_today=$(date +%Y-%m-%d_%H-%M-%S) 33 | echo "date_today=$date_today" >> $GITHUB_ENV 34 | 35 | # Set Repository Name as Env Variable 36 | - name: Set repository name as env variable 37 | run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV 38 | 39 | - name: Set Up JDK 40 | uses: actions/setup-java@v3 41 | with: 42 | distribution: 'zulu' #See 'Supported distributions' for avaible options 43 | java-version: '17' 44 | cache: 'gradle' 45 | 46 | - name: Change wrapper permission 47 | run: chmod +x ./gradlew 48 | 49 | # Run Tests Build 50 | - name: Run gradle tests 51 | run: ./gradlew test 52 | 53 | # Run Build Project 54 | - name: Build gradle project 55 | run: ./gradlew build 56 | 57 | # Create APK Debug 58 | - name: Build apk debug project (APK) - ${{ env.main_project_module }} module 59 | run: ./gradlew assembleDebug 60 | 61 | # Create APK Release 62 | - name: Build apk release roject (APK) - ${{ env.main_project_module }} module 63 | run: ./gradlew assemble 64 | 65 | # Create Bundle AAB Release 66 | # Noted for main module build [main_project_module]:bundleRelease 67 | - name: Build app bundle relase (AAB) - ${{ env.main_project_module }} module 68 | run: ./gradlew ${{ env.main_project_module }}:bundleRelease 69 | 70 | # Upload Artifact Build 71 | # Noted For Output [main_project_main]/build.outputs/apk/debug/ 72 | - name: Upload APK Debug - ${{env.repository_name }} 73 | uses: actions/upload-artifact@v3 74 | with: 75 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) debug generated 76 | path: ${{ env.main_project_module }}/build/outputs/apk/debug/ 77 | 78 | # Noted For Output [main_project_module]/build/outputs/apk/release/ 79 | - name: Upload APK Release - ${{ env.repository_name }} 80 | uses: actions/upload-artifact@v3 81 | with: 82 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) release generated 83 | path: ${{ env.main_project_module }}/build/outputs/apk/release/ 84 | 85 | # Noted For Output [main_project_module]/build/outputs/bundle/release/ 86 | - name: Upload AAB (App Bundle) Release - ${{ env.repository_name }} 87 | uses: actions/upload-artifact@v3 88 | with: 89 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - App bundle(s) AAB release generated 90 | path: ${{ env.main_project_module }}/build/outputs/bundle/release/ 91 | -------------------------------------------------------------------------------- /.github/workflows/android-ci.yml: -------------------------------------------------------------------------------- 1 | name: Generated APK AAB (Upload - Create Artifact To Github Action Test) 2 | 3 | env: 4 | # The name of the main module repository 5 | main_project_module: app 6 | 7 | # The name of the Play Store 8 | playstore_name: Test ID 9 | 10 | on: 11 | 12 | pull_request: 13 | branches: 14 | - 'main' 15 | 16 | # Allows you to run this workflow manually from the Actions tab 17 | workflow_dispatch: 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | # Set Current Date As Env Variable 28 | - name: Set current date as env variable 29 | run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV 30 | 31 | # Set Repository Name As Env Variable 32 | - name: Set repository name as env variable 33 | run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV 34 | 35 | - name: Set Up JDK 36 | uses: actions/setup-java@v3 37 | with: 38 | distribution: 'zulu' # See 'Supported distributions' for available options 39 | java-version: '17' 40 | cache: 'gradle' 41 | 42 | - name: Change wrapper permissions 43 | run: chmod +x ./gradlew 44 | 45 | # Run Tests Build 46 | - name: Run gradle tests 47 | run: ./gradlew test 48 | 49 | # Run Build Project 50 | - name: Build gradle project 51 | run: ./gradlew build 52 | 53 | # Create APK Debug 54 | - name: Build apk debug project (APK) - ${{ env.main_project_module }} module 55 | run: ./gradlew assembleDebug 56 | 57 | # Create APK Release 58 | - name: Build apk release project (APK) - ${{ env.main_project_module }} module 59 | run: ./gradlew assemble 60 | 61 | # Create Bundle AAB Release 62 | # Noted for main module build [main_project_module]:bundleRelease 63 | - name: Build app bundle release (AAB) - ${{ env.main_project_module }} module 64 | run: ./gradlew ${{ env.main_project_module }}:bundleRelease 65 | 66 | # Upload Artifact Build 67 | # Noted For Output [main_project_module]/build/outputs/apk/debug/ 68 | - name: Upload APK Debug - ${{ env.repository_name }} 69 | uses: actions/upload-artifact@v3 70 | with: 71 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) debug generated 72 | path: ${{ env.main_project_module }}/build/outputs/apk/debug/ 73 | 74 | # Noted For Output [main_project_module]/build/outputs/apk/release/ 75 | - name: Upload APK Release - ${{ env.repository_name }} 76 | uses: actions/upload-artifact@v3 77 | with: 78 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) release generated 79 | path: ${{ env.main_project_module }}/build/outputs/apk/release/ 80 | 81 | # Noted For Output [main_project_module]/build/outputs/bundle/release/ 82 | - name: Upload AAB (App Bundle) Release - ${{ env.repository_name }} 83 | uses: actions/upload-artifact@v3 84 | with: 85 | name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - App bundle(s) AAB release generated 86 | path: ${{ env.main_project_module }}/build/outputs/bundle/release/ 87 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy To Google Play 2 | 3 | on: 4 | # push: 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | test: 10 | name: Unit Test 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Setup JDK 17 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: 'temurin' 20 | java-version: 17 21 | cache: 'gradle' 22 | 23 | - name: Grant excecute permissions for gradlew 24 | run: chmod +x gradlew 25 | 26 | - name: Run unit tests 27 | run: ./gradlew clean testDebug 28 | 29 | distribute: 30 | name: Distribute bundle to Google Play 31 | needs: test 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - name: Setup JDK 17 38 | uses: actions/setup-java@v4 39 | with: 40 | distribution: 'temurin' 41 | java-version: 17 42 | cache: 'gradle' 43 | 44 | - name: Version Bump 45 | uses: chkfung/android-version-action@v1.2.3 46 | with: 47 | gradlePath: app/build.gradle.kts 48 | versionCode: ${{ github.run_number }} 49 | 50 | - name: Assemble Release Bundle 51 | run: ./gradlew bundleRelease 52 | 53 | - name: Sign Release 54 | uses: r0adkll/sign-android-release@v1 55 | with: 56 | releaseDirectory: app/build/outputs/bundle/release 57 | signingKeyBase64: ${{ secrets.ANDROID_KEYSTORE }} # generate and setting the keyStore 58 | keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} 59 | alias: ${{ secrets.ANDROID_DEVS_ALIAS }} 60 | keyPassword: ${{ secrets.ANDROID_DEVS_ALIAS_PASSWORD }} 61 | 62 | - name: Setup Authorization with Google Play Store 63 | run: echo '${{ secrets.PLAY_AUTH_JSON }}' > service_account.json 64 | 65 | - name: Deploy bundle to Google Play 66 | uses: r0adkll/upload-google-play@v1.1.3 67 | with: 68 | serviceAccountJson: service_account.json 69 | pakageName: com.github.app 70 | releaseFiles: app/build/outputs/bundle/release/app-release.aab 71 | track: internal 72 | status: 'completed' 73 | whatsNewDirectory: whatsNew/ # TODO establist whatsNew directorys under project directory 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Setup JDK 17 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: 'temurin' 18 | java-version: 17 19 | cache: 'gradle' 20 | 21 | - name: Grant excecute permissions for gradlew 22 | run: chmod +x gradlew 23 | 24 | - name: Run unit tests 25 | run: ./gradlew clean testDebug 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /.kotlin 17 | /.idea 18 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.kotlin/errors/errors-1728603696143.log: -------------------------------------------------------------------------------- 1 | kotlin version: 2.0.20 2 | error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: 3 | 1. Kotlin compile daemon is ready 4 | 5 | -------------------------------------------------------------------------------- /.kotlin/errors/errors-1728824767720.log: -------------------------------------------------------------------------------- 1 | kotlin version: 2.0.20 2 | error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: 3 | 1. Kotlin compile daemon is ready 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CleanArchitectureGitHubApp 2 | 3 | ![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen) 4 | 5 | [简体中文版说明 >>>](https://github.com/andyhaha/CleanArchitectureGitHubApp/blob/main/README_cn.md) 6 | 7 | **🔥Special Note:** Add a variable in the local.properties file: API_TOKEN=xxx Replace xxx with your own GitHub API token. 8 | ## Project Introduction 9 | 10 | This is a GitHub user search application that allows users to search for GitHub users on the homepage and display a list of users. Users can click on a user in the list to enter their detailed information page, showcasing the user's basic information as well as the repositories they have created. 11 | 12 | ## Architecture Introduction 13 | 14 | This project follows the **Clean Architecture** principles and adopts the **MVVM** (Model-View-ViewModel) architectural pattern to achieve better modularization and separation of concerns. By decoupling different business logic from UI logic, the project is easier to test and maintain. 15 | 16 | Here’s an overview of your project structure, following the style of your example: 17 | 18 | ## Modules Overview 19 | 20 | The project is divided into several modules: 21 | 22 | - **:app** - Main Android app module that coordinates feature and library modules. 23 | - **:feature:home** - Displays a list of GitHub users with the ability to search and store results locally using Room. 24 | - **:feature:details** - Shows detailed information about selected users, including their profile and repositories. 25 | - **:libs:network** - Manages network requests using Retrofit and Moshi for data serialization. 26 | - **:libs:common** - Kotlin-only module providing utility functions and common classes used throughout the app. 27 | 28 | ## Technologies Used 29 | 30 | This project utilizes the following technology stack: 31 | 32 | - **Clean Architecture**: Implements clean architecture for better separation of concerns, enhancing code maintainability and testability.. 33 | - **Multi-Module**: Divides the codebase into smaller, reusable, and testable modules, improving scalability, reducing dependencies, and simplifying the management and maintenance of large projects. 34 | - **Jetpack Compose**: For building the user interface, simplifying the declarative programming approach for UI. 35 | - **MVVM**: Implements the MVVM architectural pattern to separate view logic from business logic. 36 | - **Flow**: Used for handling asynchronous data streams, providing a reactive approach to manage and emit data updates in a lifecycle-aware manner. 37 | - **Dagger Hilt**: Dagger Hilt simplifies dependency injection across modules and enhances testability by enabling easy mocking for unit and UI tests. 38 | - **Retrofit**: Used for network requests, simplifying the process of API calls. 39 | - **Moshi**: Used for JSON parsing, simplifying the conversion between JSON and Kotlin/Java objects. 40 | - **Paging3**: Used to implement efficient pagination for user search, enabling smooth loading of data in chunks as the user scrolls. 41 | - **Room Database**: Provides local database support for easy data storage and querying. 42 | - **Moshi**: Used for parsing JSON data, enhancing the efficiency of data serialization and deserialization. 43 | - **GitHub Authorize**: Uses GitHub OAuth for authorization, ensuring users can safely access and search GitHub data. 44 | - **Room Database Testing**: Ensures reliable data storage by testing CRUD operations and data integrity in the local SQLite database through unit and instrumented tests. 45 | - **UI Testing**: Verifies the functionality and user experience of Compose-based components, including HomeContent, SearchHistory, and HomeSearchBar, through thorough Compose UI tests to ensure smooth interactions and correct rendering. 46 | 47 | I welcome suggestions and contributions to this project! Please create a Pull Request or submit an issue. 48 | 49 | ## License 50 | 51 | Copyright (c) [2024] [Andy] 52 | 53 | Permission is hereby granted, free of charge, to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software, and to permit others to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 58 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # 项目名称 2 | 3 | ![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen) 4 | 5 | **🔥注意事项:** 在local.properties文件中加一个变量,API_TOKEN=xxx, 把“xxx”替换成自己的GitHub API token 6 | 7 | ## 项目简介 8 | 9 | 这是一个 GitHub 用户搜索应用程序,用户可以在首页搜索 GitHub 用户并显示用户列表。用户可以点击列表中的某个用户,进入该用户的详细信息页面,展示用户的基本信息以及他们创建的仓库。 10 | 11 | ## 架构简介 12 | 13 | 该项目遵循 **Clean Architecture** 原则,并采用 **MVVM**(Model-View-ViewModel)架构模式,以实现更好的模块化和关注点分离。通过将不同的业务逻辑与 UI 逻辑解耦,项目更容易进行测试和维护。 14 | 15 | 以下是项目结构的概述,遵循您的示例风格: 16 | 17 | ## 模块概述 18 | 19 | 该项目分为多个模块: 20 | 21 | - **:app** - 主 Android 应用模块,协调特性模块和库模块。 22 | - **:feature:home** - 显示 GitHub 用户列表,支持搜索并使用 Room 存储搜索结果。 23 | - **:feature:details** - 显示选定用户的详细信息,包括其个人资料和仓库。 24 | - **:libs:network** - 使用 Retrofit 和 Moshi 进行网络请求和数据序列化。 25 | - **:libs:common** - 仅 Kotlin 模块,提供应用中常用的工具函数和公共类。 26 | 27 | ## 使用的技术 28 | 29 | 该项目使用了以下技术栈: 30 | 31 | - **Clean Architecture**: 实现了“清洁”架构,提供更好的关注点分离,提高了代码的可维护性和可测试性。 32 | - **Multi-Modules**:将代码库划分为更小、可重用且可测试的模块,提高可扩展性,减少依赖,并简化大型项目的管理和维护。 33 | - **MVVM**: 实现了 MVVM 架构模式,将视图逻辑与业务逻辑分离。 34 | - **Flow & ViewModel**: 用于以生命周期感知的方式管理 UI 相关数据。 35 | - **Jetpack Compose**: 用于构建用户界面,简化了声明式编程方式的 UI 开发。 36 | - **Dagger Hilt**: Dagger Hilt 简化了跨模块的依赖注入,通过便捷的单元和 UI 测试的 mock 支持,增强了可测试性。 37 | - **Retrofit**: 用于网络请求,简化了 API 调用的过程。 38 | - **Room Database**: 提供本地数据库支持,方便数据存储和查询。 39 | - **Moshi**: 用于解析 JSON 数据,提高了数据序列化和反序列化的效率。 40 | - **Coil**: 用于图像加载,提供平滑的图像处理和缓存功能。 41 | - **GitHub Authorize**: 使用 GitHub OAuth 进行授权,确保用户可以安全地访问和搜索 GitHub 数据。 42 | - **Room Database Testing**: 通过单元测试和仪器测试,确保本地 SQLite 数据库中的数据存储的可靠性,测试 CRUD 操作和数据完整性。 43 | - **UI Testing**:通过 Compose UI 测试验证 HomeContent、SearchHistory 和 HomeSearchBar 等基于 Compose 的组件的功能和用户体验,确保交互流畅且渲染正确。 44 | 45 | ## 贡献 46 | 47 | 欢迎对这个项目提出建议和贡献!请创建一个 Pull Request 或提交问题。 48 | 49 | ## 许可证 50 | 51 | 版权所有 (c) [2024] [Andy] 52 | 53 | 特此免费授予使用、复制、修改、合并、发布、分发、再许可和/或出售本软件的副本,以及允许他人这样做的权限,但须遵守以下条件: 54 | 55 | 上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中。 56 | 57 | 本软件按“原样”提供,不附任何明示或暗示的担保,包括但不限于适销性、特定用途的适用性和非侵权的担保。在任何情况下,作者或版权持有者均不对因使用本软件或与本软件有关的行为所引起的任何索赔、损害或其他责任负责。 58 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | 3 | plugins { 4 | alias(libs.plugins.android.application) 5 | alias(libs.plugins.jetbrains.kotlin.android) 6 | alias(libs.plugins.google.dagger.hilt.android) 7 | alias(libs.plugins.ksp) 8 | alias(libs.plugins.compose.compiler) 9 | // kotlin("plugin.serialization") version "2.0.21" 10 | alias(libs.plugins.kotlin.serialization) 11 | } 12 | 13 | android { 14 | namespace = "com.github.app" 15 | compileSdk = 35 16 | 17 | defaultConfig { 18 | applicationId = "com.github.app" 19 | minSdk = 25 20 | targetSdk = 35 21 | versionCode = 1 22 | versionName = "1.0" 23 | 24 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 25 | 26 | // Custom test runner to set up Hilt dependency graph 27 | // testInstrumentationRunner = "com.andy.testing.AppTestRunner" 28 | vectorDrawables { 29 | useSupportLibrary = true 30 | } 31 | 32 | vectorDrawables { 33 | useSupportLibrary = true 34 | } 35 | 36 | // get API_TOKEN from local.properties 37 | // val localProperties = Properties() 38 | // val localPropertiesFile = rootProject.file("local.properties") 39 | // if (localPropertiesFile.exists()) { 40 | // localProperties.load(localPropertiesFile.inputStream()) 41 | // } 42 | // val apiToken = localProperties.getProperty("API_TOKEN", "default_token") 43 | // buildConfigField("String", "API_TOKEN", "\"$apiToken\"") 44 | } 45 | 46 | buildTypes { 47 | release { 48 | isMinifyEnabled = true 49 | isShrinkResources = true 50 | proguardFiles( 51 | getDefaultProguardFile("proguard-android-optimize.txt"), 52 | "proguard-rules.pro" 53 | ) 54 | } 55 | } 56 | compileOptions { 57 | sourceCompatibility = JavaVersion.VERSION_1_8 58 | targetCompatibility = JavaVersion.VERSION_1_8 59 | } 60 | kotlinOptions { 61 | jvmTarget = "1.8" 62 | } 63 | buildFeatures { 64 | compose = true 65 | // buildConfig = true 66 | } 67 | composeOptions { 68 | kotlinCompilerExtensionVersion = "1.5.1" 69 | } 70 | packaging { 71 | resources { 72 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 73 | } 74 | } 75 | } 76 | 77 | dependencies { 78 | testImplementation(libs.junit) 79 | androidTestImplementation(libs.androidx.junit) 80 | androidTestImplementation(libs.androidx.espresso.core) 81 | androidTestImplementation(platform(libs.androidx.compose.bom)) 82 | androidTestImplementation(libs.androidx.ui.test.junit4) 83 | androidTestImplementation(libs.google.dagger.hilt.android.testing) 84 | debugImplementation(libs.androidx.ui.tooling) 85 | debugImplementation(libs.androidx.ui.test.manifest) 86 | kspTest(libs.google.dagger.hilt.compiler) 87 | 88 | implementation(libs.androidx.core.ktx) 89 | implementation(libs.androidx.lifecycle.runtime.ktx) 90 | implementation(libs.androidx.activity.compose) 91 | implementation(platform(libs.androidx.compose.bom)) 92 | implementation(libs.androidx.ui) 93 | implementation(libs.androidx.ui.graphics) 94 | implementation(libs.androidx.ui.tooling.preview) 95 | implementation(libs.androidx.compose.material3.android) 96 | 97 | ksp(libs.google.dagger.hilt.compiler) 98 | // only ksp(libs.hilt.compiler) is needed! 99 | // implementation(libs.dagger.hilt.compiler) 100 | implementation(libs.google.dagger.hilt.android) 101 | // navigation 102 | implementation(libs.androidx.hilt.navigation.compose) 103 | // navigation new version 104 | implementation (libs.androidx.navigation.compose) 105 | implementation(libs.kotlinx.serialization.json) 106 | 107 | // network 108 | // implementation(project(":libs:network")) 109 | 110 | // Home Page 111 | implementation(project(":feature:home")) 112 | // Details Page 113 | implementation(project(":feature:details")) 114 | implementation(project(":libs:testing")) 115 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/app/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.app 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.github.app", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.app 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class App : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/navigation/AppNavHost.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.compose.NavHost 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.toRoute 8 | import com.andy.github.details.ui.DetailScreen 9 | import com.andy.github.home.ui.HomeScreen 10 | 11 | @Composable 12 | fun AppNavHost(navController: NavHostController) { 13 | NavHost( 14 | navController = navController, 15 | startDestination = Screen.Home 16 | ) { 17 | composable { 18 | HomeScreen { 19 | navController.navigate(Screen.Detail(it.name)) 20 | } 21 | } 22 | 23 | composable { backStackEntry -> 24 | val detail = backStackEntry.toRoute() 25 | DetailScreen( 26 | navController = navController, 27 | username = detail.username 28 | ) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/navigation/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.navigation 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | sealed class Screen { 7 | @Serializable 8 | data object Home : Screen() 9 | 10 | @Serializable 11 | data class Detail(val username: String): Screen() 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.navigation.compose.rememberNavController 7 | import com.github.app.navigation.AppNavHost 8 | import com.github.app.ui.theme.GitHubAppTheme 9 | import dagger.hilt.android.AndroidEntryPoint 10 | 11 | @AndroidEntryPoint 12 | class MainActivity : ComponentActivity() { 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContent { 17 | val navController = rememberNavController() 18 | GitHubAppTheme { 19 | AppNavHost(navController = navController) 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.platform.LocalContext 13 | 14 | private val DarkColorScheme = darkColorScheme( 15 | primary = Purple80, 16 | secondary = PurpleGrey80, 17 | tertiary = Pink80 18 | ) 19 | 20 | private val LightColorScheme = lightColorScheme( 21 | primary = Purple40, 22 | secondary = PurpleGrey40, 23 | tertiary = Pink40 24 | 25 | /* Other default colors to override 26 | background = Color(0xFFFFFBFE), 27 | surface = Color(0xFFFFFBFE), 28 | onPrimary = Color.White, 29 | onSecondary = Color.White, 30 | onTertiary = Color.White, 31 | onBackground = Color(0xFF1C1B1F), 32 | onSurface = Color(0xFF1C1B1F), 33 | */ 34 | ) 35 | 36 | @Composable 37 | fun GitHubAppTheme( 38 | darkTheme: Boolean = isSystemInDarkTheme(), 39 | // Dynamic color is available on Android 12+ 40 | dynamicColor: Boolean = true, 41 | content: @Composable () -> Unit 42 | ) { 43 | val colorScheme = when { 44 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 45 | val context = LocalContext.current 46 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 47 | } 48 | 49 | darkTheme -> DarkColorScheme 50 | else -> LightColorScheme 51 | } 52 | 53 | MaterialTheme( 54 | colorScheme = colorScheme, 55 | typography = Typography, 56 | content = content 57 | ) 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/app/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.github.app.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyhaha/CleanArchitectureGitHubApp/a93cd02941385de45a8d160d1b2f8d3ae0088e65/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GitHubApp 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |