├── .github ├── ci-gradle.properties └── workflows │ └── Check.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README-template.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── androiddevchallenge │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── androiddevchallenge │ │ │ ├── Flipper.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ui │ │ │ ├── AnimateFloatAsState.kt │ │ │ ├── flipclock │ │ │ │ ├── Flap.kt │ │ │ │ ├── FlapSection.kt │ │ │ │ ├── Flaps.kt │ │ │ │ └── FlipClock.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── utils │ │ │ └── TimeParts.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ └── ic_launcher.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.webp │ │ ├── mipmap-mdpi │ │ └── ic_launcher.webp │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.webp │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.webp │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── androiddevchallenge │ └── ExampleUnitTest.kt ├── build.gradle ├── debug.keystore ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ktlint ├── results ├── screenshot_1.png ├── screenshot_2.png └── video.mp4 ├── settings.gradle └── spotless └── copyright.kt /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Copyright 2020 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | org.gradle.daemon=false 19 | org.gradle.parallel=true 20 | org.gradle.jvmargs=-Xmx5120m 21 | org.gradle.workers.max=2 22 | 23 | kotlin.incremental=false 24 | kotlin.compiler.execution.strategy=in-process 25 | -------------------------------------------------------------------------------- /.github/workflows/Check.yaml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 30 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Copy CI gradle.properties 18 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 19 | 20 | - name: Set up JDK 11 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: 11 24 | 25 | - name: Build project 26 | run: ./gradlew spotlessCheck assembleDebug lintDebug --stacktrace 27 | 28 | - name: Upload build outputs (APKs) 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: build-outputs 32 | path: app/build/outputs 33 | 34 | - name: Upload build reports 35 | if: always() 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: build-reports 39 | path: app/build/reports 40 | 41 | test: 42 | needs: build 43 | runs-on: macOS-latest # enables hardware acceleration in the virtual machine 44 | timeout-minutes: 30 45 | strategy: 46 | matrix: 47 | api-level: [23, 26, 29] 48 | 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v2 52 | 53 | - name: Copy CI gradle.properties 54 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 55 | 56 | - name: Set up JDK 11 57 | uses: actions/setup-java@v1 58 | with: 59 | java-version: 11 60 | 61 | - name: Run instrumentation tests 62 | uses: reactivecircus/android-emulator-runner@v2 63 | with: 64 | api-level: ${{ matrix.api-level }} 65 | arch: x86 66 | disable-animations: true 67 | script: ./gradlew connectedCheck --stacktrace 68 | 69 | - name: Upload test reports 70 | if: always() 71 | uses: actions/upload-artifact@v2 72 | with: 73 | name: test-reports 74 | path: app/build/reports 75 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /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-template.md: -------------------------------------------------------------------------------- 1 | # Put title of your app here 2 | 3 | 4 | 5 | ![Workflow result](https://github.com///workflows/Check/badge.svg) 6 | 7 | 8 | ## :scroll: Description 9 | 10 | 11 | 12 | ## :bulb: Motivation and Context 13 | 14 | 15 | 16 | 17 | ## :camera_flash: Screenshots 18 | 19 | 20 | 21 | ## License 22 | ``` 23 | Copyright 2020 The Android Open Source Project 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | https://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flipper 2 | 3 | ![Workflow result](https://github.com/arkivanov/android-dev-challenge-compose-2/workflows/Check/badge.svg) 4 | 5 | ## :scroll: Description 6 | Flipper is a simple countdown app written using Jetpack Compose. #AndroidDevChallenge 7 | 8 | ## :bulb: Motivation and Context 9 | Flipper simulates the [Flip Clock](https://en.wikipedia.org/wiki/Flip_clock) using some custom animations. 10 | 11 | 12 | ## :camera_flash: Screenshots 13 | 14 | 15 | ## License 16 | ``` 17 | Copyright 2020 The Android Open Source Project 18 | 19 | Licensed under the Apache License, Version 2.0 (the "License"); 20 | you may not use this file except in compliance with the License. 21 | You may obtain a copy of the License at 22 | 23 | https://www.apache.org/licenses/LICENSE-2.0 24 | 25 | Unless required by applicable law or agreed to in writing, software 26 | distributed under the License is distributed on an "AS IS" BASIS, 27 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | See the License for the specific language governing permissions and 29 | limitations under the License. 30 | ``` 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle 3 | build/ 4 | 5 | captures 6 | 7 | /local.properties 8 | 9 | # IntelliJ .idea folder 10 | /.idea 11 | *.iml 12 | 13 | # General 14 | .DS_Store 15 | .externalNativeBuild -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 30 8 | 9 | defaultConfig { 10 | applicationId "com.example.androiddevchallenge" 11 | minSdkVersion 23 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | signingConfigs { 20 | // We use a bundled debug keystore, to allow debug builds from CI to be upgradable 21 | debug { 22 | storeFile rootProject.file('debug.keystore') 23 | storePassword 'android' 24 | keyAlias 'androiddebugkey' 25 | keyPassword 'android' 26 | } 27 | } 28 | 29 | buildTypes { 30 | debug { 31 | signingConfig signingConfigs.debug 32 | } 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | kotlinOptions { 40 | jvmTarget = "1.8" 41 | } 42 | 43 | buildFeatures { 44 | compose true 45 | 46 | // Disable unused AGP features 47 | buildConfig false 48 | aidl false 49 | renderScript false 50 | resValues false 51 | shaders false 52 | } 53 | 54 | composeOptions { 55 | kotlinCompilerExtensionVersion compose_version 56 | } 57 | 58 | packagingOptions { 59 | // Multiple dependency bring these files in. Exclude them to enable 60 | // our test APK to build (has no effect on our AARs) 61 | excludes += "/META-INF/AL2.0" 62 | excludes += "/META-INF/LGPL2.1" 63 | } 64 | } 65 | 66 | dependencies { 67 | implementation 'androidx.core:core-ktx:1.3.2' 68 | implementation 'androidx.appcompat:appcompat:1.2.0' 69 | implementation 'com.google.android.material:material:1.3.0' 70 | implementation "androidx.activity:activity-compose:1.3.0-alpha03" 71 | implementation "androidx.compose.ui:ui:$compose_version" 72 | implementation "androidx.compose.material:material:$compose_version" 73 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 74 | implementation "androidx.compose.ui:ui-tooling:$compose_version" 75 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0' 76 | 77 | testImplementation 'junit:junit:4.13.2' 78 | 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 80 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 81 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/androiddevchallenge/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge 17 | 18 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 19 | import androidx.test.ext.junit.runners.AndroidJUnit4 20 | import org.junit.Rule 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | 24 | /** 25 | * Instrumented test, which will execute on an Android device. 26 | * 27 | * See [testing documentation](http://d.android.com/tools/testing). 28 | */ 29 | @RunWith(AndroidJUnit4::class) 30 | class ExampleInstrumentedTest { 31 | @get:Rule 32 | val composeTestRule = createAndroidComposeRule() 33 | 34 | @Test 35 | fun sampleTest() { 36 | // Add instrumented tests here 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 15 | 16 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/Flipper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge 17 | 18 | import android.os.SystemClock 19 | import android.text.format.DateUtils 20 | import androidx.compose.foundation.layout.Arrangement 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.Spacer 23 | import androidx.compose.foundation.layout.height 24 | import androidx.compose.material.Icon 25 | import androidx.compose.material.IconButton 26 | import androidx.compose.material.MaterialTheme 27 | import androidx.compose.material.Text 28 | import androidx.compose.material.icons.Icons 29 | import androidx.compose.material.icons.filled.DarkMode 30 | import androidx.compose.material.icons.filled.LightMode 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.runtime.LaunchedEffect 33 | import androidx.compose.runtime.getValue 34 | import androidx.compose.runtime.mutableStateOf 35 | import androidx.compose.runtime.remember 36 | import androidx.compose.runtime.setValue 37 | import androidx.compose.ui.Alignment 38 | import androidx.compose.ui.Modifier 39 | import androidx.compose.ui.res.stringResource 40 | import androidx.compose.ui.text.font.FontWeight 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.unit.sp 43 | import com.example.androiddevchallenge.ui.flipclock.FlipClock 44 | import com.example.androiddevchallenge.ui.flipclock.FlipClockEvents 45 | import kotlinx.coroutines.Dispatchers 46 | import kotlinx.coroutines.delay 47 | import kotlinx.coroutines.withContext 48 | import kotlin.math.ceil 49 | import kotlin.math.max 50 | 51 | @Composable 52 | fun Flipper(onDarkThemeToggled: () -> Unit, modifier: Modifier = Modifier) { 53 | var endTime by remember { mutableStateOf(SystemClock.uptimeMillis()) } 54 | var remainingSeconds by remember { mutableStateOf(0) } 55 | 56 | fun updateRemainingTime() { 57 | remainingSeconds = ceil(max(endTime - SystemClock.uptimeMillis(), 0L).toFloat() / 1000F).toInt() 58 | } 59 | 60 | fun addTime(millis: Long) { 61 | endTime = max(endTime, SystemClock.uptimeMillis()) + millis 62 | updateRemainingTime() 63 | } 64 | 65 | LaunchedEffect(Unit) { 66 | withContext(Dispatchers.Main) { 67 | while (true) { 68 | updateRemainingTime() 69 | delay(100L) 70 | } 71 | } 72 | } 73 | 74 | Column( 75 | modifier = modifier, 76 | verticalArrangement = Arrangement.Center, 77 | horizontalAlignment = Alignment.CenterHorizontally 78 | ) { 79 | Text( 80 | text = stringResource(R.string.app_name), 81 | fontSize = 36.sp, 82 | fontWeight = FontWeight.Medium 83 | ) 84 | 85 | Spacer(modifier = Modifier.height(48.dp)) 86 | 87 | FlipClock( 88 | seconds = remainingSeconds, 89 | endMillis = endTime, 90 | events = FlipClockEvents( 91 | onHoursIncrement = { addTime(DateUtils.HOUR_IN_MILLIS) }, 92 | onHoursDecrement = { addTime(-DateUtils.HOUR_IN_MILLIS) }, 93 | onMinutesIncrement = { addTime(DateUtils.MINUTE_IN_MILLIS) }, 94 | onMinutesDecrement = { addTime(-DateUtils.MINUTE_IN_MILLIS) }, 95 | onSecondsIncrement = { addTime(DateUtils.SECOND_IN_MILLIS) }, 96 | onSecondsDecrement = { addTime(-DateUtils.SECOND_IN_MILLIS) } 97 | ) 98 | ) 99 | 100 | Spacer(modifier = Modifier.height(48.dp)) 101 | 102 | IconButton(onClick = onDarkThemeToggled) { 103 | Icon( 104 | imageVector = if (MaterialTheme.colors.isLight) Icons.Default.DarkMode else Icons.Default.LightMode, 105 | contentDescription = "Dark theme button" 106 | ) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge 17 | 18 | import android.os.Bundle 19 | import androidx.activity.compose.setContent 20 | import androidx.appcompat.app.AppCompatActivity 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxSize 23 | import androidx.compose.material.MaterialTheme 24 | import androidx.compose.material.Surface 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.runtime.getValue 27 | import androidx.compose.runtime.mutableStateOf 28 | import androidx.compose.runtime.remember 29 | import androidx.compose.runtime.setValue 30 | import androidx.compose.ui.Modifier 31 | import com.example.androiddevchallenge.ui.theme.MyTheme 32 | 33 | class MainActivity : AppCompatActivity() { 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | 37 | setContent { 38 | var darkTheme by remember { mutableStateOf(false) } 39 | 40 | MyTheme(darkTheme = darkTheme) { 41 | MyApp(onDarkThemeToggled = { darkTheme = !darkTheme }) 42 | } 43 | } 44 | } 45 | } 46 | 47 | // Start building your app here! 48 | @Composable 49 | fun MyApp(onDarkThemeToggled: () -> Unit) { 50 | Surface(color = MaterialTheme.colors.background) { 51 | Column { 52 | Flipper(onDarkThemeToggled = onDarkThemeToggled, modifier = Modifier.fillMaxSize()) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/AnimateFloatAsState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui 17 | 18 | import androidx.compose.animation.core.AnimationState 19 | import androidx.compose.animation.core.Spring 20 | import androidx.compose.animation.core.animateTo 21 | import androidx.compose.animation.core.isFinished 22 | import androidx.compose.animation.core.spring 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.LaunchedEffect 25 | import androidx.compose.runtime.State 26 | import androidx.compose.runtime.remember 27 | 28 | @Composable 29 | fun animateFloatAsState(key: Any?, targetValue: Float): State { 30 | val animSpec = remember { spring(stiffness = Spring.StiffnessLow) } 31 | val animationState = remember(key) { AnimationState(targetValue) } 32 | 33 | LaunchedEffect(key, targetValue, animSpec) { 34 | animationState.animateTo( 35 | targetValue = targetValue, 36 | animationSpec = animSpec, 37 | sequentialAnimation = !animationState.isFinished 38 | ) 39 | } 40 | 41 | return animationState 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/flipclock/Flap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.flipclock 17 | 18 | import androidx.compose.foundation.background 19 | import androidx.compose.foundation.layout.Box 20 | import androidx.compose.foundation.layout.IntrinsicSize 21 | import androidx.compose.foundation.layout.offset 22 | import androidx.compose.foundation.layout.requiredHeight 23 | import androidx.compose.foundation.layout.requiredSize 24 | import androidx.compose.foundation.layout.requiredWidth 25 | import androidx.compose.foundation.shape.RoundedCornerShape 26 | import androidx.compose.material.Card 27 | import androidx.compose.material.MaterialTheme 28 | import androidx.compose.material.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.text.font.FontFamily 34 | import androidx.compose.ui.unit.Dp 35 | import androidx.compose.ui.unit.dp 36 | import androidx.compose.ui.unit.sp 37 | 38 | private val FLAP_WIDTH = 96.dp 39 | private val FLAP_HEIGHT = 36.dp 40 | private val BACKGROUND_COLOR_LIGHT = Color(red = 0xC0, green = 0xC0, blue = 0xC0) 41 | private val BACKGROUND_COLOR_DARK = Color(red = 0x20, green = 0x20, blue = 0x20) 42 | private val TOP_FLAP_SHAPE = RoundedCornerShape(4.dp, 4.dp, 0.dp, 0.dp) 43 | private val BOTTOM_FLAP_SHAPE = RoundedCornerShape(0.dp, 0.dp, 4.dp, 4.dp) 44 | 45 | @Composable 46 | fun Flap( 47 | text: String, 48 | position: FlapPosition, 49 | elevation: Dp, 50 | modifier: Modifier = Modifier 51 | ) { 52 | Card( 53 | modifier = modifier.requiredSize(width = FLAP_WIDTH, height = FLAP_HEIGHT), 54 | elevation = elevation, 55 | shape = when (position) { 56 | FlapPosition.TOP -> TOP_FLAP_SHAPE 57 | FlapPosition.BOTTOM -> BOTTOM_FLAP_SHAPE 58 | } 59 | ) { 60 | Box( 61 | modifier = Modifier.background(if (MaterialTheme.colors.isLight) BACKGROUND_COLOR_LIGHT else BACKGROUND_COLOR_DARK), 62 | contentAlignment = Alignment.Center 63 | ) { 64 | Text( 65 | text = text, 66 | fontFamily = FontFamily.SansSerif, 67 | fontSize = 64.sp, 68 | modifier = Modifier 69 | .requiredWidth(IntrinsicSize.Min) 70 | .requiredHeight(IntrinsicSize.Min) 71 | .run { 72 | when (position) { 73 | FlapPosition.TOP -> offset(y = FLAP_HEIGHT / 2) 74 | FlapPosition.BOTTOM -> offset(y = FLAP_HEIGHT / -2) 75 | } 76 | } 77 | .offset(y = (-2).dp) 78 | ) 79 | } 80 | } 81 | } 82 | 83 | enum class FlapPosition { 84 | TOP, BOTTOM 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/flipclock/FlapSection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.flipclock 17 | 18 | import androidx.compose.foundation.layout.Column 19 | import androidx.compose.foundation.layout.Row 20 | import androidx.compose.foundation.layout.Spacer 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.material.Icon 23 | import androidx.compose.material.IconButton 24 | import androidx.compose.material.Text 25 | import androidx.compose.material.icons.Icons 26 | import androidx.compose.material.icons.filled.Add 27 | import androidx.compose.material.icons.filled.Remove 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.ui.Alignment 30 | import androidx.compose.ui.Modifier 31 | import androidx.compose.ui.unit.dp 32 | import androidx.compose.ui.unit.sp 33 | 34 | @Composable 35 | fun FlapSection( 36 | title: String, 37 | currentValue: Int, 38 | nextValue: Int, 39 | factor: Float, 40 | onIncrement: () -> Unit, 41 | onDecrement: () -> Unit 42 | ) { 43 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 44 | Text(text = title, fontSize = 20.sp) 45 | 46 | Spacer(modifier = Modifier.height(8.dp)) 47 | 48 | Flaps( 49 | currentText = currentValue.toString().padStart(2, '0'), 50 | nextText = nextValue.toString().padStart(2, '0'), 51 | factor = factor 52 | ) 53 | 54 | Spacer(modifier = Modifier.height(8.dp)) 55 | 56 | Row { 57 | IconButton(onClick = onDecrement) { 58 | Icon(imageVector = Icons.Default.Remove, contentDescription = "$title decrement button") 59 | } 60 | 61 | IconButton(onClick = onIncrement) { 62 | Icon(imageVector = Icons.Default.Add, contentDescription = "$title increment button") 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/flipclock/Flaps.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.flipclock 17 | 18 | import androidx.compose.foundation.layout.Box 19 | import androidx.compose.foundation.layout.Column 20 | import androidx.compose.foundation.layout.Spacer 21 | import androidx.compose.foundation.layout.requiredHeight 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.graphics.TransformOrigin 26 | import androidx.compose.ui.graphics.graphicsLayer 27 | import androidx.compose.ui.unit.dp 28 | 29 | private val FLAP_ELEVATION = 8.dp 30 | 31 | @Composable 32 | fun Flaps(currentText: String, nextText: String, factor: Float) { 33 | Box(contentAlignment = Alignment.Center) { 34 | Column(modifier = Modifier.align(Alignment.Center)) { 35 | Box { 36 | Flap( 37 | text = nextText, 38 | position = FlapPosition.TOP, 39 | elevation = FLAP_ELEVATION 40 | ) 41 | 42 | if (factor < 0.5F) { 43 | val f = factor * 2F 44 | 45 | Flap( 46 | text = currentText, 47 | position = FlapPosition.TOP, 48 | elevation = FLAP_ELEVATION * f, 49 | modifier = Modifier.graphicsLayer( 50 | rotationX = -90F * f, 51 | transformOrigin = TransformOrigin(pivotFractionX = 0.5F, pivotFractionY = 1F) 52 | ) 53 | ) 54 | } 55 | } 56 | 57 | Spacer(modifier = Modifier.requiredHeight(1.dp)) 58 | 59 | Box { 60 | Flap( 61 | text = currentText, 62 | position = FlapPosition.BOTTOM, 63 | elevation = FLAP_ELEVATION 64 | ) 65 | 66 | if (factor >= 0.5F) { 67 | val f = (1F - factor) * 2F 68 | 69 | Flap( 70 | text = nextText, 71 | position = FlapPosition.BOTTOM, 72 | elevation = FLAP_ELEVATION * f, 73 | modifier = Modifier.graphicsLayer( 74 | rotationX = 90F * f, 75 | transformOrigin = TransformOrigin(pivotFractionX = 0.5F, pivotFractionY = 0F) 76 | ) 77 | ) 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/flipclock/FlipClock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.flipclock 17 | 18 | import androidx.compose.foundation.layout.Row 19 | import androidx.compose.foundation.layout.Spacer 20 | import androidx.compose.foundation.layout.width 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.unit.dp 26 | import com.example.androiddevchallenge.R 27 | import com.example.androiddevchallenge.ui.animateFloatAsState 28 | import com.example.androiddevchallenge.utils.getTimeParts 29 | import kotlin.math.ceil 30 | 31 | @Composable 32 | fun FlipClock( 33 | seconds: Int, 34 | endMillis: Long, 35 | events: FlipClockEvents 36 | ) { 37 | val animatedSeconds by animateFloatAsState(key = endMillis, targetValue = seconds.toFloat()) 38 | 39 | val currentSeconds = ceil(animatedSeconds).toInt() 40 | val nextSeconds = currentSeconds - 1 41 | val factor = currentSeconds.toFloat() - animatedSeconds 42 | val currentParts = getTimeParts(currentSeconds) 43 | val nextParts = getTimeParts(nextSeconds) 44 | 45 | Row { 46 | FlapSection( 47 | title = stringResource(R.string.hours), 48 | currentValue = currentParts.hours, 49 | nextValue = nextParts.hours, 50 | factor = if (currentParts.hours == nextParts.hours) 0F else factor, 51 | onIncrement = events.onHoursIncrement, 52 | onDecrement = events.onHoursDecrement 53 | ) 54 | 55 | Spacer(modifier = Modifier.width(16.dp)) 56 | 57 | FlapSection( 58 | title = stringResource(R.string.minutes), 59 | currentValue = currentParts.minutes, 60 | nextValue = nextParts.minutes, 61 | factor = if (currentParts.minutes == nextParts.minutes) 0F else factor, 62 | onIncrement = events.onMinutesIncrement, 63 | onDecrement = events.onMinutesDecrement 64 | ) 65 | 66 | Spacer(modifier = Modifier.width(16.dp)) 67 | 68 | FlapSection( 69 | title = stringResource(R.string.seconds), 70 | currentValue = currentParts.seconds, 71 | nextValue = nextParts.seconds, 72 | factor = if (currentParts.seconds == nextParts.seconds) 0F else factor, 73 | onIncrement = events.onSecondsIncrement, 74 | onDecrement = events.onSecondsDecrement 75 | ) 76 | } 77 | } 78 | 79 | class FlipClockEvents( 80 | val onHoursIncrement: () -> Unit, 81 | val onHoursDecrement: () -> Unit, 82 | val onMinutesIncrement: () -> Unit, 83 | val onMinutesDecrement: () -> Unit, 84 | val onSecondsIncrement: () -> Unit, 85 | val onSecondsDecrement: () -> Unit, 86 | ) 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.theme 17 | 18 | import androidx.compose.ui.graphics.Color 19 | 20 | val purple200 = Color(0xFFBB86FC) 21 | val purple500 = Color(0xFF6200EE) 22 | val purple700 = Color(0xFF3700B3) 23 | val teal200 = Color(0xFF03DAC5) 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.theme 17 | 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.material.Shapes 20 | import androidx.compose.ui.unit.dp 21 | 22 | val shapes = Shapes( 23 | small = RoundedCornerShape(4.dp), 24 | medium = RoundedCornerShape(4.dp), 25 | large = RoundedCornerShape(0.dp) 26 | ) 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.theme 17 | 18 | import androidx.compose.foundation.isSystemInDarkTheme 19 | import androidx.compose.material.MaterialTheme 20 | import androidx.compose.material.darkColors 21 | import androidx.compose.material.lightColors 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.graphics.Color 24 | 25 | private val DarkColorPalette = darkColors( 26 | primary = purple200, 27 | primaryVariant = purple700, 28 | secondary = teal200, 29 | background = Color(red = 0x30, green = 0x30, blue = 0x30), 30 | surface = Color(red = 0x30, green = 0x30, blue = 0x30) 31 | ) 32 | 33 | private val LightColorPalette = lightColors( 34 | primary = purple500, 35 | primaryVariant = purple700, 36 | secondary = teal200 37 | 38 | /* Other default colors to override 39 | background = Color.White, 40 | surface = Color.White, 41 | onPrimary = Color.White, 42 | onSecondary = Color.Black, 43 | onBackground = Color.Black, 44 | onSurface = Color.Black, 45 | */ 46 | ) 47 | 48 | @Composable 49 | fun MyTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 50 | val colors = if (darkTheme) { 51 | DarkColorPalette 52 | } else { 53 | LightColorPalette 54 | } 55 | 56 | MaterialTheme( 57 | colors = colors, 58 | typography = typography, 59 | shapes = shapes, 60 | content = content 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.ui.theme 17 | 18 | import androidx.compose.material.Typography 19 | import androidx.compose.ui.text.TextStyle 20 | import androidx.compose.ui.text.font.FontFamily 21 | import androidx.compose.ui.text.font.FontWeight 22 | import androidx.compose.ui.unit.sp 23 | 24 | // Set of Material typography styles to start with 25 | val typography = Typography( 26 | body1 = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Normal, 29 | fontSize = 16.sp 30 | ) 31 | /* Other default text styles to override 32 | button = TextStyle( 33 | fontFamily = FontFamily.Default, 34 | fontWeight = FontWeight.W500, 35 | fontSize = 14.sp 36 | ), 37 | caption = TextStyle( 38 | fontFamily = FontFamily.Default, 39 | fontWeight = FontWeight.Normal, 40 | fontSize = 12.sp 41 | ) 42 | */ 43 | ) 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddevchallenge/utils/TimeParts.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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.androiddevchallenge.utils 17 | 18 | fun getTimeParts(seconds: Int): TimeParts { 19 | val partHours = seconds / 60 / 60 20 | val partMinutes = (seconds - partHours * 60 * 60) / 60 21 | val partSeconds = seconds - partHours * 60 * 60 - partMinutes * 60 22 | 23 | return TimeParts( 24 | hours = partHours, 25 | minutes = partMinutes, 26 | seconds = partSeconds 27 | ) 28 | } 29 | 30 | class TimeParts( 31 | val hours: Int, 32 | val minutes: Int, 33 | val seconds: Int 34 | ) 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 11 | 17 | 18 | 19 | 25 | 28 | 31 | 32 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 17 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 181 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkivanov/android-dev-challenge-compose-2/9582ce0aded80768b9d7e62b0f48c5d712fb8b99/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkivanov/android-dev-challenge-compose-2/9582ce0aded80768b9d7e62b0f48c5d712fb8b99/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkivanov/android-dev-challenge-compose-2/9582ce0aded80768b9d7e62b0f48c5d712fb8b99/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkivanov/android-dev-challenge-compose-2/9582ce0aded80768b9d7e62b0f48c5d712fb8b99/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkivanov/android-dev-challenge-compose-2/9582ce0aded80768b9d7e62b0f48c5d712fb8b99/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | #FFBB86FC 14 | #FF6200EE 15 | #FF3700B3 16 | #FF03DAC5 17 | #FF018786 18 | #FF000000 19 | #FFFFFFFF 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | Flipper 13 | Hours 14 | Minutes 15 | Seconds 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 26 | 27 | 31 | 32 |