├── .github ├── renovate.json └── workflows │ └── build.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── example │ │ └── juicetracker │ │ ├── JuiceTrackerApplication.kt │ │ ├── MainActivity.kt │ │ ├── data │ │ ├── AppContainer.kt │ │ ├── AppDataContainer.kt │ │ ├── AppDatabase.kt │ │ ├── Juice.kt │ │ ├── JuiceDao.kt │ │ ├── JuiceRepository.kt │ │ └── RoomJuiceRepository.kt │ │ └── ui │ │ ├── AppViewModelProvider.kt │ │ ├── JuiceTrackerApp.kt │ │ ├── JuiceTrackerViewModel.kt │ │ ├── bottomsheet │ │ ├── ColorSpinnerRow.kt │ │ ├── EntryBottomSheet.kt │ │ └── RatingInputRow.kt │ │ ├── homescreen │ │ ├── AdBanner.kt │ │ ├── JuiceIcon.kt │ │ ├── JuiceTrackerFAB.kt │ │ ├── JuiceTrackerList.kt │ │ ├── JuiceTrackerTopBar.kt │ │ └── RatingDisplay.kt │ │ └── theme │ │ ├── Color.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── delete_icon.xml │ ├── ic_launcher_background.xml │ ├── juice_clear_icon.xml │ ├── juice_color.xml │ └── star.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>android/.github:renovate-config" 5 | ], 6 | "baseBranches": [ 7 | "compose-with-views", 8 | "views-with-compose", 9 | "compose-starter", 10 | "views-starter", 11 | "views" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - compose-with-views 7 | - views-with-compose 8 | - compose-starter 9 | - views-starter 10 | - views 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set Up JDK 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'zulu' # See 'Supported distributions' for available options 22 | java-version: '17' 23 | cache: 'gradle' 24 | 25 | - name: Setup Gradle 26 | uses: gradle/actions/setup-gradle@v4 27 | 28 | - name: Setup Android SDK 29 | uses: android-actions/setup-android@v3 30 | 31 | - name: Build project and run local and device tests 32 | run: ./gradlew :app:assembleDebug 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Mac files 6 | .DS_Store 7 | 8 | # files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # generated files 15 | bin/ 16 | gen/ 17 | 18 | # Ignore gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | proguard-project.txt 28 | 29 | # Eclipse files 30 | .project 31 | .classpath 32 | .settings/ 33 | 34 | # Android Studio/IDEA 35 | *.iml 36 | .idea 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we have to jump a couple of 6 | legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement (CLA). 9 | 10 | * If you are an individual writing original source code and you're sure you 11 | own the intellectual property, then you'll need to sign an [individual CLA] 12 | (https://cla.developers.google.com). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA] 15 | (https://cla.developers.google.com). 16 | * Please make sure you sign both, Android and Google CLA 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Android Code Style Guide] 32 | (https://source.android.com/source/code-style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Juice Tracker app 2 | ================================== 3 | 4 | Code for Android Basics with Compose Juice Tracker app. 5 | 6 | Pre-requisites 7 | -------------- 8 | 9 | You need to know: 10 | - How to create a new project in Android Studio. 11 | - How to use architecture components including ViewModel. 12 | - How to use Room with StateFlow. 13 | 14 | Getting Started 15 | --------------- 16 | 17 | 1. [Install Android Studio](https://developer.android.com/studio/install.html), if you don't already 18 | have it. 19 | 2. Download the app. 20 | 3. Import the app into Android Studio. 21 | 4. Build and run the app. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("com.android.application") 19 | id("org.jetbrains.kotlin.android") 20 | id("com.google.devtools.ksp") version "2.1.0-1.0.29" 21 | id("org.jetbrains.kotlin.plugin.compose") 22 | } 23 | 24 | android { 25 | namespace = "com.example.juicetracker" 26 | compileSdk = 35 27 | 28 | defaultConfig { 29 | applicationId = "com.example.juicetracker" 30 | minSdk = 24 31 | targetSdk = 35 32 | versionCode = 1 33 | versionName = "1.0" 34 | 35 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 36 | vectorDrawables { 37 | useSupportLibrary = true 38 | } 39 | } 40 | 41 | buildTypes { 42 | release { 43 | isMinifyEnabled = false 44 | proguardFiles( 45 | getDefaultProguardFile("proguard-android-optimize.txt"), 46 | "proguard-rules.pro" 47 | ) 48 | } 49 | } 50 | compileOptions { 51 | sourceCompatibility = JavaVersion.VERSION_17 52 | targetCompatibility = JavaVersion.VERSION_17 53 | } 54 | kotlinOptions { 55 | jvmTarget = "17" 56 | } 57 | buildFeatures { 58 | compose = true 59 | } 60 | packaging { 61 | resources { 62 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 63 | } 64 | } 65 | } 66 | 67 | dependencies { 68 | 69 | implementation(platform("androidx.compose:compose-bom:2024.12.01")) 70 | implementation("androidx.activity:activity-compose:1.9.3") 71 | implementation("androidx.compose.material3:material3") 72 | implementation("androidx.compose.ui:ui") 73 | implementation("androidx.compose.ui:ui-graphics") 74 | implementation("androidx.compose.ui:ui-tooling-preview") 75 | implementation("androidx.core:core-ktx:1.15.0") 76 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["arch_lifecycle_version"]}") 77 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:${rootProject.extra["arch_lifecycle_version"]}") 78 | implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}") 79 | implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}") 80 | implementation("com.google.android.gms:play-services-ads:23.6.0") 81 | 82 | ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}") 83 | 84 | debugImplementation("androidx.compose.ui:ui-test-manifest") 85 | debugImplementation("androidx.compose.ui:ui-tooling") 86 | } 87 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 28 | 29 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/JuiceTrackerApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker 17 | 18 | import android.app.Application 19 | import com.example.juicetracker.data.AppContainer 20 | import com.example.juicetracker.data.AppDataContainer 21 | 22 | class JuiceTrackerApplication : Application() { 23 | /** 24 | * AppContainer instance used by the rest of classes to obtain dependencies 25 | */ 26 | lateinit var container: AppContainer 27 | override fun onCreate() { 28 | super.onCreate() 29 | container = AppDataContainer(this) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker 17 | 18 | import android.os.Bundle 19 | import androidx.activity.ComponentActivity 20 | import androidx.activity.compose.setContent 21 | import androidx.activity.enableEdgeToEdge 22 | import com.example.juicetracker.ui.JuiceTrackerApp 23 | import com.example.juicetracker.ui.theme.JuiceTrackerTheme 24 | 25 | class MainActivity : ComponentActivity() { 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | enableEdgeToEdge() 28 | super.onCreate(savedInstanceState) 29 | setContent { 30 | JuiceTrackerTheme { 31 | JuiceTrackerApp() 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/AppContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | /** 19 | * App container for Dependency injection. 20 | */ 21 | interface AppContainer { 22 | val juiceRepository: JuiceRepository 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/AppDataContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import android.content.Context 19 | 20 | /** 21 | * [AppContainer] implementation that provides instance of [RoomJuiceRepository] 22 | */ 23 | class AppDataContainer(private val context: Context) : AppContainer { 24 | /** 25 | * Implementation for [JuiceRepository] 26 | */ 27 | override val juiceRepository: JuiceRepository by lazy { 28 | RoomJuiceRepository(AppDatabase.getDatabase(context).juiceDao()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import android.content.Context 19 | import androidx.room.Database 20 | import androidx.room.Room 21 | import androidx.room.RoomDatabase 22 | 23 | @Database(entities = [Juice::class], version = 1) 24 | abstract class AppDatabase: RoomDatabase() { 25 | abstract fun juiceDao(): JuiceDao 26 | companion object { 27 | @Volatile 28 | private var INSTANCE: AppDatabase? = null 29 | fun getDatabase(context: Context): AppDatabase { 30 | return INSTANCE ?: synchronized(this) { 31 | Room.databaseBuilder( 32 | context, 33 | AppDatabase::class.java, 34 | "app_database" 35 | ) 36 | // Setting this option in your app's database builder means that Room 37 | // permanently deletes all data from the tables in your database when it 38 | // attempts to perform a migration with no defined migration path. 39 | .fallbackToDestructiveMigration() 40 | .build() 41 | .also { 42 | INSTANCE = it 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/Juice.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import androidx.annotation.StringRes 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.room.Entity 21 | import androidx.room.PrimaryKey 22 | import com.example.juicetracker.R 23 | import com.example.juicetracker.ui.theme.Orange as OrangeColor 24 | 25 | /** 26 | * Represents a single table in the database 27 | * 28 | * Note that the color type is different than in the Main branch. 29 | * Please remove app and re-install if existing app from the Main branch exist 30 | * to avoid database error 31 | */ 32 | @Entity 33 | data class Juice( 34 | @PrimaryKey(autoGenerate = true) 35 | val id: Long, 36 | val name: String, 37 | val description: String = "", 38 | val color: String, 39 | val rating: Int 40 | ) 41 | 42 | enum class JuiceColor(val color: Color, @StringRes val label: Int) { 43 | Red(Color.Red, R.string.red), 44 | Blue(Color.Blue, R.string.blue), 45 | Green(Color.Green, R.string.green), 46 | Cyan(Color.Cyan, R.string.cyan), 47 | Yellow(Color.Yellow, R.string.yellow), 48 | Magenta(Color.Magenta, R.string.magenta), 49 | Orange(OrangeColor, R.string.orange) 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/JuiceDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import androidx.room.Dao 19 | import androidx.room.Delete 20 | import androidx.room.Insert 21 | import androidx.room.Query 22 | import androidx.room.Update 23 | import kotlinx.coroutines.flow.Flow 24 | 25 | /** 26 | * Juice Data Access Object which contains methods to access and modify Juice table in Room DB 27 | */ 28 | @Dao 29 | interface JuiceDao { 30 | @Query("SELECT * FROM juice") 31 | fun getAll(): Flow> 32 | 33 | @Insert 34 | suspend fun insert(juice: Juice) 35 | 36 | @Delete 37 | suspend fun delete(juice: Juice) 38 | 39 | @Update 40 | suspend fun update(juice: Juice) 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/JuiceRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import kotlinx.coroutines.flow.Flow 19 | 20 | /** 21 | * Interface for [JuiceRepository] which contains method to access and modify juice items 22 | */ 23 | interface JuiceRepository { 24 | val juiceStream: Flow> 25 | suspend fun addJuice(juice: Juice) 26 | suspend fun deleteJuice(juice: Juice) 27 | suspend fun updateJuice(juice: Juice) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/data/RoomJuiceRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.data 17 | 18 | import kotlinx.coroutines.flow.Flow 19 | 20 | /** 21 | * Implementation of [JuiceRepository] interface 22 | * which allow access and modification of Juice items through [JuiceDao] 23 | */ 24 | class RoomJuiceRepository(private val juiceDao: JuiceDao) : JuiceRepository { 25 | override val juiceStream: Flow> = juiceDao.getAll() 26 | 27 | override suspend fun addJuice(juice: Juice) = juiceDao.insert(juice) 28 | override suspend fun deleteJuice(juice: Juice) = juiceDao.delete(juice) 29 | override suspend fun updateJuice(juice: Juice) = juiceDao.update(juice) 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/AppViewModelProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui 17 | 18 | import android.app.Application 19 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory 20 | import androidx.lifecycle.viewmodel.CreationExtras 21 | import androidx.lifecycle.viewmodel.initializer 22 | import androidx.lifecycle.viewmodel.viewModelFactory 23 | import com.example.juicetracker.JuiceTrackerApplication 24 | 25 | object AppViewModelProvider { 26 | val Factory = viewModelFactory { 27 | // Initializer for JuiceTrackerViewModel 28 | initializer { 29 | JuiceTrackerViewModel(juiceTrackerApplication().container.juiceRepository) 30 | } 31 | } 32 | } 33 | /** 34 | * Extension function to queries for [Application] object and returns an instance of 35 | * [JuiceTrackerApplication]. 36 | */ 37 | fun CreationExtras.juiceTrackerApplication(): JuiceTrackerApplication = 38 | (this[AndroidViewModelFactory.APPLICATION_KEY] as JuiceTrackerApplication) 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/JuiceTrackerApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui 17 | 18 | import androidx.compose.foundation.layout.Column 19 | import androidx.compose.foundation.layout.fillMaxWidth 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.material3.BottomSheetScaffold 22 | import androidx.compose.material3.BottomSheetScaffoldState 23 | import androidx.compose.material3.ExperimentalMaterial3Api 24 | import androidx.compose.material3.Scaffold 25 | import androidx.compose.material3.SheetValue 26 | import androidx.compose.material3.rememberBottomSheetScaffoldState 27 | import androidx.compose.material3.rememberStandardBottomSheetState 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.collectAsState 30 | import androidx.compose.runtime.getValue 31 | import androidx.compose.runtime.rememberCoroutineScope 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.res.dimensionResource 34 | import androidx.lifecycle.viewmodel.compose.viewModel 35 | import com.example.juicetracker.R 36 | import com.example.juicetracker.ui.bottomsheet.EntryBottomSheet 37 | import com.example.juicetracker.ui.homescreen.AdBanner 38 | import com.example.juicetracker.ui.homescreen.JuiceTrackerFAB 39 | import com.example.juicetracker.ui.homescreen.JuiceTrackerList 40 | import com.example.juicetracker.ui.homescreen.JuiceTrackerTopAppBar 41 | import kotlinx.coroutines.launch 42 | 43 | @OptIn(ExperimentalMaterial3Api::class) 44 | @Composable 45 | fun JuiceTrackerApp( 46 | juiceTrackerViewModel: JuiceTrackerViewModel = viewModel(factory = AppViewModelProvider.Factory) 47 | ) { 48 | 49 | val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( 50 | bottomSheetState = rememberStandardBottomSheetState( 51 | initialValue = SheetValue.Hidden, 52 | skipHiddenState = false, 53 | ) 54 | ) 55 | 56 | val scope = rememberCoroutineScope() 57 | val trackerState by juiceTrackerViewModel.juiceListStream.collectAsState(emptyList()) 58 | 59 | EntryBottomSheet( 60 | juiceTrackerViewModel = juiceTrackerViewModel, 61 | sheetScaffoldState = bottomSheetScaffoldState, 62 | modifier = Modifier, 63 | onCancel = { 64 | scope.launch { 65 | bottomSheetScaffoldState.bottomSheetState.hide() 66 | } 67 | }, 68 | onSubmit = { 69 | juiceTrackerViewModel.saveJuice() 70 | scope.launch { 71 | bottomSheetScaffoldState.bottomSheetState.hide() 72 | } 73 | } 74 | ) { 75 | Scaffold( 76 | topBar = { 77 | JuiceTrackerTopAppBar() 78 | }, 79 | floatingActionButton = { 80 | JuiceTrackerFAB( 81 | onClick = { 82 | juiceTrackerViewModel.resetCurrentJuice() 83 | scope.launch { bottomSheetScaffoldState.bottomSheetState.expand() } 84 | } 85 | ) 86 | } 87 | ) { contentPadding -> 88 | Column(Modifier.padding(contentPadding)) { 89 | AdBanner( 90 | Modifier 91 | .fillMaxWidth() 92 | .padding( 93 | top = dimensionResource(R.dimen.padding_medium), 94 | bottom = dimensionResource(R.dimen.padding_small) 95 | ) 96 | ) 97 | JuiceTrackerList( 98 | juices = trackerState, 99 | onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) }, 100 | onUpdate = { juice -> 101 | juiceTrackerViewModel.updateCurrentJuice(juice) 102 | scope.launch { 103 | bottomSheetScaffoldState.bottomSheetState.expand() 104 | } 105 | }, 106 | ) 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/JuiceTrackerViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui 17 | 18 | import androidx.lifecycle.ViewModel 19 | import androidx.lifecycle.viewModelScope 20 | import com.example.juicetracker.data.Juice 21 | import com.example.juicetracker.data.JuiceColor 22 | import com.example.juicetracker.data.JuiceRepository 23 | import kotlinx.coroutines.flow.Flow 24 | import kotlinx.coroutines.flow.MutableStateFlow 25 | import kotlinx.coroutines.flow.StateFlow 26 | import kotlinx.coroutines.flow.update 27 | import kotlinx.coroutines.launch 28 | 29 | /** 30 | * View Model which maintain states for [JuiceTrackerApp] 31 | */ 32 | class JuiceTrackerViewModel(private val juiceRepository: JuiceRepository) : ViewModel() { 33 | private val emptyJuice = Juice(0, "", "", JuiceColor.Red.name, 3) 34 | private val _currentJuiceStream = MutableStateFlow(emptyJuice) 35 | val currentJuiceStream: StateFlow = _currentJuiceStream 36 | val juiceListStream: Flow> = juiceRepository.juiceStream 37 | 38 | fun resetCurrentJuice() = _currentJuiceStream.update { emptyJuice } 39 | fun updateCurrentJuice(juice: Juice) = _currentJuiceStream.update { juice } 40 | 41 | fun saveJuice() = viewModelScope.launch { 42 | if (_currentJuiceStream.value.id > 0) { 43 | juiceRepository.updateJuice(_currentJuiceStream.value) 44 | } else { 45 | juiceRepository.addJuice(_currentJuiceStream.value) 46 | } 47 | } 48 | 49 | fun deleteJuice(juice: Juice) = viewModelScope.launch { 50 | juiceRepository.deleteJuice(juice) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/bottomsheet/ColorSpinnerRow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.bottomsheet 17 | 18 | import android.view.View 19 | import android.widget.AdapterView 20 | import android.widget.ArrayAdapter 21 | import android.widget.Spinner 22 | import androidx.compose.foundation.layout.fillMaxWidth 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.viewinterop.AndroidView 27 | import com.example.juicetracker.R 28 | import com.example.juicetracker.data.JuiceColor 29 | 30 | /** 31 | * Adapter Class implementation for Color Spinner 32 | */ 33 | class SpinnerAdapter(val onColorChange: (Int) -> Unit) : AdapterView.OnItemSelectedListener { 34 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 35 | onColorChange(position) 36 | } 37 | 38 | override fun onNothingSelected(parent: AdapterView<*>?) { 39 | onColorChange(0) 40 | } 41 | } 42 | 43 | /** 44 | * Composable which includes a spinner which is rendered through AndroidView 45 | * using Spinner view component 46 | */ 47 | @Composable 48 | fun ColorSpinnerRow( 49 | colorSpinnerPosition: Int, 50 | onColorChange: (Int) -> Unit, 51 | modifier: Modifier = Modifier 52 | ) { 53 | val juiceColorArray = JuiceColor.values().map { 54 | juiceColor -> stringResource(juiceColor.label) 55 | } 56 | InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) { 57 | AndroidView( 58 | modifier = Modifier.fillMaxWidth(), 59 | factory = { context -> 60 | Spinner(context).apply { 61 | adapter = 62 | ArrayAdapter( 63 | context, 64 | android.R.layout.simple_spinner_dropdown_item, 65 | juiceColorArray 66 | ) 67 | } 68 | }, 69 | update = { spinner -> 70 | spinner.setSelection(colorSpinnerPosition) 71 | spinner.onItemSelectedListener = SpinnerAdapter(onColorChange) 72 | } 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/bottomsheet/EntryBottomSheet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.bottomsheet 17 | 18 | import androidx.compose.foundation.layout.Arrangement 19 | import androidx.compose.foundation.layout.Box 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.Row 22 | import androidx.compose.foundation.layout.fillMaxWidth 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.foundation.text.KeyboardOptions 25 | import androidx.compose.material3.BottomSheetScaffold 26 | import androidx.compose.material3.BottomSheetScaffoldState 27 | import androidx.compose.material3.Button 28 | import androidx.compose.material3.Divider 29 | import androidx.compose.material3.ExperimentalMaterial3Api 30 | import androidx.compose.material3.MaterialTheme 31 | import androidx.compose.material3.MaterialTheme.colorScheme 32 | import androidx.compose.material3.OutlinedButton 33 | import androidx.compose.material3.Text 34 | import androidx.compose.material3.TextField 35 | import androidx.compose.material3.TextFieldDefaults 36 | import androidx.compose.runtime.Composable 37 | import androidx.compose.runtime.collectAsState 38 | import androidx.compose.runtime.getValue 39 | import androidx.compose.ui.Alignment 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.res.dimensionResource 42 | import androidx.compose.ui.res.stringResource 43 | import androidx.compose.ui.text.font.FontWeight 44 | import androidx.compose.ui.text.input.ImeAction 45 | import androidx.compose.ui.unit.dp 46 | import com.example.juicetracker.R 47 | import com.example.juicetracker.data.Juice 48 | import com.example.juicetracker.data.JuiceColor 49 | import com.example.juicetracker.ui.JuiceTrackerViewModel 50 | import java.util.Locale 51 | 52 | @OptIn(ExperimentalMaterial3Api::class) 53 | @Composable 54 | fun EntryBottomSheet( 55 | juiceTrackerViewModel: JuiceTrackerViewModel, 56 | sheetScaffoldState: BottomSheetScaffoldState, 57 | onCancel: () -> Unit, 58 | onSubmit: () -> Unit, 59 | modifier: Modifier = Modifier, 60 | content: @Composable () -> Unit, 61 | ) { 62 | val juice by juiceTrackerViewModel.currentJuiceStream.collectAsState() 63 | 64 | BottomSheetScaffold( 65 | modifier = modifier, 66 | scaffoldState = sheetScaffoldState, 67 | sheetPeekHeight = 0.dp, 68 | sheetContent = { 69 | Column { 70 | SheetHeader(Modifier.padding(dimensionResource(R.dimen.padding_small))) 71 | SheetForm( 72 | juice = juice, 73 | onUpdateJuice = juiceTrackerViewModel::updateCurrentJuice, 74 | onCancel = onCancel, 75 | onSubmit = onSubmit, 76 | modifier = Modifier.padding( 77 | horizontal = dimensionResource(R.dimen.padding_medium) 78 | ) 79 | ) 80 | } 81 | } 82 | ) { 83 | content() 84 | } 85 | } 86 | 87 | @Composable 88 | fun SheetHeader(modifier: Modifier = Modifier) { 89 | Column(modifier = modifier) { 90 | Text( 91 | modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)), 92 | text = stringResource(R.string.bottom_sheet_headline), 93 | style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold) 94 | ) 95 | Divider() 96 | } 97 | } 98 | 99 | @Composable 100 | fun SheetForm( 101 | juice: Juice, 102 | onUpdateJuice: (Juice) -> Unit, 103 | onCancel: () -> Unit, 104 | onSubmit: () -> Unit, 105 | modifier: Modifier = Modifier 106 | ) { 107 | Column( 108 | modifier = modifier, 109 | verticalArrangement = Arrangement.spacedBy(4.dp) 110 | ) { 111 | TextInputRow( 112 | inputLabel = stringResource(R.string.juice_name), 113 | fieldValue = juice.name, 114 | onValueChange = { name -> onUpdateJuice(juice.copy(name = name)) }, 115 | modifier = Modifier.fillMaxWidth() 116 | ) 117 | TextInputRow( 118 | inputLabel = stringResource(R.string.juice_description), 119 | fieldValue = juice.description, 120 | onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) }, 121 | modifier = Modifier.fillMaxWidth() 122 | ) 123 | ColorSpinnerRow( 124 | colorSpinnerPosition = findColorIndex(juice.color), 125 | onColorChange = { color -> 126 | onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name)) 127 | } 128 | ) 129 | RatingInputRow( 130 | rating = juice.rating, 131 | onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) } 132 | ) 133 | ButtonRow( 134 | modifier = Modifier 135 | .align(Alignment.End) 136 | .padding(bottom = dimensionResource(R.dimen.padding_medium)), 137 | onCancel = onCancel, 138 | onSubmit = onSubmit, 139 | submitButtonEnabled = juice.name.isNotEmpty() 140 | ) 141 | } 142 | } 143 | 144 | @Composable 145 | fun ButtonRow( 146 | onCancel: () -> Unit, 147 | onSubmit: () -> Unit, 148 | submitButtonEnabled: Boolean, 149 | modifier: Modifier = Modifier 150 | ) { 151 | Row( 152 | modifier = modifier, 153 | horizontalArrangement = Arrangement.spacedBy(24.dp) 154 | ) { 155 | OutlinedButton( 156 | onClick = onCancel, 157 | border = null 158 | ) { 159 | Text(stringResource(android.R.string.cancel).uppercase(Locale.getDefault())) 160 | } 161 | Button( 162 | onClick = onSubmit, 163 | enabled = submitButtonEnabled 164 | ) { 165 | Text(stringResource(R.string.save).uppercase(Locale.getDefault())) 166 | } 167 | } 168 | } 169 | 170 | @Composable 171 | fun TextInputRow( 172 | inputLabel: String, 173 | fieldValue: String, 174 | onValueChange: (String) -> Unit, 175 | modifier: Modifier = Modifier 176 | ) { 177 | InputRow(inputLabel, modifier) { 178 | TextField( 179 | modifier = Modifier.padding(bottom = dimensionResource(R.dimen.padding_small)), 180 | value = fieldValue, 181 | onValueChange = onValueChange, 182 | singleLine = true, 183 | maxLines = 1, 184 | colors = TextFieldDefaults.colors( 185 | focusedContainerColor = colorScheme.surface, 186 | unfocusedContainerColor = colorScheme.surface, 187 | disabledContainerColor = colorScheme.surface, 188 | ), 189 | keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) 190 | ) 191 | } 192 | } 193 | 194 | @Composable 195 | fun InputRow( 196 | inputLabel: String, 197 | modifier: Modifier = Modifier, 198 | content: @Composable () -> Unit 199 | ) { 200 | Row( 201 | modifier = modifier, 202 | verticalAlignment = Alignment.CenterVertically 203 | ) { 204 | Text( 205 | text = inputLabel, 206 | fontWeight = FontWeight.SemiBold, 207 | modifier = modifier.weight(1f) 208 | ) 209 | Box(modifier = Modifier.weight(2f)) { 210 | content() 211 | } 212 | } 213 | } 214 | 215 | private fun findColorIndex(color: String): Int { 216 | val juiceColor = JuiceColor.valueOf(color) 217 | return JuiceColor.values().indexOf(juiceColor) 218 | } 219 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/bottomsheet/RatingInputRow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.bottomsheet 17 | 18 | import android.widget.RatingBar 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.viewinterop.AndroidView 23 | import com.example.juicetracker.R 24 | 25 | /** 26 | * Showing star rating by using RatingBar through AndroidView 27 | */ 28 | @Composable 29 | fun RatingInputRow(rating: Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier) { 30 | InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) { 31 | AndroidView( 32 | factory = { context -> 33 | RatingBar(context).apply { 34 | stepSize = 1f 35 | 36 | } 37 | }, 38 | update = { ratingBar -> 39 | ratingBar.rating = rating.toFloat() 40 | ratingBar.setOnRatingBarChangeListener { _, _, _ -> 41 | onRatingChange(ratingBar.rating.toInt()) 42 | } 43 | } 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/AdBanner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.viewinterop.AndroidView 21 | import com.google.android.gms.ads.AdRequest 22 | import com.google.android.gms.ads.AdSize 23 | import com.google.android.gms.ads.AdView 24 | 25 | /** 26 | * A composable which uses AndroidView to render AdView. 27 | * Showing a banner test ad 28 | */ 29 | @Composable 30 | fun AdBanner(modifier: Modifier = Modifier) { 31 | AndroidView( 32 | modifier = modifier, 33 | factory = { context -> 34 | AdView(context).apply { 35 | setAdSize(AdSize.BANNER) 36 | // Use test ad unit ID 37 | adUnitId = "ca-app-pub-3940256099942544/6300978111" 38 | } 39 | }, 40 | update = { 41 | it.loadAd(AdRequest.Builder().build()) 42 | } 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/JuiceIcon.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.foundation.layout.Box 19 | import androidx.compose.material3.Icon 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.semantics.contentDescription 26 | import androidx.compose.ui.semantics.semantics 27 | import com.example.juicetracker.R 28 | import com.example.juicetracker.data.JuiceColor 29 | 30 | /** 31 | * Custom icon for juice which is able to adjust for Dark Mode. 32 | * contentDescription for Box is added through semantics to support better accessibility. 33 | * Icons' contentDescription are nullified as its meaning has been explained by 34 | * the box's contentDescription 35 | */ 36 | @Composable 37 | fun JuiceIcon(color: String, modifier: Modifier = Modifier) { 38 | val juiceIconContentDescription = stringResource(R.string.juice_color, color) 39 | Box( 40 | modifier.semantics { 41 | contentDescription = juiceIconContentDescription 42 | } 43 | ) { 44 | Icon( 45 | painter = painterResource(R.drawable.juice_color), 46 | contentDescription = null, 47 | tint = JuiceColor.valueOf(color).color, 48 | modifier = Modifier.align(Alignment.Center) 49 | ) 50 | Icon(painter = painterResource(R.drawable.juice_clear_icon), contentDescription = null) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/JuiceTrackerFAB.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.material.icons.Icons 19 | import androidx.compose.material.icons.filled.Add 20 | import androidx.compose.material3.FloatingActionButton 21 | import androidx.compose.material3.Icon 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.res.stringResource 25 | import com.example.juicetracker.R 26 | 27 | @Composable 28 | fun JuiceTrackerFAB( 29 | onClick: () -> Unit, 30 | modifier: Modifier = Modifier 31 | ) { 32 | FloatingActionButton( 33 | onClick = onClick, 34 | modifier = modifier 35 | ) { 36 | Icon( 37 | imageVector = Icons.Default.Add, 38 | contentDescription = stringResource(R.string.add_juice), 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/JuiceTrackerList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.foundation.clickable 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.PaddingValues 22 | import androidx.compose.foundation.layout.Row 23 | import androidx.compose.foundation.layout.fillMaxWidth 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.foundation.lazy.LazyColumn 26 | import androidx.compose.foundation.lazy.items 27 | import androidx.compose.material3.Icon 28 | import androidx.compose.material3.IconButton 29 | import androidx.compose.material3.MaterialTheme 30 | import androidx.compose.material3.Text 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.ui.Alignment 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.res.dimensionResource 35 | import androidx.compose.ui.res.painterResource 36 | import androidx.compose.ui.res.stringResource 37 | import androidx.compose.ui.text.font.FontWeight 38 | import androidx.compose.ui.tooling.preview.Preview 39 | import com.example.juicetracker.R 40 | import com.example.juicetracker.data.Juice 41 | 42 | @Composable 43 | fun JuiceTrackerList( 44 | juices: List, 45 | onDelete: (Juice) -> Unit, 46 | onUpdate: (Juice) -> Unit, 47 | modifier: Modifier = Modifier 48 | ) { 49 | LazyColumn( 50 | modifier = modifier, 51 | contentPadding = PaddingValues(vertical = dimensionResource(R.dimen.padding_small)), 52 | ) { 53 | items(items = juices) { juice -> 54 | JuiceTrackerListItem( 55 | juice = juice, 56 | onDelete = onDelete, 57 | modifier = Modifier 58 | .fillMaxWidth() 59 | .clickable { onUpdate(juice) } 60 | .padding( 61 | vertical = dimensionResource(R.dimen.padding_small), 62 | horizontal = dimensionResource(R.dimen.padding_medium) 63 | ) 64 | ) 65 | } 66 | } 67 | } 68 | 69 | @Composable 70 | fun JuiceTrackerListItem( 71 | juice: Juice, 72 | onDelete: (Juice) -> Unit, 73 | modifier: Modifier = Modifier 74 | ) { 75 | Row( 76 | modifier = modifier, 77 | horizontalArrangement = Arrangement.SpaceBetween 78 | ) { 79 | JuiceIcon(juice.color) 80 | JuiceDetails(juice, Modifier.weight(1f)) 81 | DeleteButton( 82 | onDelete = { 83 | onDelete(juice) 84 | }, 85 | modifier = Modifier.align(Alignment.Top) 86 | ) 87 | } 88 | } 89 | 90 | @Composable 91 | fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) { 92 | Column(modifier, verticalArrangement = Arrangement.Top) { 93 | Text( 94 | text = juice.name, 95 | style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold), 96 | ) 97 | Text(juice.description) 98 | RatingDisplay( 99 | rating = juice.rating, 100 | modifier = Modifier.padding(top = dimensionResource(R.dimen.padding_small)) 101 | ) 102 | } 103 | } 104 | 105 | @Composable 106 | fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) { 107 | IconButton( 108 | onClick = { onDelete() }, 109 | ) { 110 | Icon( 111 | modifier = modifier, 112 | painter = painterResource(R.drawable.delete_icon), 113 | contentDescription = stringResource(R.string.delete) 114 | ) 115 | } 116 | } 117 | 118 | @Preview 119 | @Composable 120 | fun JuiceTrackerListPreview() { 121 | MaterialTheme { 122 | JuiceTrackerList( 123 | juices = listOf( 124 | Juice(1, "Mango", "Yummy!", "Yellow", 5), 125 | Juice(2, "Orange", "Refreshing~", "Orange", 4), 126 | Juice(3, "Grape", "Refreshing~", "Magenta", 2), 127 | Juice(4, "Celery", "Refreshing~", "Green", 1), 128 | Juice(5, "ABC", "Refreshing~", "Red", 4) 129 | ), 130 | onDelete = {}, 131 | onUpdate = {}) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/JuiceTrackerTopBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.foundation.layout.padding 19 | import androidx.compose.material3.CenterAlignedTopAppBar 20 | import androidx.compose.material3.ExperimentalMaterial3Api 21 | import androidx.compose.material3.MaterialTheme 22 | import androidx.compose.material3.Text 23 | import androidx.compose.material3.TopAppBarDefaults 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.res.dimensionResource 27 | import androidx.compose.ui.res.stringResource 28 | import androidx.compose.ui.text.font.FontWeight 29 | import com.example.juicetracker.R 30 | 31 | @OptIn(ExperimentalMaterial3Api::class) 32 | @Composable 33 | fun JuiceTrackerTopAppBar(modifier: Modifier = Modifier) { 34 | CenterAlignedTopAppBar( 35 | modifier = modifier, 36 | colors = TopAppBarDefaults.largeTopAppBarColors( 37 | containerColor = MaterialTheme.colorScheme.background 38 | ), 39 | title = { 40 | Text( 41 | text = stringResource(R.string.app_name), 42 | modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium)), 43 | fontWeight = FontWeight.Bold, 44 | style = MaterialTheme.typography.titleLarge, 45 | color = MaterialTheme.colorScheme.onBackground 46 | ) 47 | } 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/homescreen/RatingDisplay.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.juicetracker.ui.homescreen 17 | 18 | import androidx.compose.foundation.Image 19 | import androidx.compose.foundation.layout.Row 20 | import androidx.compose.foundation.layout.size 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.res.pluralStringResource 25 | import androidx.compose.ui.semantics.contentDescription 26 | import androidx.compose.ui.semantics.semantics 27 | import androidx.compose.ui.unit.dp 28 | import com.example.juicetracker.R 29 | 30 | @Composable 31 | fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) { 32 | val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating) 33 | Row( 34 | // Content description is added here to support accessibility 35 | modifier.semantics { 36 | contentDescription = displayDescription 37 | } 38 | ) { 39 | repeat(rating) { 40 | // Star [contentDescription] is null as the image is for illustrative purpose 41 | Image( 42 | modifier = Modifier.size(32.dp), 43 | painter = painterResource(R.drawable.star), 44 | contentDescription = null 45 | ) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.juicetracker.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val md_theme_light_primary = Color(0xFF6750A4) 22 | val md_theme_light_onPrimary = Color(0xFFFFFFFF) 23 | val md_theme_light_primaryContainer = Color(0xFFEADDFF) 24 | val md_theme_light_onPrimaryContainer = Color(0xFF21005D) 25 | val md_theme_light_secondary = Color(0xFF625B71) 26 | val md_theme_light_onSecondary = Color(0xFFFFFFFF) 27 | val md_theme_light_secondaryContainer = Color(0xFFE8DEF8) 28 | val md_theme_light_onSecondaryContainer = Color(0xFF1D192B) 29 | val md_theme_light_tertiary = Color(0xFF7D5260) 30 | val md_theme_light_onTertiary = Color(0xFFFFFFFF) 31 | val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4) 32 | val md_theme_light_onTertiaryContainer = Color(0xFF31111D) 33 | val md_theme_light_error = Color(0xFFB3261E) 34 | val md_theme_light_onError = Color(0xFFFFFFFF) 35 | val md_theme_light_errorContainer = Color(0xFFF9DEDC) 36 | val md_theme_light_onErrorContainer = Color(0xFF410E0B) 37 | val md_theme_light_outline = Color(0xFF79747E) 38 | val md_theme_light_background = Color(0xFFFFFBFE) 39 | val md_theme_light_onBackground = Color(0xFF1C1B1F) 40 | val md_theme_light_surface = Color(0xFFFFFBFE) 41 | val md_theme_light_onSurface = Color(0xFF1C1B1F) 42 | val md_theme_light_surfaceVariant = Color(0xFFE7E0EC) 43 | val md_theme_light_onSurfaceVariant = Color(0xFF49454F) 44 | val md_theme_light_inverseSurface = Color(0xFF313033) 45 | val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4) 46 | val md_theme_light_inversePrimary = Color(0xFFD0BCFF) 47 | val md_theme_light_surfaceTint = Color(0xFF6750A4) 48 | val md_theme_light_outlineVariant = Color(0xFFCAC4D0) 49 | val md_theme_light_scrim = Color(0xFF000000) 50 | 51 | val md_theme_dark_primary = Color(0xFFD0BCFF) 52 | val md_theme_dark_onPrimary = Color(0xFF381E72) 53 | val md_theme_dark_primaryContainer = Color(0xFF4F378B) 54 | val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF) 55 | val md_theme_dark_secondary = Color(0xFFCCC2DC) 56 | val md_theme_dark_onSecondary = Color(0xFF332D41) 57 | val md_theme_dark_secondaryContainer = Color(0xFF4A4458) 58 | val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8) 59 | val md_theme_dark_tertiary = Color(0xFFEFB8C8) 60 | val md_theme_dark_onTertiary = Color(0xFF492532) 61 | val md_theme_dark_tertiaryContainer = Color(0xFF633B48) 62 | val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4) 63 | val md_theme_dark_error = Color(0xFFF2B8B5) 64 | val md_theme_dark_onError = Color(0xFF601410) 65 | val md_theme_dark_errorContainer = Color(0xFF8C1D18) 66 | val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC) 67 | val md_theme_dark_outline = Color(0xFF938F99) 68 | val md_theme_dark_background = Color(0xFF1C1B1F) 69 | val md_theme_dark_onBackground = Color(0xFFE6E1E5) 70 | val md_theme_dark_surface = Color(0xFF1C1B1F) 71 | val md_theme_dark_onSurface = Color(0xFFE6E1E5) 72 | val md_theme_dark_surfaceVariant = Color(0xFF49454F) 73 | val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0) 74 | val md_theme_dark_inverseSurface = Color(0xFFE6E1E5) 75 | val md_theme_dark_inverseOnSurface = Color(0xFF313033) 76 | val md_theme_dark_inversePrimary = Color(0xFF6750A4) 77 | val md_theme_dark_surfaceTint = Color(0xFFD0BCFF) 78 | val md_theme_dark_outlineVariant = Color(0xFF49454F) 79 | val md_theme_dark_scrim = Color(0xFF000000) 80 | 81 | val Orange = Color(0xFFFF8A00) 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.juicetracker.ui.theme 18 | 19 | import android.app.Activity 20 | import android.os.Build 21 | import androidx.compose.foundation.isSystemInDarkTheme 22 | import androidx.compose.material3.MaterialTheme 23 | import androidx.compose.material3.darkColorScheme 24 | import androidx.compose.material3.dynamicDarkColorScheme 25 | import androidx.compose.material3.dynamicLightColorScheme 26 | import androidx.compose.material3.lightColorScheme 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.SideEffect 29 | import androidx.compose.ui.graphics.toArgb 30 | import androidx.compose.ui.platform.LocalContext 31 | import androidx.compose.ui.platform.LocalView 32 | import androidx.core.view.WindowCompat 33 | 34 | private val LightColorScheme = lightColorScheme( 35 | primary = md_theme_light_primary, 36 | onPrimary = md_theme_light_onPrimary, 37 | primaryContainer = md_theme_light_primaryContainer, 38 | onPrimaryContainer = md_theme_light_onPrimaryContainer, 39 | secondary = md_theme_light_secondary, 40 | onSecondary = md_theme_light_onSecondary, 41 | secondaryContainer = md_theme_light_secondaryContainer, 42 | onSecondaryContainer = md_theme_light_onSecondaryContainer, 43 | tertiary = md_theme_light_tertiary, 44 | onTertiary = md_theme_light_onTertiary, 45 | tertiaryContainer = md_theme_light_tertiaryContainer, 46 | onTertiaryContainer = md_theme_light_onTertiaryContainer, 47 | error = md_theme_light_error, 48 | onError = md_theme_light_onError, 49 | errorContainer = md_theme_light_errorContainer, 50 | onErrorContainer = md_theme_light_onErrorContainer, 51 | outline = md_theme_light_outline, 52 | background = md_theme_light_background, 53 | onBackground = md_theme_light_onBackground, 54 | surface = md_theme_light_surface, 55 | onSurface = md_theme_light_onSurface, 56 | surfaceVariant = md_theme_light_surfaceVariant, 57 | onSurfaceVariant = md_theme_light_onSurfaceVariant, 58 | inverseSurface = md_theme_light_inverseSurface, 59 | inverseOnSurface = md_theme_light_inverseOnSurface, 60 | inversePrimary = md_theme_light_inversePrimary, 61 | surfaceTint = md_theme_light_surfaceTint, 62 | outlineVariant = md_theme_light_outlineVariant, 63 | scrim = md_theme_light_scrim, 64 | ) 65 | 66 | 67 | private val DarkColorScheme = darkColorScheme( 68 | primary = md_theme_dark_primary, 69 | onPrimary = md_theme_dark_onPrimary, 70 | primaryContainer = md_theme_dark_primaryContainer, 71 | onPrimaryContainer = md_theme_dark_onPrimaryContainer, 72 | secondary = md_theme_dark_secondary, 73 | onSecondary = md_theme_dark_onSecondary, 74 | secondaryContainer = md_theme_dark_secondaryContainer, 75 | onSecondaryContainer = md_theme_dark_onSecondaryContainer, 76 | tertiary = md_theme_dark_tertiary, 77 | onTertiary = md_theme_dark_onTertiary, 78 | tertiaryContainer = md_theme_dark_tertiaryContainer, 79 | onTertiaryContainer = md_theme_dark_onTertiaryContainer, 80 | error = md_theme_dark_error, 81 | onError = md_theme_dark_onError, 82 | errorContainer = md_theme_dark_errorContainer, 83 | onErrorContainer = md_theme_dark_onErrorContainer, 84 | outline = md_theme_dark_outline, 85 | background = md_theme_dark_background, 86 | onBackground = md_theme_dark_onBackground, 87 | surface = md_theme_dark_surface, 88 | onSurface = md_theme_dark_onSurface, 89 | surfaceVariant = md_theme_dark_surfaceVariant, 90 | onSurfaceVariant = md_theme_dark_onSurfaceVariant, 91 | inverseSurface = md_theme_dark_inverseSurface, 92 | inverseOnSurface = md_theme_dark_inverseOnSurface, 93 | inversePrimary = md_theme_dark_inversePrimary, 94 | surfaceTint = md_theme_dark_surfaceTint, 95 | outlineVariant = md_theme_dark_outlineVariant, 96 | scrim = md_theme_dark_scrim, 97 | ) 98 | 99 | @Composable 100 | fun JuiceTrackerTheme( 101 | darkTheme: Boolean = isSystemInDarkTheme(), 102 | // Dynamic color is available on Android 12+, set to false for training purposes 103 | dynamicColor: Boolean = false, 104 | content: @Composable () -> Unit 105 | ) { 106 | val colorScheme = when { 107 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 108 | val context = LocalContext.current 109 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 110 | } 111 | 112 | darkTheme -> DarkColorScheme 113 | else -> LightColorScheme 114 | } 115 | val view = LocalView.current 116 | if (!view.isInEditMode) { 117 | SideEffect { 118 | val window = (view.context as Activity).window 119 | window.statusBarColor = colorScheme.primary.toArgb() 120 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 121 | } 122 | } 123 | 124 | MaterialTheme( 125 | colorScheme = colorScheme, 126 | typography = Typography, 127 | content = content 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/juicetracker/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.juicetracker.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | // Set of Material typography styles to start with 26 | val Typography = Typography( 27 | bodyLarge = TextStyle( 28 | fontFamily = FontFamily.Default, 29 | fontWeight = FontWeight.Normal, 30 | fontSize = 16.sp, 31 | lineHeight = 24.sp, 32 | letterSpacing = 0.5.sp 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 16 | 22 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 58 | 61 | 64 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 118 | 119 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete_icon.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/juice_clear_icon.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/juice_color.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/star.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker/271e6fc190ab8540a669e9c11e176b7c8f08dc07/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 8dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | Juice Tracker 18 | Delete 19 | Juice Name 20 | Description 21 | Save 22 | Rating 23 | Color 24 | Add juice 25 | Types of juice 26 | %s juice 27 | 28 | %d star 29 | %d stars 30 | 31 | 32 | Red 33 | Blue 34 | Green 35 | Cyan 36 | Yellow 37 | Magenta 38 | Orange 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 |