├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── dependabot.yml ├── workflows │ ├── Lint.yml │ ├── Test.yml │ ├── Build.yml │ └── Publish.yml ├── FUNDING.yml └── PULL_REQUEST_TEMPLATE.md ├── .editorconfig ├── media └── FoodiumHeader.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── font │ │ │ │ └── google_sans.ttf │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── anim │ │ │ │ ├── layout_animation_right_to_left.xml │ │ │ │ └── right_to_left.xml │ │ │ ├── menu │ │ │ │ ├── detail_menu.xml │ │ │ │ └── main_menu.xml │ │ │ ├── drawable │ │ │ │ ├── ic_share.xml │ │ │ │ ├── ic_check.xml │ │ │ │ ├── splash_screen.xml │ │ │ │ ├── ic_person.xml │ │ │ │ ├── ic_photo.xml │ │ │ │ ├── ic_broken_image.xml │ │ │ │ ├── ic_lightbulb.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── values │ │ │ │ ├── bool.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── dimen.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── theme.xml │ │ │ ├── values-night │ │ │ │ ├── bool.xml │ │ │ │ └── colors.xml │ │ │ ├── transition │ │ │ │ └── change_image_transform.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── layout │ │ │ │ ├── item_post.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── content_post_details.xml │ │ │ │ └── activity_post_details.xml │ │ ├── java │ │ │ └── dev │ │ │ │ └── shreyaspatil │ │ │ │ └── foodium │ │ │ │ ├── data │ │ │ │ ├── repository │ │ │ │ │ ├── Resource.kt │ │ │ │ │ ├── PostRepository.kt │ │ │ │ │ └── NetworkBoundRepository.kt │ │ │ │ ├── remote │ │ │ │ │ └── api │ │ │ │ │ │ └── FoodiumService.kt │ │ │ │ └── local │ │ │ │ │ ├── DatabaseMigrations.kt │ │ │ │ │ ├── dao │ │ │ │ │ └── PostsDao.kt │ │ │ │ │ └── FoodiumPostsDatabase.kt │ │ │ │ ├── utils │ │ │ │ ├── ViewUtils.kt │ │ │ │ ├── TimeUtils.kt │ │ │ │ ├── ActivityUtils.kt │ │ │ │ └── NetworkUtils.kt │ │ │ │ ├── model │ │ │ │ ├── Post.kt │ │ │ │ └── State.kt │ │ │ │ ├── di │ │ │ │ └── module │ │ │ │ │ ├── FoodiumDatabaseModule.kt │ │ │ │ │ ├── PostRepositoryModule.kt │ │ │ │ │ └── FoodiumApiModule.kt │ │ │ │ ├── FoodiumApp.kt │ │ │ │ └── ui │ │ │ │ ├── base │ │ │ │ └── BaseActivity.kt │ │ │ │ ├── main │ │ │ │ ├── viewholder │ │ │ │ │ └── PostViewHolder.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── adapter │ │ │ │ │ └── PostListAdapter.kt │ │ │ │ └── MainActivity.kt │ │ │ │ └── details │ │ │ │ ├── PostDetailsViewModel.kt │ │ │ │ └── PostDetailsActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ ├── resources │ │ │ └── api-response │ │ │ │ └── posts.json │ │ └── java │ │ │ └── dev │ │ │ └── shreyaspatil │ │ │ └── foodium │ │ │ └── data │ │ │ └── remote │ │ │ └── api │ │ │ └── FoodiumServiceTest.kt │ └── androidTest │ │ └── java │ │ └── dev │ │ └── shreyaspatil │ │ └── foodium │ │ └── data │ │ └── local │ │ └── dao │ │ └── PostsDaoTest.kt ├── proguard-rules.pro ├── .gitignore ├── schemas │ ├── dev.shreyaspatil.foodium.database.FoodiumPostsDatabase │ │ └── 1.json │ └── dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase │ │ ├── 1.json │ │ └── 2.json └── build.gradle.kts ├── .gitignore ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── CONTRIBUTING.md ├── LICENSE ├── settings.gradle.kts ├── gradle.properties ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── gradlew └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @PatilShreyas 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{kt, kts}] 2 | disabled_rules = no-wildcard-imports, import-ordering -------------------------------------------------------------------------------- /media/FoodiumHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/media/FoodiumHeader.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/font/google_sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/font/google_sans.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .cxx 9 | /buildSrc/build/ 10 | /.idea/ 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rimshadpcs/Foodium/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/repository/Resource.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.foodium.data.repository 2 | 3 | sealed class Resource { 4 | class Success(val data: T) : Resource() 5 | class Failed(val message: String) : Resource() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation_right_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/resources/api-response/posts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "title": "Title 1", 5 | "body": "Body 1", 6 | "imageUrl": "Image URL 1", 7 | "author": "John Doe" 8 | }, 9 | { 10 | "id": 2, 11 | "title": "Title 2", 12 | "body": "Body 2", 13 | "imageUrl": "Image URL 2", 14 | "author": "Doe John" 15 | } 16 | ] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/Feature request template 3 | about: Issue Template for reporting bug/feature request. 4 | title: "[BUG/FEATURE REQUEST] Title" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug/request** 11 | A clear and concise description. 12 | 13 | **Additional context** 14 | Add any other context about the problem here. 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/detail_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Feeling Awesome! Thanks for thinking about this. 2 | 3 | You can contribute us by filing issues, bugs and PRs. 4 | 5 | ### Contributing: 6 | - Open issue regarding proposed change. 7 | - Repo owner will contact you there. 8 | - If your proposed change is approved, Fork this repo and do changes. 9 | - Open PR against latest `dev` branch. Add nice description in PR. 10 | - You're done! 11 | -------------------------------------------------------------------------------- /.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://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/Lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Lint Check 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout Code 12 | uses: actions/checkout@v2 13 | 14 | - name: Lint Code Base 15 | uses: docker://github/super-linter:v2.2.0 16 | env: 17 | VALIDATE_ALL_CODEBASE: true 18 | VALIDATE_MD: false 19 | VALIDATE_XML: true 20 | VALIDATE_KOTLIN: true 21 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: PatilShreyas 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.me/PatilShreyas99/'] 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/Test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Run Unit Tests 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | 12 | # Cache gradle 13 | - name: Cache Gradle and wrapper 14 | uses: actions/cache@v2 15 | with: 16 | path: | 17 | ~/.gradle/caches 18 | ~/.gradle/wrapper 19 | key: cache-${{ runner.os }}-${{ matrix.jdk }}-gradle-${{ hashFiles('**/*.gradle*') }} 20 | restore-keys: | 21 | ${{ runner.os }}-gradle- 22 | 23 | - name: Validate Gradle Wrapper 24 | uses: gradle/wrapper-validation-action@v1 25 | 26 | - name: Run Unit tests 27 | run: ./gradlew test --stacktrace 28 | -------------------------------------------------------------------------------- /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.kts. 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 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | **- Summary** 15 | 16 | 20 | 21 | **- Description for the changelog** 22 | 23 | 27 | 28 | **- A picture or screenshot regarding change (not mandatory but encouraged)** 29 | -------------------------------------------------------------------------------- /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build_apk: 6 | name: Generate APK 7 | runs-on: ubuntu-18.04 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Set up JDK 12 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: 12 15 | 16 | # Cache gradle 17 | - name: Cache Gradle and wrapper 18 | uses: actions/cache@v2 19 | with: 20 | path: | 21 | ~/.gradle/caches 22 | ~/.gradle/wrapper 23 | key: cache-${{ runner.os }}-${{ matrix.jdk }}-gradle-${{ hashFiles('**/*.gradle*') }} 24 | restore-keys: | 25 | ${{ runner.os }}-gradle- 26 | 27 | - name: Grant Permission to Execute 28 | run: chmod +x gradlew 29 | 30 | - name: Build debug APK 31 | run: bash ./gradlew assembleDebug --stacktrace 32 | - name: Upload APK 33 | uses: actions/upload-artifact@v1 34 | with: 35 | name: app 36 | path: app/build/outputs/apk/debug/app-debug.apk 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shreyas Patil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/Publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | name: Publish APK 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: set up JDK 1.8 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 1.8 19 | 20 | # Cache gradle 21 | - name: Cache Gradle and wrapper 22 | uses: actions/cache@v2 23 | with: 24 | path: | 25 | ~/.gradle/caches 26 | ~/.gradle/wrapper 27 | key: cache-${{ runner.os }}-${{ matrix.jdk }}-gradle-${{ hashFiles('**/*.gradle*') }} 28 | restore-keys: | 29 | ${{ runner.os }}-gradle- 30 | 31 | - name: Grant Permission to Execute 32 | run: chmod +x gradlew 33 | 34 | - name: Build debug APK 35 | run: bash ./gradlew assembleDebug --stacktrace 36 | 37 | - name: Upload APK to release 38 | uses: svenstaro/upload-release-action@v1-release 39 | with: 40 | repo_token: ${{ secrets.GITHUB_TOKEN }} 41 | file: app/build/outputs/apk/debug/app-debug.apk 42 | asset_name: app.apk 43 | tag: ${{ github.ref }} 44 | overwrite: true 45 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | rootProject.name = "Foodium" 26 | include(":app") 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | true 28 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | false 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | #FFFFFF 28 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 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 | 40 | # Keystore files 41 | # Uncomment the following lines if you do not want to check your keystore files in. 42 | #*.jks 43 | #*.keystore 44 | 45 | # External native build folder generated in Android Studio 2.2 and later 46 | .externalNativeBuild 47 | .cxx/ 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | # google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | 57 | # fastlane 58 | fastlane/report.xml 59 | fastlane/Preview.html 60 | fastlane/screenshots 61 | fastlane/test_output 62 | fastlane/readme.md 63 | 64 | # Version control 65 | vcs.xml 66 | 67 | # lint 68 | lint/intermediates/ 69 | lint/generated/ 70 | lint/outputs/ 71 | lint/tmp/ 72 | # lint/reports/ 73 | 74 | 75 | .DS_Store 76 | /captures 77 | -------------------------------------------------------------------------------- /app/src/main/res/transition/change_image_transform.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/utils/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.utils 26 | 27 | import android.view.View 28 | 29 | fun View.show() { 30 | visibility = View.VISIBLE 31 | } 32 | 33 | fun View.hide() { 34 | visibility = View.GONE 35 | } 36 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2020 Shreyas Patil 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | #Thu Mar 19 18:38:55 IST 2020 26 | distributionBase=GRADLE_USER_HOME 27 | distributionPath=wrapper/dists 28 | zipStoreBase=GRADLE_USER_HOME 29 | zipStorePath=wrapper/dists 30 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 31 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 1dp 28 | 4dp 29 | 8dp 30 | 16dp 31 | 32 | 192dp 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 28 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/utils/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.utils 26 | 27 | import java.util.* 28 | 29 | /** 30 | * Returns [Boolean] based on current time. 31 | * Returns true if hours are between 06:00 pm - 07:00 am 32 | */ 33 | fun isNight(): Boolean { 34 | val currentHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) 35 | return (currentHour <= 7 || currentHour >= 18) 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/schemas/dev.shreyaspatil.foodium.database.FoodiumPostsDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "a1cad22e3cf35858259e73b9e14f94de", 6 | "entities": [ 7 | { 8 | "tableName": "foodium_posts", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `title` TEXT, `author` TEXT, `imageUrl` TEXT, PRIMARY KEY(`id`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "title", 19 | "columnName": "title", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "author", 25 | "columnName": "author", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "imageUrl", 31 | "columnName": "imageUrl", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | } 35 | ], 36 | "primaryKey": { 37 | "columnNames": [ 38 | "id" 39 | ], 40 | "autoGenerate": false 41 | }, 42 | "indices": [], 43 | "foreignKeys": [] 44 | } 45 | ], 46 | "views": [], 47 | "setupQueries": [ 48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a1cad22e3cf35858259e73b9e14f94de')" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | #002022 28 | #001719 29 | #002022 30 | 31 | @android:color/white 32 | @android:color/white 33 | 34 | #0DFFFFFF 35 | 36 | #006064 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_photo.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | Foodium 27 | No Connection 28 | Back Online 29 | Post Header 30 | Are you sure want to exit? 31 | Exit? 32 | Yes 33 | No 34 | \"%s\" by %s. \nVisit: https://github.com/PatilShreyas/Foodium 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/model/Post.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.model 26 | 27 | import androidx.room.Entity 28 | import androidx.room.PrimaryKey 29 | import dev.shreyaspatil.foodium.model.Post.Companion.TABLE_NAME 30 | 31 | /** 32 | * Data class for Database entity and Serialization. 33 | */ 34 | @Entity(tableName = TABLE_NAME) 35 | data class Post( 36 | 37 | @PrimaryKey 38 | var id: Int? = 0, 39 | var title: String? = null, 40 | var author: String? = null, 41 | var body: String? = null, 42 | var imageUrl: String? = null 43 | ) { 44 | companion object { 45 | const val TABLE_NAME = "foodium_posts" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/schemas/dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "a96af16b48a8aeb2062facb317b31080", 6 | "entities": [ 7 | { 8 | "tableName": "foodium_posts", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `title` TEXT, `author` TEXT, `body` TEXT, `imageUrl` TEXT, PRIMARY KEY(`id`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "title", 19 | "columnName": "title", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "author", 25 | "columnName": "author", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "body", 31 | "columnName": "body", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "imageUrl", 37 | "columnName": "imageUrl", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "id" 45 | ], 46 | "autoGenerate": false 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a96af16b48a8aeb2062facb317b31080')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "a96af16b48a8aeb2062facb317b31080", 6 | "entities": [ 7 | { 8 | "tableName": "foodium_posts", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `title` TEXT, `author` TEXT, `body` TEXT, `imageUrl` TEXT, PRIMARY KEY(`id`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "title", 19 | "columnName": "title", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "author", 25 | "columnName": "author", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "body", 31 | "columnName": "body", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "imageUrl", 37 | "columnName": "imageUrl", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "id" 45 | ], 46 | "autoGenerate": false 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a96af16b48a8aeb2062facb317b31080')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/remote/api/FoodiumService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.remote.api 26 | 27 | import dev.shreyaspatil.foodium.data.remote.api.FoodiumService.Companion.FOODIUM_API_URL 28 | import dev.shreyaspatil.foodium.model.Post 29 | import retrofit2.Response 30 | import retrofit2.http.GET 31 | 32 | /** 33 | * Service to fetch Foodium posts using dummy end point [FOODIUM_API_URL]. 34 | */ 35 | interface FoodiumService { 36 | 37 | @GET("/DummyFoodiumApi/api/posts/") 38 | suspend fun getPosts(): Response> 39 | 40 | companion object { 41 | const val FOODIUM_API_URL = "https://patilshreyas.github.io/" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lightbulb.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/local/DatabaseMigrations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.local 26 | 27 | import androidx.room.migration.Migration 28 | import androidx.sqlite.db.SupportSQLiteDatabase 29 | import dev.shreyaspatil.foodium.model.Post 30 | 31 | object DatabaseMigrations { 32 | const val DB_VERSION = 2 33 | 34 | val MIGRATIONS: Array 35 | get() = arrayOf( 36 | migration12() 37 | ) 38 | 39 | private fun migration12(): Migration = object : Migration(1, 2) { 40 | override fun migrate(database: SupportSQLiteDatabase) { 41 | database.execSQL("ALTER TABLE ${Post.TABLE_NAME} ADD COLUMN body TEXT") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/di/module/FoodiumDatabaseModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.di.module 26 | 27 | import android.app.Application 28 | import dagger.Module 29 | import dagger.Provides 30 | import dagger.hilt.InstallIn 31 | import dagger.hilt.components.SingletonComponent 32 | import dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase 33 | import javax.inject.Singleton 34 | 35 | @InstallIn(SingletonComponent::class) 36 | @Module 37 | class FoodiumDatabaseModule { 38 | 39 | @Singleton 40 | @Provides 41 | fun provideDatabase(application: Application) = FoodiumPostsDatabase.getInstance(application) 42 | 43 | @Singleton 44 | @Provides 45 | fun providePostsDao(database: FoodiumPostsDatabase) = database.getPostsDao() 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/FoodiumApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium 26 | 27 | import android.app.Application 28 | import androidx.appcompat.app.AppCompatDelegate 29 | import dagger.hilt.android.HiltAndroidApp 30 | import dev.shreyaspatil.foodium.utils.isNight 31 | import kotlinx.coroutines.ExperimentalCoroutinesApi 32 | 33 | @ExperimentalCoroutinesApi 34 | @HiltAndroidApp 35 | class FoodiumApp : Application() { 36 | 37 | override fun onCreate() { 38 | super.onCreate() 39 | 40 | // Get UI mode and set 41 | val mode = if (isNight()) { 42 | AppCompatDelegate.MODE_NIGHT_YES 43 | } else { 44 | AppCompatDelegate.MODE_NIGHT_NO 45 | } 46 | 47 | AppCompatDelegate.setDefaultNightMode(mode) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | @android:color/white 28 | @android:color/black 29 | #F3F3F3 30 | #00838F 31 | @android:color/white 32 | 33 | @android:color/black 34 | @android:color/black 35 | @android:color/white 36 | 37 | #C3C3C3 38 | 39 | #43A047 40 | #D32F2F 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.base 26 | 27 | import android.os.Bundle 28 | import androidx.appcompat.app.AppCompatActivity 29 | import androidx.lifecycle.ViewModel 30 | import androidx.viewbinding.ViewBinding 31 | 32 | /** 33 | * Abstract Activity which binds [ViewModel] [VM] and [ViewBinding] [VB] 34 | */ 35 | abstract class BaseActivity : AppCompatActivity() { 36 | 37 | protected abstract val mViewModel: VM 38 | 39 | protected lateinit var mViewBinding: VB 40 | 41 | override fun onCreate(savedInstanceState: Bundle?) { 42 | super.onCreate(savedInstanceState) 43 | 44 | mViewBinding = getViewBinding() 45 | } 46 | 47 | /** 48 | * It returns [VB] which is assigned to [mViewBinding] and used in [onCreate] 49 | */ 50 | abstract fun getViewBinding(): VB 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/di/module/PostRepositoryModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.di.module 26 | 27 | import dagger.Binds 28 | import dagger.Module 29 | import dagger.hilt.InstallIn 30 | import dagger.hilt.android.components.ActivityRetainedComponent 31 | import dagger.hilt.android.scopes.ActivityRetainedScoped 32 | import dev.shreyaspatil.foodium.data.repository.DefaultPostRepository 33 | import dev.shreyaspatil.foodium.data.repository.PostRepository 34 | import kotlinx.coroutines.ExperimentalCoroutinesApi 35 | 36 | /** 37 | * Currently PostRepository is only used in ViewModels. 38 | * PostDetailsViewModel is not injected using @HiltViewModel so can't install in ViewModelComponent. 39 | */ 40 | @ExperimentalCoroutinesApi 41 | @InstallIn(ActivityRetainedComponent::class) 42 | @Module 43 | abstract class PostRepositoryModule { 44 | 45 | @ActivityRetainedScoped 46 | @Binds 47 | abstract fun bindPostRepository(repository: DefaultPostRepository): PostRepository 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/utils/ActivityUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.utils 26 | 27 | import android.app.Activity 28 | import android.widget.Toast 29 | import androidx.annotation.ColorRes 30 | import androidx.appcompat.app.AppCompatActivity 31 | import androidx.core.content.ContextCompat 32 | import androidx.lifecycle.ViewModel 33 | import androidx.lifecycle.ViewModelProvider 34 | 35 | /** 36 | * Can show [Toast] from every [Activity]. 37 | */ 38 | fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) { 39 | Toast.makeText(applicationContext, message, duration).show() 40 | } 41 | 42 | /** 43 | * Returns Color from resource. 44 | * @param id Color Resource ID 45 | */ 46 | fun Activity.getColorRes(@ColorRes id: Int) = ContextCompat.getColor(applicationContext, id) 47 | 48 | /** 49 | * Provides [ViewModel] of type [VM] from [factory]. 50 | */ 51 | inline fun AppCompatActivity.viewModelOf( 52 | factory: ViewModelProvider.Factory 53 | ) = ViewModelProvider(this, factory).get(VM::class.java) 54 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/di/module/FoodiumApiModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.di.module 26 | 27 | import com.squareup.moshi.Moshi 28 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 29 | import dagger.Module 30 | import dagger.Provides 31 | import dagger.hilt.InstallIn 32 | import dagger.hilt.components.SingletonComponent 33 | import dev.shreyaspatil.foodium.data.remote.api.FoodiumService 34 | import retrofit2.Retrofit 35 | import retrofit2.converter.moshi.MoshiConverterFactory 36 | import javax.inject.Singleton 37 | 38 | @InstallIn(SingletonComponent::class) 39 | @Module 40 | class FoodiumApiModule { 41 | 42 | @Singleton 43 | @Provides 44 | fun provideRetrofitService(): FoodiumService = Retrofit.Builder() 45 | .baseUrl(FoodiumService.FOODIUM_API_URL) 46 | .addConverterFactory( 47 | MoshiConverterFactory.create( 48 | Moshi.Builder().add(KotlinJsonAdapterFactory()).build() 49 | ) 50 | ) 51 | .build() 52 | .create(FoodiumService::class.java) 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/main/viewholder/PostViewHolder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.main.viewholder 26 | 27 | import android.widget.ImageView 28 | import androidx.recyclerview.widget.RecyclerView 29 | import coil.load 30 | import dev.shreyaspatil.foodium.R 31 | import dev.shreyaspatil.foodium.databinding.ItemPostBinding 32 | import dev.shreyaspatil.foodium.model.Post 33 | import dev.shreyaspatil.foodium.ui.main.adapter.PostListAdapter 34 | 35 | /** 36 | * [RecyclerView.ViewHolder] implementation to inflate View for RecyclerView. 37 | * See [PostListAdapter]] 38 | */ 39 | class PostViewHolder(private val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root) { 40 | 41 | fun bind(post: Post, onItemClicked: (Post, ImageView) -> Unit) { 42 | binding.postTitle.text = post.title 43 | binding.postAuthor.text = post.author 44 | binding.imageView.load(post.imageUrl) { 45 | placeholder(R.drawable.ic_photo) 46 | error(R.drawable.ic_broken_image) 47 | } 48 | 49 | binding.root.setOnClickListener { 50 | onItemClicked(post, binding.imageView) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2020 Shreyas Patil 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | # Project-wide Gradle settings. 26 | # IDE (e.g. Android Studio) users: 27 | # Gradle settings configured through the IDE *will override* 28 | # any settings specified in this file. 29 | # For more details on how to configure your build environment visit 30 | # http://www.gradle.org/docs/current/userguide/build_environment.html 31 | # Specifies the JVM arguments used for the daemon process. 32 | # The setting is particularly useful for tweaking memory settings. 33 | org.gradle.jvmargs=-Xmx1536m 34 | # When configured, Gradle will run in incubating parallel mode. 35 | # This option should only be used with decoupled projects. More details, visit 36 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 37 | # org.gradle.parallel=true 38 | # AndroidX package structure to make it clearer which packages are bundled with the 39 | # Android operating system, and which are packaged with your app's APK 40 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 41 | android.useAndroidX=true 42 | # Automatically convert third-party libraries to use AndroidX 43 | android.enableJetifier=true 44 | # Kotlin code style for this project: "official" or "obsolete": 45 | kotlin.code.style=official 46 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.main 26 | 27 | import androidx.lifecycle.ViewModel 28 | import androidx.lifecycle.viewModelScope 29 | import dagger.hilt.android.lifecycle.HiltViewModel 30 | import dev.shreyaspatil.foodium.data.repository.PostRepository 31 | import dev.shreyaspatil.foodium.model.Post 32 | import dev.shreyaspatil.foodium.model.State 33 | import kotlinx.coroutines.ExperimentalCoroutinesApi 34 | import kotlinx.coroutines.flow.* 35 | import kotlinx.coroutines.launch 36 | import javax.inject.Inject 37 | 38 | /** 39 | * ViewModel for [MainActivity] 40 | */ 41 | @ExperimentalCoroutinesApi 42 | @HiltViewModel 43 | class MainViewModel @Inject constructor(private val postRepository: PostRepository) : 44 | ViewModel() { 45 | 46 | private val _posts: MutableStateFlow>> = MutableStateFlow(State.loading()) 47 | 48 | val posts: StateFlow>> = _posts 49 | 50 | fun getPosts() { 51 | viewModelScope.launch { 52 | postRepository.getAllPosts() 53 | .map { resource -> State.fromResource(resource) } 54 | .collect { state -> _posts.value = state } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/details/PostDetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.details 26 | 27 | import androidx.lifecycle.ViewModel 28 | import androidx.lifecycle.ViewModelProvider 29 | import androidx.lifecycle.asLiveData 30 | import dagger.assisted.Assisted 31 | import dagger.assisted.AssistedFactory 32 | import dagger.assisted.AssistedInject 33 | import dev.shreyaspatil.foodium.data.repository.PostRepository 34 | import kotlinx.coroutines.ExperimentalCoroutinesApi 35 | 36 | /** 37 | * ViewModel for [PostDetailsActivity] 38 | */ 39 | @ExperimentalCoroutinesApi 40 | class PostDetailsViewModel @AssistedInject constructor( 41 | postRepository: PostRepository, 42 | @Assisted postId: Int 43 | ) : ViewModel() { 44 | 45 | val post = postRepository.getPostById(postId).asLiveData() 46 | 47 | @AssistedFactory 48 | interface PostDetailsViewModelFactory { 49 | fun create(postId: Int): PostDetailsViewModel 50 | } 51 | 52 | @Suppress("UNCHECKED_CAST") 53 | companion object { 54 | fun provideFactory( 55 | assistedFactory: PostDetailsViewModelFactory, 56 | postId: Int 57 | ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { 58 | override fun create(modelClass: Class): T { 59 | return assistedFactory.create(postId) as T 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/model/State.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.model 26 | 27 | import dev.shreyaspatil.foodium.data.repository.Resource 28 | 29 | /** 30 | * State Management for UI & Data. 31 | */ 32 | sealed class State { 33 | class Loading : State() 34 | 35 | data class Success(val data: T) : State() 36 | 37 | data class Error(val message: String) : State() 38 | 39 | fun isLoading(): Boolean = this is Loading 40 | 41 | fun isSuccessful(): Boolean = this is Success 42 | 43 | fun isFailed(): Boolean = this is Error 44 | 45 | companion object { 46 | 47 | /** 48 | * Returns [State.Loading] instance. 49 | */ 50 | fun loading() = Loading() 51 | 52 | /** 53 | * Returns [State.Success] instance. 54 | * @param data Data to emit with status. 55 | */ 56 | fun success(data: T) = 57 | Success(data) 58 | 59 | /** 60 | * Returns [State.Error] instance. 61 | * @param message Description of failure. 62 | */ 63 | fun error(message: String) = 64 | Error(message) 65 | 66 | /** 67 | * Returns [State] from [Resource] 68 | */ 69 | fun fromResource(resource: Resource): State = when (resource) { 70 | is Resource.Success -> success(resource.data) 71 | is Resource.Failed -> error(resource.message) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/local/dao/PostsDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.local.dao 26 | 27 | import androidx.room.Dao 28 | import androidx.room.Insert 29 | import androidx.room.OnConflictStrategy 30 | import androidx.room.Query 31 | import dev.shreyaspatil.foodium.model.Post 32 | import kotlinx.coroutines.flow.Flow 33 | 34 | /** 35 | * Data Access Object (DAO) for [dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase] 36 | */ 37 | @Dao 38 | interface PostsDao { 39 | 40 | /** 41 | * Inserts [posts] into the [Post.TABLE_NAME] table. 42 | * Duplicate values are replaced in the table. 43 | * @param posts Posts 44 | */ 45 | @Insert(onConflict = OnConflictStrategy.REPLACE) 46 | suspend fun addPosts(posts: List) 47 | 48 | /** 49 | * Deletes all the posts from the [Post.TABLE_NAME] table. 50 | */ 51 | @Query("DELETE FROM ${Post.TABLE_NAME}") 52 | suspend fun deleteAllPosts() 53 | 54 | /** 55 | * Fetches the post from the [Post.TABLE_NAME] table whose id is [postId]. 56 | * @param postId Unique ID of [Post] 57 | * @return [Flow] of [Post] from database table. 58 | */ 59 | @Query("SELECT * FROM ${Post.TABLE_NAME} WHERE ID = :postId") 60 | fun getPostById(postId: Int): Flow 61 | 62 | /** 63 | * Fetches all the posts from the [Post.TABLE_NAME] table. 64 | * @return [Flow] 65 | */ 66 | @Query("SELECT * FROM ${Post.TABLE_NAME}") 67 | fun getAllPosts(): Flow> 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/local/FoodiumPostsDatabase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.local 26 | 27 | import android.content.Context 28 | import androidx.room.Database 29 | import androidx.room.Room 30 | import androidx.room.RoomDatabase 31 | import dev.shreyaspatil.foodium.data.local.dao.PostsDao 32 | import dev.shreyaspatil.foodium.model.Post 33 | 34 | /** 35 | * Abstract Foodium database. 36 | * It provides DAO [PostsDao] by using method [getPostsDao]. 37 | */ 38 | @Database( 39 | entities = [Post::class], 40 | version = DatabaseMigrations.DB_VERSION 41 | ) 42 | abstract class FoodiumPostsDatabase : RoomDatabase() { 43 | 44 | /** 45 | * @return [PostsDao] Foodium Posts Data Access Object. 46 | */ 47 | abstract fun getPostsDao(): PostsDao 48 | 49 | companion object { 50 | const val DB_NAME = "foodium_database" 51 | 52 | @Volatile 53 | private var INSTANCE: FoodiumPostsDatabase? = null 54 | 55 | fun getInstance(context: Context): FoodiumPostsDatabase { 56 | val tempInstance = INSTANCE 57 | if (tempInstance != null) { 58 | return tempInstance 59 | } 60 | 61 | synchronized(this) { 62 | val instance = Room.databaseBuilder( 63 | context.applicationContext, 64 | FoodiumPostsDatabase::class.java, 65 | DB_NAME 66 | ).addMigrations(*DatabaseMigrations.MIGRATIONS).build() 67 | 68 | INSTANCE = instance 69 | return instance 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/main/adapter/PostListAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.main.adapter 26 | 27 | import android.view.LayoutInflater 28 | import android.view.ViewGroup 29 | import android.widget.ImageView 30 | import androidx.recyclerview.widget.DiffUtil 31 | import androidx.recyclerview.widget.ListAdapter 32 | import androidx.recyclerview.widget.RecyclerView 33 | import dev.shreyaspatil.foodium.databinding.ItemPostBinding 34 | import dev.shreyaspatil.foodium.model.Post 35 | import dev.shreyaspatil.foodium.ui.main.viewholder.PostViewHolder 36 | 37 | /** 38 | * Adapter class [RecyclerView.Adapter] for [RecyclerView] which binds [Post] along with [PostViewHolder] 39 | * @param onItemClicked which will receive callback when item is clicked. 40 | */ 41 | class PostListAdapter( 42 | private val onItemClicked: (Post, ImageView) -> Unit 43 | ) : ListAdapter(DIFF_CALLBACK) { 44 | 45 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PostViewHolder( 46 | ItemPostBinding.inflate( 47 | LayoutInflater.from(parent.context), 48 | parent, 49 | false 50 | ) 51 | ) 52 | 53 | override fun onBindViewHolder(holder: PostViewHolder, position: Int) = 54 | holder.bind(getItem(position), onItemClicked) 55 | 56 | companion object { 57 | private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { 58 | override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean = 59 | oldItem.id == newItem.id 60 | 61 | override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean = 62 | oldItem == newItem 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | 32 | 33 | 39 | 42 | 45 | 46 | 47 | 48 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_post.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 35 | 36 | 45 | 46 | 54 | 55 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/repository/PostRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.repository 26 | 27 | import androidx.annotation.MainThread 28 | import dev.shreyaspatil.foodium.data.local.dao.PostsDao 29 | import dev.shreyaspatil.foodium.data.remote.api.FoodiumService 30 | import dev.shreyaspatil.foodium.model.Post 31 | import kotlinx.coroutines.ExperimentalCoroutinesApi 32 | import kotlinx.coroutines.flow.Flow 33 | import kotlinx.coroutines.flow.distinctUntilChanged 34 | import retrofit2.Response 35 | import javax.inject.Inject 36 | 37 | interface PostRepository { 38 | fun getAllPosts(): Flow>> 39 | fun getPostById(postId: Int): Flow 40 | } 41 | 42 | /** 43 | * Singleton repository for fetching data from remote and storing it in database 44 | * for offline capability. This is Single source of data. 45 | */ 46 | @ExperimentalCoroutinesApi 47 | class DefaultPostRepository @Inject constructor( 48 | private val postsDao: PostsDao, 49 | private val foodiumService: FoodiumService 50 | ) : PostRepository { 51 | 52 | /** 53 | * Fetched the posts from network and stored it in database. At the end, data from persistence 54 | * storage is fetched and emitted. 55 | */ 56 | override fun getAllPosts(): Flow>> { 57 | return object : NetworkBoundRepository, List>() { 58 | 59 | override suspend fun saveRemoteData(response: List) = postsDao.addPosts(response) 60 | 61 | override fun fetchFromLocal(): Flow> = postsDao.getAllPosts() 62 | 63 | override suspend fun fetchFromRemote(): Response> = foodiumService.getPosts() 64 | }.asFlow() 65 | } 66 | 67 | /** 68 | * Retrieves a post with specified [postId]. 69 | * @param postId Unique id of a [Post]. 70 | * @return [Post] data fetched from the database. 71 | */ 72 | @MainThread 73 | override fun getPostById(postId: Int): Flow = postsDao.getPostById(postId).distinctUntilChanged() 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/utils/NetworkUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.utils 26 | 27 | import android.content.Context 28 | import android.net.ConnectivityManager 29 | import android.net.Network 30 | import android.net.NetworkCapabilities 31 | import android.net.NetworkRequest 32 | import android.os.Build 33 | import androidx.lifecycle.LiveData 34 | import androidx.lifecycle.MutableLiveData 35 | 36 | /** 37 | * Network Utility to detect availability or unavailability of Internet connection 38 | */ 39 | object NetworkUtils : ConnectivityManager.NetworkCallback() { 40 | 41 | private val networkLiveData: MutableLiveData = MutableLiveData() 42 | 43 | /** 44 | * Returns instance of [LiveData] which can be observed for network changes. 45 | */ 46 | fun getNetworkLiveData(context: Context): LiveData { 47 | val connectivityManager = 48 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 49 | 50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 51 | connectivityManager.registerDefaultNetworkCallback(this) 52 | } else { 53 | val builder = NetworkRequest.Builder() 54 | connectivityManager.registerNetworkCallback(builder.build(), this) 55 | } 56 | 57 | var isConnected = false 58 | 59 | // Retrieve current status of connectivity 60 | connectivityManager.allNetworks.forEach { network -> 61 | val networkCapability = connectivityManager.getNetworkCapabilities(network) 62 | 63 | networkCapability?.let { 64 | if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { 65 | isConnected = true 66 | return@forEach 67 | } 68 | } 69 | } 70 | 71 | networkLiveData.postValue(isConnected) 72 | 73 | return networkLiveData 74 | } 75 | 76 | override fun onAvailable(network: Network) { 77 | networkLiveData.postValue(true) 78 | } 79 | 80 | override fun onLost(network: Network) { 81 | networkLiveData.postValue(false) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/shreyaspatil/foodium/data/local/dao/PostsDaoTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.local.dao 26 | 27 | import androidx.room.Room 28 | import androidx.test.core.app.ApplicationProvider 29 | import androidx.test.ext.junit.runners.AndroidJUnit4 30 | import dev.shreyaspatil.foodium.data.local.FoodiumPostsDatabase 31 | import dev.shreyaspatil.foodium.model.Post 32 | import kotlinx.coroutines.flow.first 33 | import kotlinx.coroutines.runBlocking 34 | import org.hamcrest.CoreMatchers.equalTo 35 | import org.hamcrest.MatcherAssert.assertThat 36 | import org.junit.After 37 | import org.junit.Before 38 | import org.junit.Test 39 | import org.junit.runner.RunWith 40 | 41 | @RunWith(AndroidJUnit4::class) 42 | class PostsDaoTest { 43 | 44 | private lateinit var mDatabase: FoodiumPostsDatabase 45 | 46 | @Before 47 | fun init() { 48 | mDatabase = Room.inMemoryDatabaseBuilder( 49 | ApplicationProvider.getApplicationContext(), 50 | FoodiumPostsDatabase::class.java 51 | ).build() 52 | } 53 | 54 | @Test 55 | @Throws(InterruptedException::class) 56 | fun insert_and_select_posts() = runBlocking { 57 | val posts = listOf( 58 | Post(1, "Test 1", "Test 1", "Test 1"), 59 | Post(2, "Test 2", "Test 2", "Test 3") 60 | ) 61 | 62 | mDatabase.getPostsDao().addPosts(posts) 63 | 64 | val dbPosts = mDatabase.getPostsDao().getAllPosts().first() 65 | 66 | assertThat(dbPosts, equalTo(posts)) 67 | } 68 | 69 | @Test 70 | @Throws(InterruptedException::class) 71 | fun select_post_by_id() = runBlocking { 72 | val posts = listOf( 73 | Post(1, "Test 1", "Test 1", "Test 1"), 74 | Post(2, "Test 2", "Test 2", "Test 3") 75 | ) 76 | 77 | mDatabase.getPostsDao().addPosts(posts) 78 | 79 | var dbPost = mDatabase.getPostsDao().getPostById(1).first() 80 | assertThat(dbPost, equalTo(posts[0])) 81 | 82 | dbPost = mDatabase.getPostsDao().getPostById(2).first() 83 | assertThat(dbPost, equalTo(posts[1])) 84 | } 85 | 86 | @After 87 | fun cleanup() { 88 | mDatabase.close() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/data/repository/NetworkBoundRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.repository 26 | 27 | import androidx.annotation.MainThread 28 | import androidx.annotation.WorkerThread 29 | import kotlinx.coroutines.ExperimentalCoroutinesApi 30 | import kotlinx.coroutines.flow.* 31 | import retrofit2.Response 32 | 33 | /** 34 | * A repository which provides resource from local database as well as remote end point. 35 | * 36 | * [RESULT] represents the type for database. 37 | * [REQUEST] represents the type for network. 38 | */ 39 | @ExperimentalCoroutinesApi 40 | abstract class NetworkBoundRepository { 41 | 42 | fun asFlow() = flow> { 43 | 44 | // Emit Database content first 45 | emit(Resource.Success(fetchFromLocal().first())) 46 | 47 | // Fetch latest posts from remote 48 | val apiResponse = fetchFromRemote() 49 | 50 | // Parse body 51 | val remotePosts = apiResponse.body() 52 | 53 | // Check for response validation 54 | if (apiResponse.isSuccessful && remotePosts != null) { 55 | // Save posts into the persistence storage 56 | saveRemoteData(remotePosts) 57 | } else { 58 | // Something went wrong! Emit Error state. 59 | emit(Resource.Failed(apiResponse.message())) 60 | } 61 | 62 | // Retrieve posts from persistence storage and emit 63 | emitAll( 64 | fetchFromLocal().map { 65 | Resource.Success(it) 66 | } 67 | ) 68 | }.catch { e -> 69 | e.printStackTrace() 70 | emit(Resource.Failed("Network error! Can't get latest posts.")) 71 | } 72 | 73 | /** 74 | * Saves retrieved from remote into the persistence storage. 75 | */ 76 | @WorkerThread 77 | protected abstract suspend fun saveRemoteData(response: REQUEST) 78 | 79 | /** 80 | * Retrieves all data from persistence storage. 81 | */ 82 | @MainThread 83 | protected abstract fun fetchFromLocal(): Flow 84 | 85 | /** 86 | * Fetches [Response] from the remote end point. 87 | */ 88 | @MainThread 89 | protected abstract suspend fun fetchFromRemote(): Response 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | 32 | 37 | 38 | 47 | 48 | 54 | 55 | 56 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_post_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 33 | 34 | 38 | 39 | 49 | 50 | 60 | 61 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at shreyaspatilg@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /app/src/test/java/dev/shreyaspatil/foodium/data/remote/api/FoodiumServiceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.data.remote.api 26 | 27 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 28 | import com.google.common.truth.Truth.assertThat 29 | import com.squareup.moshi.Moshi 30 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 31 | import kotlinx.coroutines.runBlocking 32 | import okhttp3.mockwebserver.MockResponse 33 | import okhttp3.mockwebserver.MockWebServer 34 | import okio.buffer 35 | import okio.source 36 | import org.junit.After 37 | import org.junit.Before 38 | import org.junit.Rule 39 | import org.junit.Test 40 | import org.junit.runner.RunWith 41 | import org.junit.runners.JUnit4 42 | import retrofit2.Retrofit 43 | import retrofit2.converter.moshi.MoshiConverterFactory 44 | 45 | @RunWith(JUnit4::class) 46 | class FoodiumServiceTest { 47 | @Rule 48 | @JvmField 49 | val instantExecutorRule = InstantTaskExecutorRule() 50 | 51 | private lateinit var service: FoodiumService 52 | 53 | private lateinit var mockWebServer: MockWebServer 54 | 55 | @Before 56 | fun createService() { 57 | mockWebServer = MockWebServer() 58 | service = Retrofit.Builder() 59 | .baseUrl(mockWebServer.url("/")) 60 | .addConverterFactory( 61 | MoshiConverterFactory.create( 62 | Moshi.Builder().add(KotlinJsonAdapterFactory()).build() 63 | ) 64 | ) 65 | .build() 66 | .create(FoodiumService::class.java) 67 | } 68 | 69 | @After 70 | fun stopService() { 71 | mockWebServer.shutdown() 72 | } 73 | 74 | @Test 75 | fun getPostsTest() = runBlocking { 76 | enqueueResponse("posts.json") 77 | val posts = service.getPosts().body() 78 | 79 | assertThat(posts).isNotNull() 80 | assertThat(posts!!.size).isEqualTo(2) 81 | assertThat(posts[0].title).isEqualTo("Title 1") 82 | } 83 | 84 | private fun enqueueResponse(fileName: String, headers: Map = emptyMap()) { 85 | val inputStream = javaClass.classLoader!! 86 | .getResourceAsStream("api-response/$fileName") 87 | val source = inputStream.source().buffer() 88 | val mockResponse = MockResponse() 89 | for ((key, value) in headers) { 90 | mockResponse.addHeader(key, value) 91 | } 92 | mockWebServer.enqueue( 93 | mockResponse 94 | .setBody(source.readString(Charsets.UTF_8)) 95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_post_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 32 | 33 | 39 | 40 | 51 | 52 | 63 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 35 | 36 | 40 | 41 | 47 | 48 | 52 | 53 | 58 | 59 | 64 | 65 | 68 | 69 | 74 | 75 | 82 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/values/theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 52 | 53 | 54 | 72 | 73 | 74 | 77 | 78 | 81 | 82 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/details/PostDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.details 26 | 27 | import android.content.Context 28 | import android.content.Intent 29 | import android.os.Bundle 30 | import android.view.Menu 31 | import android.view.MenuItem 32 | import androidx.activity.viewModels 33 | import androidx.core.app.ShareCompat 34 | import coil.load 35 | import dagger.hilt.android.AndroidEntryPoint 36 | import dev.shreyaspatil.foodium.R 37 | import dev.shreyaspatil.foodium.databinding.ActivityPostDetailsBinding 38 | import dev.shreyaspatil.foodium.ui.base.BaseActivity 39 | import kotlinx.coroutines.ExperimentalCoroutinesApi 40 | import javax.inject.Inject 41 | 42 | @ExperimentalCoroutinesApi 43 | @AndroidEntryPoint 44 | class PostDetailsActivity : BaseActivity() { 45 | 46 | @Inject 47 | lateinit var viewModelFactory: PostDetailsViewModel.PostDetailsViewModelFactory 48 | 49 | override val mViewModel: PostDetailsViewModel by viewModels { 50 | val postId = intent.extras?.getInt(KEY_POST_ID) 51 | ?: throw IllegalArgumentException("`postId` must be non-null") 52 | 53 | PostDetailsViewModel.provideFactory(viewModelFactory, postId) 54 | } 55 | 56 | override fun onCreate(savedInstanceState: Bundle?) { 57 | super.onCreate(savedInstanceState) 58 | setContentView(mViewBinding.root) 59 | 60 | setSupportActionBar(mViewBinding.toolbar) 61 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 62 | } 63 | 64 | override fun onStart() { 65 | super.onStart() 66 | initPost() 67 | } 68 | 69 | private fun initPost() { 70 | mViewModel.post.observe(this) { post -> 71 | mViewBinding.postContent.apply { 72 | postTitle.text = post.title 73 | postAuthor.text = post.author 74 | postBody.text = post.body 75 | } 76 | mViewBinding.imageView.load(post.imageUrl) 77 | } 78 | } 79 | 80 | private fun share() { 81 | val post = mViewModel.post.value ?: return 82 | val shareMsg = getString(R.string.share_message, post.title, post.author) 83 | 84 | val intent = ShareCompat.IntentBuilder.from(this) 85 | .setType("text/plain") 86 | .setText(shareMsg) 87 | .intent 88 | 89 | startActivity(Intent.createChooser(intent, null)) 90 | } 91 | 92 | override fun getViewBinding(): ActivityPostDetailsBinding = 93 | ActivityPostDetailsBinding.inflate(layoutInflater) 94 | 95 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 96 | menuInflater.inflate(R.menu.detail_menu, menu) 97 | return true 98 | } 99 | 100 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 101 | when (item.itemId) { 102 | android.R.id.home -> { 103 | supportFinishAfterTransition() 104 | return true 105 | } 106 | 107 | R.id.action_share -> { 108 | share() 109 | return true 110 | } 111 | } 112 | 113 | return super.onOptionsItemSelected(item) 114 | } 115 | 116 | companion object { 117 | private const val KEY_POST_ID = "postId" 118 | 119 | fun getStartIntent( 120 | context: Context, 121 | postId: Int 122 | ) = Intent(context, PostDetailsActivity::class.java).apply { putExtra(KEY_POST_ID, postId) } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | plugins { 26 | id("com.android.application") 27 | id("kotlin-android") 28 | id("kotlin-kapt") 29 | id("dagger.hilt.android.plugin") 30 | id("org.jlleitschuh.gradle.ktlint") 31 | } 32 | 33 | android { 34 | compileSdkVersion(31) 35 | buildToolsVersion("30.0.3") 36 | 37 | defaultConfig { 38 | applicationId = "dev.shreyaspatil.foodium" 39 | minSdkVersion(21) 40 | targetSdkVersion(31) 41 | versionCode = 1 42 | versionName = "1.0" 43 | 44 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 45 | 46 | javaCompileOptions { 47 | annotationProcessorOptions { 48 | arguments.plusAssign( 49 | hashMapOf( 50 | "room.schemaLocation" to "$projectDir/schemas", 51 | "room.incremental" to "true", 52 | "room.expandProjection" to "true" 53 | ) 54 | ) 55 | } 56 | } 57 | } 58 | 59 | buildFeatures.viewBinding = true 60 | 61 | buildTypes { 62 | getByName("release") { 63 | isMinifyEnabled = false 64 | proguardFiles( 65 | getDefaultProguardFile("proguard-android-optimize.txt"), 66 | "proguard-rules.pro" 67 | ) 68 | } 69 | } 70 | 71 | compileOptions { 72 | sourceCompatibility = JavaVersion.VERSION_1_8 73 | targetCompatibility = JavaVersion.VERSION_1_8 74 | } 75 | 76 | packagingOptions { 77 | exclude("META-INF/*.kotlin_module") 78 | } 79 | } 80 | 81 | tasks.withType().configureEach { 82 | kotlinOptions { 83 | jvmTarget = "1.8" 84 | } 85 | } 86 | 87 | dependencies { 88 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 89 | 90 | // Kotlin 91 | implementation(Dependencies.kotlin) 92 | 93 | // Coroutines 94 | implementation(Coroutines.core) 95 | implementation(Coroutines.android) 96 | 97 | // Android 98 | implementation(Android.appcompat) 99 | implementation(Android.activityKtx) 100 | implementation(Android.coreKtx) 101 | implementation(Android.constraintLayout) 102 | implementation(Android.swipeRefreshLayout) 103 | 104 | // Architecture Components 105 | implementation(Lifecycle.viewModel) 106 | implementation(Lifecycle.liveData) 107 | implementation(Lifecycle.runtimeKtx) 108 | 109 | // Room components 110 | implementation(Room.runtime) 111 | implementation(Room.ktx) 112 | kapt(Room.compiler) 113 | 114 | // Material Design 115 | implementation(Dependencies.materialDesign) 116 | implementation(Dependencies.materialDialog) 117 | 118 | // Coil-kt 119 | implementation(Dependencies.coil) 120 | 121 | // Retrofit 122 | implementation(Retrofit.retrofit) 123 | implementation(Retrofit.moshiRetrofitConverter) 124 | 125 | // Moshi 126 | implementation(Moshi.moshi) 127 | implementation(Moshi.codeGen) 128 | kapt(Moshi.codeGen) 129 | 130 | // Hilt + Dagger 131 | implementation(Hilt.hiltAndroid) 132 | implementation(Hilt.hiltViewModel) 133 | kapt(Hilt.daggerCompiler) 134 | kapt(Hilt.hiltCompiler) 135 | 136 | // Testing 137 | testImplementation(Testing.core) 138 | testImplementation(Testing.coroutines) 139 | testImplementation(Testing.room) 140 | testImplementation(Testing.okHttp) 141 | testImplementation(Testing.jUnit) 142 | testImplementation(Testing.truth) 143 | 144 | // Android Testing 145 | androidTestImplementation(Testing.extJUnit) 146 | androidTestImplementation(Testing.espresso) 147 | } 148 | 149 | ktlint { 150 | android.set(true) 151 | outputColorName.set("RED") 152 | } 153 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ]; do 30 | ls=$(ls -ld "$PRG") 31 | link=$(expr "$ls" : '.*-> \(.*\)$') 32 | if expr "$link" : '/.*' >/dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=$(dirname "$PRG")"/$link" 36 | fi 37 | done 38 | SAVED="$(pwd)" 39 | cd "$(dirname \"$PRG\")/" >/dev/null 40 | APP_HOME="$(pwd -P)" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=$(basename "$0") 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn() { 53 | echo "$*" 54 | } 55 | 56 | die() { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "$(uname)" in 69 | CYGWIN*) 70 | cygwin=true 71 | ;; 72 | Darwin*) 73 | darwin=true 74 | ;; 75 | MINGW*) 76 | msys=true 77 | ;; 78 | NONSTOP*) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ]; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ]; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then 109 | MAX_FD_LIMIT=$(ulimit -H -n) 110 | if [ $? -eq 0 ]; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ]; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ]; then 130 | APP_HOME=$(cygpath --path --mixed "$APP_HOME") 131 | CLASSPATH=$(cygpath --path --mixed "$CLASSPATH") 132 | JAVACMD=$(cygpath --unix "$JAVACMD") 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null) 136 | SEP="" 137 | for dir in $ROOTDIRSRAW; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ]; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@"; do 149 | CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -) 150 | CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition 153 | eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg") 154 | else 155 | eval $(echo args$i)="\"$arg\"" 156 | fi 157 | i=$((i + 1)) 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save() { 175 | for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 31 | 34 | 39 | 44 | 49 | 54 | 59 | 64 | 69 | 74 | 79 | 84 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 139 | 144 | 149 | 154 | 159 | 164 | 169 | 174 | 179 | 184 | 189 | 194 | 195 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/foodium/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Shreyas Patil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package dev.shreyaspatil.foodium.ui.main 26 | 27 | import android.animation.Animator 28 | import android.animation.AnimatorListenerAdapter 29 | import android.content.res.Configuration 30 | import android.os.Bundle 31 | import android.view.Menu 32 | import android.view.MenuItem 33 | import android.widget.ImageView 34 | import androidx.activity.viewModels 35 | import androidx.appcompat.app.AppCompatDelegate 36 | import androidx.core.app.ActivityOptionsCompat 37 | import androidx.lifecycle.Lifecycle 38 | import androidx.lifecycle.lifecycleScope 39 | import androidx.lifecycle.repeatOnLifecycle 40 | import com.shreyaspatil.MaterialDialog.MaterialDialog 41 | import dagger.hilt.android.AndroidEntryPoint 42 | import dev.shreyaspatil.foodium.R 43 | import dev.shreyaspatil.foodium.databinding.ActivityMainBinding 44 | import dev.shreyaspatil.foodium.model.Post 45 | import dev.shreyaspatil.foodium.model.State 46 | import dev.shreyaspatil.foodium.ui.base.BaseActivity 47 | import dev.shreyaspatil.foodium.ui.details.PostDetailsActivity 48 | import dev.shreyaspatil.foodium.ui.main.adapter.PostListAdapter 49 | import dev.shreyaspatil.foodium.utils.* 50 | import kotlinx.coroutines.ExperimentalCoroutinesApi 51 | import kotlinx.coroutines.flow.collect 52 | import kotlinx.coroutines.launch 53 | 54 | @ExperimentalCoroutinesApi 55 | @AndroidEntryPoint 56 | class MainActivity : BaseActivity() { 57 | 58 | override val mViewModel: MainViewModel by viewModels() 59 | 60 | private val mAdapter = PostListAdapter(this::onItemClicked) 61 | 62 | override fun onCreate(savedInstanceState: Bundle?) { 63 | setTheme(R.style.AppTheme) // Set AppTheme before setting content view. 64 | 65 | super.onCreate(savedInstanceState) 66 | setContentView(mViewBinding.root) 67 | 68 | initView() 69 | observePosts() 70 | } 71 | 72 | override fun onStart() { 73 | super.onStart() 74 | handleNetworkChanges() 75 | } 76 | 77 | private fun initView() { 78 | mViewBinding.run { 79 | postsRecyclerView.adapter = mAdapter 80 | 81 | swipeRefreshLayout.setOnRefreshListener { getPosts() } 82 | } 83 | } 84 | 85 | private fun observePosts() { 86 | lifecycleScope.launch { 87 | repeatOnLifecycle(Lifecycle.State.STARTED) { 88 | mViewModel.posts.collect { state -> 89 | when (state) { 90 | is State.Loading -> showLoading(true) 91 | is State.Success -> { 92 | if (state.data.isNotEmpty()) { 93 | mAdapter.submitList(state.data.toMutableList()) 94 | showLoading(false) 95 | } 96 | } 97 | is State.Error -> { 98 | showToast(state.message) 99 | showLoading(false) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | private fun getPosts() = mViewModel.getPosts() 108 | 109 | private fun showLoading(isLoading: Boolean) { 110 | mViewBinding.swipeRefreshLayout.isRefreshing = isLoading 111 | } 112 | 113 | /** 114 | * Observe network changes i.e. Internet Connectivity 115 | */ 116 | private fun handleNetworkChanges() { 117 | NetworkUtils.getNetworkLiveData(applicationContext).observe(this) { isConnected -> 118 | if (!isConnected) { 119 | mViewBinding.textViewNetworkStatus.text = 120 | getString(R.string.text_no_connectivity) 121 | mViewBinding.networkStatusLayout.apply { 122 | show() 123 | setBackgroundColor(getColorRes(R.color.colorStatusNotConnected)) 124 | } 125 | } else { 126 | if (mAdapter.itemCount == 0) getPosts() 127 | mViewBinding.textViewNetworkStatus.text = getString(R.string.text_connectivity) 128 | mViewBinding.networkStatusLayout.apply { 129 | setBackgroundColor(getColorRes(R.color.colorStatusConnected)) 130 | 131 | animate() 132 | .alpha(1f) 133 | .setStartDelay(ANIMATION_DURATION) 134 | .setDuration(ANIMATION_DURATION) 135 | .setListener(object : AnimatorListenerAdapter() { 136 | override fun onAnimationEnd(animation: Animator) { 137 | hide() 138 | } 139 | }) 140 | } 141 | } 142 | } 143 | } 144 | 145 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 146 | menuInflater.inflate(R.menu.main_menu, menu) 147 | return true 148 | } 149 | 150 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 151 | return when (item.itemId) { 152 | R.id.action_theme -> { 153 | // Get new mode. 154 | val mode = 155 | if ((resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == 156 | Configuration.UI_MODE_NIGHT_NO 157 | ) { 158 | AppCompatDelegate.MODE_NIGHT_YES 159 | } else { 160 | AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY 161 | } 162 | 163 | // Change UI Mode 164 | AppCompatDelegate.setDefaultNightMode(mode) 165 | true 166 | } 167 | 168 | else -> true 169 | } 170 | } 171 | 172 | override fun onBackPressed() { 173 | MaterialDialog.Builder(this) 174 | .setTitle(getString(R.string.exit_dialog_title)) 175 | .setMessage(getString(R.string.exit_dialog_message)) 176 | .setPositiveButton(getString(R.string.option_yes)) { dialogInterface, _ -> 177 | dialogInterface.dismiss() 178 | super.onBackPressed() 179 | } 180 | .setNegativeButton(getString(R.string.option_no)) { dialogInterface, _ -> 181 | dialogInterface.dismiss() 182 | } 183 | .build() 184 | .show() 185 | } 186 | 187 | override fun getViewBinding(): ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater) 188 | 189 | private fun onItemClicked(post: Post, imageView: ImageView) { 190 | val options = ActivityOptionsCompat.makeSceneTransitionAnimation( 191 | this, 192 | imageView, 193 | imageView.transitionName 194 | ) 195 | val postId = post.id ?: run { 196 | showToast("Unable to launch details") 197 | return 198 | } 199 | val intent = PostDetailsActivity.getStartIntent(this, postId) 200 | startActivity(intent, options.toBundle()) 201 | } 202 | 203 | companion object { 204 | const val ANIMATION_DURATION = 1000L 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](media/FoodiumHeader.png) 2 | 3 | # Foodium 🍲  4 | 5 | [![Test](https://github.com/PatilShreyas/Foodium/workflows/Test/badge.svg?branch=master)](https://github.com/PatilShreyas/Foodium/actions?query=workflow%3ATest) 6 | [![Build](https://github.com/PatilShreyas/Foodium/workflows/Build/badge.svg?branch=master)](https://github.com/PatilShreyas/Foodium/actions?query=workflow%3ABuild) 7 | [![Lint](https://github.com/PatilShreyas/Foodium/workflows/Lint/badge.svg?branch=master)](https://github.com/PatilShreyas/Foodium/actions?query=workflow%3ALint) 8 | 9 | [![GitHub license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 10 | [![Android Weekly](https://img.shields.io/badge/Android%20Weekly-%23406-2CA3E6.svg?style=flat)](http://androidweekly.net/issues/issue-406) 11 | [![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/) 12 | ![Github Followers](https://img.shields.io/github/followers/PatilShreyas?label=Follow&style=social) 13 | ![GitHub stars](https://img.shields.io/github/stars/PatilShreyas/Foodium?style=social) 14 | ![GitHub forks](https://img.shields.io/github/forks/PatilShreyas/Foodium?style=social) 15 | ![GitHub watchers](https://img.shields.io/github/watchers/PatilShreyas/Foodium?style=social) 16 | ![Twitter Follow](https://img.shields.io/twitter/follow/imShreyasPatil?label=Follow&style=social) 17 | 18 | **Foodium** is a sample food blog 🍲 Android application 📱 built to demonstrate use of *Modern Android development* tools. Dedicated to all Android Developers with ❤️. 19 | 20 | ***You can Install and test latest Foodium app from below 👇*** 21 | 22 | [![Foodium App](https://img.shields.io/badge/Foodium🍲-APK-red.svg?style=for-the-badge&logo=android)](https://github.com/PatilShreyas/Foodium/releases/latest/download/app.apk) 23 | 24 | 25 | ## About 26 | It simply loads **Posts** data from API and stores it in persistence storage (i.e. SQLite Database). Posts will be always loaded from local database. Remote data (from API) and Local data is always synchronized. 27 | - This makes it offline capable 😃. 28 | - Clean and Simple Material UI. 29 | - It supports dark theme too 🌗. 30 | 31 | *Dummy API is used in this app. JSON response is statically hosted [here](https://patilshreyas.github.io/DummyFoodiumApi/api/posts/)*. 32 | 33 | ## Built With 🛠 34 | - [Kotlin](https://kotlinlang.org/) - First class and official programming language for Android development. 35 | - [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) - For asynchronous and more.. 36 | - [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) - A cold asynchronous data stream that sequentially emits values and completes normally or with an exception. 37 | - [Android Architecture Components](https://developer.android.com/topic/libraries/architecture) - Collection of libraries that help you design robust, testable, and maintainable apps. 38 | - [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - Data objects that notify views when the underlying database changes. 39 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - Stores UI-related data that isn't destroyed on UI changes. 40 | - [ViewBinding](https://developer.android.com/topic/libraries/view-binding) - Generates a binding class for each XML layout file present in that module and allows you to more easily write code that interacts with views. 41 | - [Room](https://developer.android.com/topic/libraries/architecture/room) - SQLite object mapping library. 42 | - [Dependency Injection](https://developer.android.com/training/dependency-injection) - 43 | - [Hilt-Dagger](https://dagger.dev/hilt/) - Standard library to incorporate Dagger dependency injection into an Android application. 44 | - [Hilt-ViewModel](https://developer.android.com/training/dependency-injection/hilt-jetpack) - DI for injecting `ViewModel`. 45 | - [Retrofit](https://square.github.io/retrofit/) - A type-safe HTTP client for Android and Java. 46 | - [Moshi](https://github.com/square/moshi) - A modern JSON library for Kotlin and Java. 47 | - [Moshi Converter](https://github.com/square/retrofit/tree/master/retrofit-converters/moshi) - A Converter which uses Moshi for serialization to and from JSON. 48 | - [Coil-kt](https://coil-kt.github.io/coil/) - An image loading library for Android backed by Kotlin Coroutines. 49 | - [Material Components for Android](https://github.com/material-components/material-components-android) - Modular and customizable Material Design UI components for Android. 50 | - [Gradle Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html) - For writing Gradle build scripts using Kotlin. 51 | 52 | ## Lint ✅ 53 | This project uses [***GitHub Super Linter***](https://github.com/github/super-linter) which is Combination of multiple linters to install as a GitHub Action. 54 | 55 | Following Linters are used internally by super linter (enabled for this project): 56 | - XML: [LibXML](http://xmlsoft.org/) 57 | - Kotlin: [ktlint](https://github.com/pinterest/ktlint) 58 | 59 | 60 | ## [`Dagger`](https://dagger.dev/) (Old) DI Version 🗡️ 61 | If you want to refer old way of Dependency Injetion using Dagger2, see branch [***`dagger2-di`***](https://github.com/PatilShreyas/Foodium/tree/dagger2-di) 62 | 63 | [![Dagger2 Version](https://img.shields.io/static/v1?label=Foodium&message=Dagger2-DI&color=brightgreen&logo=android)](https://github.com/PatilShreyas/Foodium/tree/dev-hilt-android) 64 | 65 | 66 | ## [`Koin`](https://insert-koin.io/) DI Version 🗡️ 67 | If you want to use *Koin - Dependency Injection framework* in app then visit below repository. 68 | 69 | [![Koin Version](https://img.shields.io/badge/PranayPatel512-Foodium-blue.svg?style=flat-square&logo=github)](https://github.com/pranaypatel512/Foodium) 70 | 71 | **Contributed By:** [Pranay Patel](https://github.com/pranaypatel512/) 72 | 73 | 74 | # Package Structure 75 | 76 | dev.shreyaspatil.foodium # Root Package 77 | . 78 | ├── data # For data handling. 79 | │ ├── local # Local Persistence Database. Room (SQLite) database 80 | | │ ├── dao # Data Access Object for Room 81 | │ ├── remote # Remote Data Handlers 82 | | │ ├── api # Retrofit API for remote end point. 83 | │ └── repository # Single source of data. 84 | | 85 | ├── model # Model classes 86 | | 87 | ├── di # Dependency Injection 88 | │ ├── builder # Activity Builder 89 | │ ├── component # DI Components 90 | │ └── module # DI Modules 91 | | 92 | ├── ui # Activity/View layer 93 | │ ├── base # Base View 94 | │ ├── main # Main Screen Activity & ViewModel 95 | | │ ├── adapter # Adapter for RecyclerView 96 | | │ └── viewmodel # ViewHolder for RecyclerView 97 | │ └── details # Detail Screen Activity and ViewModel 98 | | 99 | └── utils # Utility Classes / Kotlin extensions 100 | 101 | 102 | ## Architecture 103 | This app uses [***MVVM (Model View View-Model)***](https://developer.android.com/jetpack/docs/guide#recommended-app-arch) architecture. 104 | 105 | ![](https://developer.android.com/topic/libraries/architecture/images/final-architecture.png) 106 | 107 | 108 | ## Contribute 109 | If you want to contribute to this library, you're always welcome! 110 | See [Contributing Guidelines](CONTRIBUTING.md). 111 | 112 | ## Discuss 💬 113 | 114 | Have any questions, doubts or want to present your opinions, views? You're always welcome. You can [start discussions](https://github.com/PatilShreyas/Foodium/discussions). 115 | 116 | ## Contact 117 | If you need any help, you can connect with me. 118 | 119 | Visit:- [shreyaspatil.dev](https://shreyaspatil.dev) 120 | 121 | ## License 122 | ``` 123 | MIT License 124 | 125 | Copyright (c) 2020 Shreyas Patil 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining a copy 128 | of this software and associated documentation files (the "Software"), to deal 129 | in the Software without restriction, including without limitation the rights 130 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 131 | copies of the Software, and to permit persons to whom the Software is 132 | furnished to do so, subject to the following conditions: 133 | 134 | The above copyright notice and this permission notice shall be included in all 135 | copies or substantial portions of the Software. 136 | 137 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 138 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 139 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 140 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 141 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 142 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 143 | SOFTWARE. 144 | ``` 145 | --------------------------------------------------------------------------------