├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-lifelike ├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── example │ │ └── lifelike │ │ ├── DeepLink.kt │ │ ├── MainActivity.kt │ │ ├── composable │ │ ├── LoggedIn.kt │ │ ├── LoggedOut.kt │ │ ├── Root.kt │ │ ├── common │ │ │ └── BigButton.kt │ │ ├── loggedin │ │ │ ├── AlbumList.kt │ │ │ ├── FullScreenPhoto.kt │ │ │ ├── Gallery.kt │ │ │ ├── Menu.kt │ │ │ ├── News.kt │ │ │ ├── PhotosOfAlbum.kt │ │ │ └── Profile.kt │ │ └── loggedout │ │ │ ├── RegConfirmSmsCode.kt │ │ │ ├── RegFinal.kt │ │ │ ├── RegUserName.kt │ │ │ ├── RegUserPhone.kt │ │ │ ├── Splash.kt │ │ │ └── common │ │ │ └── RegFlowPanel.kt │ │ └── entity │ │ ├── Album.kt │ │ ├── Photo.kt │ │ └── User.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ └── placeholder.png │ ├── 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 │ ├── colors.xml │ ├── material_colors.xml │ ├── strings.xml │ └── styles.xml ├── app-nested-containers ├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── example │ │ └── nestedcontainers │ │ ├── DeepLink.kt │ │ ├── MainActivity.kt │ │ └── composable │ │ └── SomeChild.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.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 │ ├── colors.xml │ ├── material_colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── router ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── zsoltk │ └── compose │ ├── backpress │ └── BackPressHandler.kt │ ├── router │ ├── BackStack.kt │ └── Router.kt │ └── savedinstancestate │ ├── BundleScope.kt │ ├── Persistent.kt │ └── SavedInstanceState.kt └── settings.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | pull_request: 7 | branches: 8 | - "*" 9 | 10 | jobs: 11 | validation: 12 | name: Validate Gradle Wrapper 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout latest code 16 | uses: actions/checkout@v2 17 | - name: Validate Gradle Wrapper 18 | uses: gradle/wrapper-validation-action@v1 19 | gradle: 20 | name: Build Gradle Tasks 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest, macos-latest, windows-latest] 24 | runs-on: ${{ matrix.os }} 25 | if: ${{ !contains(github.event.head_commit.message, 'ci skip') }} 26 | steps: 27 | - name: Set up JDK 11 28 | uses: actions/setup-java@v1 29 | with: 30 | java-version: 11 31 | - name: Checkout Repo 32 | uses: actions/checkout@v2 33 | - name: Cache Gradle Caches 34 | uses: actions/cache@v1 35 | with: 36 | path: ~/.gradle/caches/ 37 | key: cache-gradle-cache 38 | - name: Cache Gradle Wrapper 39 | uses: actions/cache@v1 40 | with: 41 | path: ~/.gradle/wrapper/ 42 | key: cache-gradle-wrapper 43 | - name: Run Gradle tasks 44 | run: ./gradlew build --continue 45 | - name: Stop Gradle 46 | run: ./gradlew --stop 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Zsolt Kocsi 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This project is now deprecated. No more development will be taking place here. 4 | 5 | The successor of this project is [ Appyx](https://github.com/bumble-tech/appyx). Check it out! 6 | 7 | 8 | 9 | ## compose-router 10 | 11 | [![Build](https://github.com/zsoltk/compose-router/workflows/Build/badge.svg)](https://github.com/zsoltk/compose-router/actions) 12 | [![Version](https://jitpack.io/v/zsoltk/compose-router.svg)](https://jitpack.io/#zsoltk/compose-router) 13 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 14 | 15 | ![logo](https://i.imgur.com/kKcAHa3.png) 16 | 17 | 18 | ## What's this? 19 | 20 | Routing functionality for Jetpack Compose with back stack: 21 | 22 | - Helps to map your whole app structure using Compose — not just the UI parts 23 | - Supports a single-Activity approach — no Fragments, no Navigation component needed 24 | - Simply branch on current routing and compose any other @Composable 25 | - Back stack saves the history of routing 26 | - Can be integrated with automatic back press handling to go back in screen history 27 | - Can be integrated with automatic scoped `savedInstanceState` persistence 28 | - Supports routing based on deep links (POC impl) 29 | 30 | Compatible with Compose version **1.0** 31 | 32 | ## Sample apps 33 | 34 | 1. **Sample module #1** - [app-lifelike](app-lifelike) — Displays a registration flow + logged in content with back stack 35 | 36 | 2. **Sample module #2** - [app-nested-containers](app-nested-containers) — Displays nested screen history on generated levels. 37 | 38 | 3. **Jetnews** - [fork](https://github.com/zsoltk/compose-samples) — Built with `compose-router`, adding proper screen history functionality. 39 | 40 | 4. **Pokedex** - [compose-pokedex](https://github.com/zsoltk/compose-pokedex) — Using `compose-router` for app structure. 41 | 42 | ## Download 43 | 44 | Available through jitpack. 45 | 46 | Add the maven repo to your root `build.gradle` 47 | 48 | ```groovy 49 | allprojects { 50 | repositories { 51 | maven { url 'https://jitpack.io' } 52 | } 53 | } 54 | ``` 55 | 56 | Add the dependency: 57 | 58 | ```groovy 59 | implementation 'com.github.zsoltk:compose-router:{latest-version}' 60 | ``` 61 | 62 | ## How to use 63 | 64 | On any level where routing functionality is needed, create a sealed class to represent your routing: 65 | 66 | ```kotlin 67 | sealed class Routing { 68 | object AlbumList : Routing() 69 | data class PhotosOfAlbum(val album: Album) : Routing() 70 | data class FullScreenPhoto(val photo: Photo) : Routing() 71 | } 72 | ``` 73 | 74 | Use the `Router` Composable and enjoy back stack functionality: 75 | 76 | ```kotlin 77 | @Composable 78 | fun GalleryView(defaultRouting: Routing) { 79 | Router("GalleryView", defaultRouting) { backStack -> 80 | // compose further based on current routing: 81 | when (val routing = backStack.last()) { 82 | is Routing.AlbumList -> AlbumList.Content( 83 | onAlbumSelected = { 84 | // add a new routing to the back stack: 85 | backStack.push(Routing.PhotosOfAlbum(it)) 86 | }) 87 | 88 | is Routing.PhotosOfAlbum -> PhotosOfAlbum.Content( 89 | album = routing.album, 90 | onPhotoSelected = { 91 | // add a new routing to the back stack: 92 | backStack.push(Routing.FullScreenPhoto(it)) 93 | }) 94 | 95 | is Routing.FullScreenPhoto -> FullScreenPhoto.Content( 96 | photo = routing.photo 97 | ) 98 | } 99 | } 100 | } 101 | ``` 102 | 103 | For more usage examples see the example apps. 104 | 105 | To go back in the back stack, you can either call the `.pop()` method programmatically, or just press the back button on the device (see next section for back press integration). 106 | 107 | Back stack operations: 108 | 109 | - **push()** 110 | - **pushAndDropNested()** 111 | - **pop()** 112 | - **replace()** 113 | - **newRoot()** 114 | 115 | ## Connect it to back press event 116 | 117 | To ensure that back press automatically pops the back stack and restores history, add this to your Activity: 118 | 119 | ```kotlin 120 | class MainActivity : AppCompatActivity() { 121 | private val backPressHandler = BackPressHandler() 122 | 123 | override fun onCreate(savedInstanceState: Bundle?) { 124 | super.onCreate(savedInstanceState) 125 | setContent { 126 | Providers( 127 | LocalBackPressHandler provides backPressHandler 128 | ) { 129 | // Your root composable goes here 130 | } 131 | } 132 | } 133 | 134 | override fun onBackPressed() { 135 | if (!backPressHandler.handle()) { 136 | super.onBackPressed() 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | ## Connect it to savedInstanceState 143 | 144 | Router can automatically add scoped Bundle support for your client code. 145 | 146 | Minimal setup: 147 | 148 | ```kotlin 149 | class MainActivity : AppCompatActivity() { 150 | override fun onCreate(savedInstanceState: Bundle?) { 151 | super.onCreate(savedInstanceState) 152 | setContent { 153 | MaterialTheme { 154 | BundleScope(savedInstanceState) { 155 | // Your root composable goes here 156 | } 157 | } 158 | } 159 | } 160 | 161 | override fun onSaveInstanceState(outState: Bundle) { 162 | super.onSaveInstanceState(outState) 163 | outState.saveLocal() 164 | } 165 | } 166 | ``` 167 | 168 | In client code you can now use: 169 | 170 | ```kotlin 171 | @Composable 172 | fun Content() { 173 | var counter by persistentInt("counter", 0) 174 | 175 | Clickable(onClick = { counter++ }) { 176 | Text("Counter value saved/restored from bundle: $counter") 177 | } 178 | } 179 | ``` 180 | 181 | ## Routing from deep links 182 | 183 | _Note: this is even more of a proof-of-concept only implementation than the other parts._ 184 | 185 | ### Example 1 186 | 187 | Build and install `app-lifelike` on your device. 188 | 189 | Open a console and type: 190 | 191 | ``` 192 | adb shell 'am start -a "android.intent.action.VIEW" -d "app-lifelike://go-to-profile?name=fake&phone=123123"' 193 | ``` 194 | 195 | This will open `app-lifelike` with skipped registration flow and go directly to Profile screen with fake user: 196 | 197 | ![](https://i.imgur.com/XomlkS3.png) 198 | 199 | ### Example 2 200 | 201 | Build and install `app-nested-containers` on your device. 202 | 203 | Open a console and type: 204 | 205 | ``` 206 | adb shell 'am start -a "android.intent.action.VIEW" -d "app-nested://default/BGR"' 207 | ``` 208 | 209 | This will open `app-nested-containers` with (B)lue / (G)reen / (R)ed subtrees pre-selected as routing: 210 | 211 | ![](https://i.imgur.com/d7agB8D.png) 212 | 213 | See `MainActivity.kt`, `AndroidManifest.xml`, and `DeepLink.kt` in both sample apps to see usage example. 214 | -------------------------------------------------------------------------------- /app-lifelike/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app-lifelike/README.md: -------------------------------------------------------------------------------- 1 | **What you're seeing here** 2 | - There's a whole app hierarchy represented solely with Compose 3 | - Any level that needs a back stack can have one 4 | - `Root` level switches between `LoggedOut` / `LoggedIn` routing 5 | - Once logged in, `LoggedOut` flow is removed from back stack, you cannot go back by pressing back button 6 | - `LoggedOut` flow has its own routing of registration screens with its own back stack 7 | - `Loggedin` subtree has its own routing of `Menu` + main screens (`Gallery`, `News`, `Profile`), with back stack for demonstration purposes 8 | - `Gallery` subtree has its own routing with `Album list`, `Album view`, `Photo view` with back stack 9 | - Once there's no more back stack to pop on back press, Android default action happens (Activity finishes) 10 | 11 | ![](https://i.imgur.com/4h22NyZ.gif) 12 | -------------------------------------------------------------------------------- /app-lifelike/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion 31 9 | buildToolsVersion "30.0.3" 10 | 11 | defaultConfig { 12 | applicationId "com.example.lifelike" 13 | minSdkVersion 24 14 | targetSdkVersion 30 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | buildFeatures { 32 | compose true 33 | } 34 | composeOptions { 35 | kotlinCompilerVersion kotlin_version 36 | kotlinCompilerExtensionVersion compose_version 37 | } 38 | kotlinOptions { 39 | jvmTarget = "1.8" 40 | freeCompilerArgs += ["-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check"] 41 | } 42 | } 43 | 44 | dependencies { 45 | // kotlin 46 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 47 | 48 | // android 49 | implementation 'androidx.core:core-ktx:1.6.0' 50 | implementation 'androidx.appcompat:appcompat:1.3.1' 51 | implementation 'androidx.activity:activity-compose:1.4.0-alpha02' 52 | 53 | // jetpack compose 54 | implementation "androidx.compose.ui:ui:$compose_version" 55 | implementation "androidx.compose.foundation:foundation-layout:$compose_version" 56 | implementation "androidx.compose.material:material:$compose_version" 57 | implementation "androidx.compose.ui:ui-tooling:$compose_version" 58 | implementation "androidx.compose.foundation:foundation:$compose_version" 59 | 60 | // compose-router module in the local project scope 61 | implementation project(":router") 62 | 63 | // testing 64 | testImplementation 'junit:junit:4.13.2' 65 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 67 | } 68 | -------------------------------------------------------------------------------- /app-lifelike/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 22 | -------------------------------------------------------------------------------- /app-lifelike/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/DeepLink.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.util.Log 6 | import com.example.lifelike.composable.LoggedIn 7 | import com.example.lifelike.composable.Root 8 | import com.example.lifelike.entity.User 9 | 10 | // TODO: implement your own validation / conversion 11 | fun Intent.deepLinkRoute(): List = 12 | when (data?.host) { 13 | // adb shell 'am start -a "android.intent.action.VIEW" -d "app-lifelike://go-to-profile?name=fake&phone=123123"' 14 | "go-to-profile" -> parseProfileDeepLink(data!!) 15 | null -> emptyList() 16 | else -> emptyList().also { 17 | Log.e("compose-router", "Unexpected deep link: $data") 18 | } 19 | } 20 | 21 | private fun parseProfileDeepLink(data: Uri): List { 22 | // Bear in mind that this is only a proof of concept implementation. 23 | // In real life you definitely don't want to do it like this. 24 | val name = data.getQueryParameter("name") ?: "" 25 | val phone = data.getQueryParameter("phone") ?: "" 26 | val user = User(name, phone) 27 | 28 | return listOf( 29 | // TODO support waiting for a routing to happen (e.g. user to authenticate by themselves) 30 | // before moving on with the rest 31 | Root.Routing.LoggedIn(user), 32 | LoggedIn.Routing.Profile 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.compose.foundation.ExperimentalFoundationApi 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.runtime.CompositionLocalProvider 9 | import com.example.lifelike.composable.Root 10 | import com.example.lifelike.composable.Root.Routing.LoggedOut 11 | import com.github.zsoltk.compose.backpress.BackPressHandler 12 | import com.github.zsoltk.compose.backpress.LocalBackPressHandler 13 | import com.github.zsoltk.compose.router.LocalRouting 14 | import com.github.zsoltk.compose.savedinstancestate.BundleScope 15 | import com.github.zsoltk.compose.savedinstancestate.saveAmbient 16 | import com.github.zsoltk.compose.savedinstancestate.saveLocal 17 | 18 | class MainActivity : AppCompatActivity() { 19 | 20 | private val backPressHandler = BackPressHandler() 21 | 22 | @ExperimentalFoundationApi 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | 26 | setContent { 27 | MaterialTheme { 28 | CompositionLocalProvider( 29 | LocalBackPressHandler provides backPressHandler, 30 | LocalRouting provides intent.deepLinkRoute() 31 | ) { 32 | BundleScope(savedInstanceState) { 33 | Root.Content(LoggedOut) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | override fun onBackPressed() { 41 | if (!backPressHandler.handle()) { 42 | super.onBackPressed() 43 | } 44 | } 45 | 46 | override fun onSaveInstanceState(outState: Bundle) { 47 | super.onSaveInstanceState(outState) 48 | outState.saveLocal() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/LoggedIn.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.wrapContentSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import com.example.lifelike.composable.loggedin.Gallery 10 | import com.example.lifelike.composable.loggedin.Gallery.Routing.AlbumList 11 | import com.example.lifelike.composable.loggedin.Menu 12 | import com.example.lifelike.composable.loggedin.News 13 | import com.example.lifelike.composable.loggedin.Profile 14 | import com.example.lifelike.entity.User 15 | import com.github.zsoltk.compose.router.Router 16 | 17 | interface LoggedIn { 18 | sealed class Routing { 19 | object Gallery : Routing() 20 | object News : Routing() 21 | object Profile : Routing() 22 | } 23 | 24 | companion object { 25 | @Composable 26 | fun Content(defaultRouting: Routing, user: User, onLogout: () -> Unit) { 27 | Router(defaultRouting) { backStack -> 28 | val routing = backStack.last() 29 | 30 | Column { 31 | Box(modifier = Modifier.weight(1f).wrapContentSize(Alignment.TopStart)) { 32 | routing.toContent(user, onLogout) 33 | } 34 | 35 | Menu.Content( 36 | state = routing.toMenuState(), 37 | onMenuItemClicked = { menuItem -> 38 | backStack.push(menuItem.toRouting()) 39 | } 40 | ) 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Renders Content of another Composable module based on the selected Routing 47 | */ 48 | @Composable 49 | private fun Routing.toContent(user: User, onLogout: () -> Unit) = when (this) { 50 | is Routing.Gallery -> Gallery.Content(AlbumList) 51 | is Routing.News -> News.Content() 52 | is Routing.Profile -> Profile.Content( 53 | user, 54 | onLogout 55 | ) 56 | } 57 | 58 | /** 59 | * Creates Menu state with menu item selected based on current routing choice 60 | */ 61 | private fun Routing.toMenuState() = 62 | Menu.State( 63 | currentSelection = when (this) { 64 | is Routing.Gallery -> Menu.MenuItem.Gallery 65 | is Routing.News -> Menu.MenuItem.News 66 | is Routing.Profile -> Menu.MenuItem.Profile 67 | } 68 | ) 69 | 70 | /** 71 | * Maps the MenuItem to the corresponding Routing choice 72 | */ 73 | private fun Menu.MenuItem.toRouting(): Routing = when (this) { 74 | is Menu.MenuItem.Gallery -> Routing.Gallery 75 | is Menu.MenuItem.News -> Routing.News 76 | is Menu.MenuItem.Profile -> Routing.Profile 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/LoggedOut.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.runtime.Composable 5 | import com.example.lifelike.composable.loggedout.RegConfirmSmsCode 6 | import com.example.lifelike.composable.loggedout.RegFinal 7 | import com.example.lifelike.composable.loggedout.RegUserName 8 | import com.example.lifelike.composable.loggedout.RegUserPhone 9 | import com.example.lifelike.composable.loggedout.Splash 10 | import com.example.lifelike.entity.User 11 | import com.github.zsoltk.compose.router.Router 12 | 13 | interface LoggedOut { 14 | 15 | sealed class Routing { 16 | object Splash : Routing() 17 | object RegUserName : Routing() 18 | object RegUserPhone : Routing() 19 | object RegConfirmSmsCode : Routing() 20 | object RegFinal : Routing() 21 | } 22 | 23 | companion object { 24 | @ExperimentalFoundationApi 25 | @Composable 26 | fun Content(defaultRouting: Routing, onLoggedIn: (User) -> Unit) { 27 | val user = User("Demo user", "123456789") 28 | 29 | Router(defaultRouting) { backStack -> 30 | fun Routing.next(): () -> Unit = { 31 | when (this) { 32 | Routing.Splash -> Routing.RegUserName 33 | Routing.RegUserName -> Routing.RegUserPhone 34 | Routing.RegUserPhone -> Routing.RegConfirmSmsCode 35 | Routing.RegConfirmSmsCode -> Routing.RegFinal 36 | Routing.RegFinal -> null 37 | }?.let { 38 | backStack.push(it) 39 | } 40 | } 41 | 42 | when (val currentRouting = backStack.last()) { 43 | Routing.Splash -> Splash.Content(onNext = currentRouting.next()) 44 | Routing.RegUserName -> RegUserName.Content( 45 | user = user, 46 | onNext = currentRouting.next() 47 | ) 48 | Routing.RegUserPhone -> RegUserPhone.Content( 49 | user = user, 50 | onNext = currentRouting.next() 51 | ) 52 | Routing.RegConfirmSmsCode -> RegConfirmSmsCode.Content(onNext = currentRouting.next()) 53 | Routing.RegFinal -> RegFinal.Content(onNext = { onLoggedIn(user) }) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/Root.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.runtime.Composable 5 | import com.example.lifelike.composable.LoggedIn.Routing.Gallery 6 | import com.example.lifelike.composable.LoggedOut.Routing.Splash 7 | import com.example.lifelike.entity.User 8 | import com.github.zsoltk.compose.router.Router 9 | 10 | interface Root { 11 | 12 | sealed class Routing { 13 | object LoggedOut : Routing() 14 | data class LoggedIn(val user: User) : Routing() 15 | } 16 | 17 | companion object { 18 | @ExperimentalFoundationApi 19 | @Composable 20 | fun Content(defaultRouting: Routing) { 21 | Router(defaultRouting) { backStack -> 22 | when (val currentRouting = backStack.last()) { 23 | is Routing.LoggedOut -> LoggedOut.Content( 24 | defaultRouting = Splash, 25 | onLoggedIn = { user -> 26 | // play around with other back stack operations here: 27 | backStack.newRoot( 28 | Routing.LoggedIn(user) 29 | ) 30 | } 31 | ) 32 | is Routing.LoggedIn -> LoggedIn.Content( 33 | defaultRouting = Gallery, 34 | user = currentRouting.user, 35 | onLogout = { 36 | // play around with other back stack operations here: 37 | backStack.newRoot( 38 | Routing.LoggedOut 39 | ) 40 | } 41 | ) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/common/BigButton.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.common 2 | 3 | import androidx.compose.material.Button 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.graphics.Color 8 | 9 | @Composable 10 | fun BigButton(text: String, onClick: () -> Unit) { 11 | Button(onClick = onClick) { 12 | Text( 13 | text = text.toUpperCase(), 14 | style = MaterialTheme.typography.body1.copy(color = Color.White) 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/AlbumList.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.lazy.items 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | import com.example.lifelike.R 17 | import com.example.lifelike.entity.Album 18 | import com.example.lifelike.entity.albums 19 | 20 | 21 | interface AlbumList { 22 | 23 | companion object { 24 | @Composable 25 | fun Content(onAlbumSelected: (Album) -> Unit) { 26 | LazyColumn { 27 | items(albums) { 28 | AlbumRow(it, onAlbumSelected) 29 | } 30 | } 31 | } 32 | 33 | @Composable 34 | private fun AlbumRow(album: Album, onAlbumSelected: (Album) -> Unit) { 35 | val image = painterResource(R.drawable.placeholder) 36 | val typography = MaterialTheme.typography 37 | 38 | Box( 39 | modifier = Modifier.clickable(onClick = { onAlbumSelected(album) }) 40 | ) { 41 | Row(modifier = Modifier.padding(all = 16.dp)) { 42 | Box(modifier = Modifier.size(40.dp, 40.dp)) { 43 | Image(image, null) 44 | } 45 | Spacer(modifier = Modifier.width(16.dp)) 46 | Column { 47 | Text( 48 | album.name, 49 | style = typography.subtitle1.copy(fontWeight = FontWeight.Bold) 50 | ) 51 | Text("${album.photos.size} photos", style = typography.body1) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | 60 | @Preview 61 | @Composable 62 | fun AlbumListPreview() { 63 | AlbumList.Content {} 64 | } 65 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/FullScreenPhoto.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import com.example.lifelike.R 16 | import com.example.lifelike.entity.Photo 17 | 18 | interface FullScreenPhoto { 19 | 20 | companion object { 21 | @Composable 22 | fun Content(photo: Photo) { 23 | val image = painterResource(R.drawable.placeholder) 24 | val typography = MaterialTheme.typography 25 | 26 | Surface(color = Color.DarkGray) { 27 | Box(modifier = Modifier.padding(32.dp)) { 28 | Column { 29 | Box(modifier = Modifier.weight(0.8f).fillMaxSize().wrapContentSize(Alignment.Center)) { 30 | Image(image, null) 31 | } 32 | 33 | Spacer(modifier = Modifier.height(32.dp)) 34 | Text( 35 | text = photo.title, 36 | style = typography.subtitle1.copy(color = Color.White) 37 | ) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | 46 | @Preview 47 | @Composable 48 | fun FullScreenPhotoPreview() { 49 | FullScreenPhoto.Content(Photo(167)) 50 | } 51 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/Gallery.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.example.lifelike.entity.Album 5 | import com.example.lifelike.entity.Photo 6 | import com.github.zsoltk.compose.router.Router 7 | 8 | interface Gallery { 9 | 10 | sealed class Routing { 11 | object AlbumList : Routing() 12 | data class PhotosOfAlbum(val album: Album) : Routing() 13 | data class FullScreenPhoto(val photo: Photo) : Routing() 14 | } 15 | 16 | companion object { 17 | @Composable 18 | fun Content(defaultRouting: Routing) { 19 | Router(defaultRouting) { backStack -> 20 | when (val routing = backStack.last()) { 21 | is Routing.AlbumList -> AlbumList.Content( 22 | onAlbumSelected = { 23 | backStack.push(Routing.PhotosOfAlbum(it)) 24 | }) 25 | 26 | is Routing.PhotosOfAlbum -> PhotosOfAlbum.Content( 27 | album = routing.album, 28 | onPhotoSelected = { 29 | backStack.push(Routing.FullScreenPhoto(it)) 30 | }) 31 | 32 | is Routing.FullScreenPhoto -> FullScreenPhoto.Content( 33 | photo = routing.photo 34 | ) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/Menu.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.shape.RoundedCornerShape 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.material.Surface 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.text.AnnotatedString 15 | import androidx.compose.ui.text.ParagraphStyle 16 | import androidx.compose.ui.text.style.TextAlign 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.compose.ui.unit.dp 19 | import com.example.lifelike.composable.loggedin.Menu.MenuItem.Gallery 20 | import com.example.lifelike.composable.loggedin.Menu.MenuItem.News 21 | import com.example.lifelike.composable.loggedin.Menu.MenuItem.Profile 22 | 23 | interface Menu { 24 | 25 | sealed class MenuItem { 26 | object Gallery : MenuItem() 27 | object News : MenuItem() 28 | object Profile : MenuItem() 29 | } 30 | 31 | data class State( 32 | val menuItems: List = listOf(Gallery, News, Profile), 33 | val currentSelection: MenuItem 34 | ) 35 | 36 | companion object { 37 | @Composable 38 | fun Content(state: State, onMenuItemClicked: (MenuItem) -> Unit) { 39 | Row { 40 | state.menuItems.forEach { item -> 41 | Box(modifier = Modifier.weight(1f)) { 42 | MenuItem( 43 | item, 44 | item == state.currentSelection 45 | ) { 46 | onMenuItemClicked(it) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | @Composable 54 | private fun MenuItem(item: MenuItem, isSelected: Boolean, onClick: (MenuItem) -> Unit) { 55 | val color = MaterialTheme.colors 56 | 57 | Surface( 58 | color = if (isSelected) color.secondary else color.surface, 59 | shape = RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp) 60 | ) { 61 | Box( 62 | modifier = Modifier.clickable(onClick = { onClick.invoke(item) }) 63 | ) { 64 | Text( 65 | modifier = Modifier.fillMaxWidth().padding(8.dp), 66 | text = AnnotatedString( 67 | text = when (item) { 68 | Gallery -> "Gallery" 69 | News -> "News" 70 | Profile -> "Profile" 71 | }, 72 | paragraphStyle = ParagraphStyle( 73 | textAlign = TextAlign.Center 74 | ) 75 | ), 76 | style = MaterialTheme.typography.body2.copy( 77 | color = if (isSelected) color.onSecondary else color.onSurface 78 | ) 79 | ) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | @Preview 87 | @Composable 88 | fun PreviewMenu() { 89 | Menu.Content(Menu.State(currentSelection = Gallery), onMenuItemClicked = {}) 90 | } 91 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/News.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.rememberScrollState 7 | import androidx.compose.foundation.verticalScroll 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | 14 | 15 | interface News { 16 | 17 | companion object { 18 | @Composable 19 | fun Content() { 20 | Box(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { 21 | Column(modifier = Modifier.verticalScroll(rememberScrollState())) { 22 | Text( 23 | text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Adipiscing elit duis tristique sollicitudin nibh sit amet. Sed lectus vestibulum mattis ullamcorper. Sit amet purus gravida quis blandit turpis cursus. Amet nulla facilisi morbi tempus iaculis urna id volutpat lacus. In nibh mauris cursus mattis molestie a iaculis. Eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum. Sollicitudin tempor id eu nisl. Vitae nunc sed velit dignissim sodales ut eu sem. Vulputate eu scelerisque felis imperdiet proin fermentum. Aliquam ut porttitor leo a diam sollicitudin tempor. Sed sed risus pretium quam.\n" + 24 | "\n" + 25 | "Amet cursus sit amet dictum sit. Duis ut diam quam nulla porttitor. Ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Rhoncus dolor purus non enim praesent elementum facilisis leo. Risus nullam eget felis eget. Massa sed elementum tempus egestas sed. Rhoncus urna neque viverra justo nec ultrices dui. Volutpat sed cras ornare arcu. Lacus luctus accumsan tortor posuere ac ut consequat semper. Penatibus et magnis dis parturient montes nascetur ridiculus mus.\n" + 26 | "\n" + 27 | "Purus viverra accumsan in nisl nisi scelerisque. Habitant morbi tristique senectus et netus. Et netus et malesuada fames ac turpis egestas integer eget. Urna duis convallis convallis tellus id interdum. Adipiscing diam donec adipiscing tristique risus nec. Viverra mauris in aliquam sem fringilla. Ut pharetra sit amet aliquam id diam maecenas ultricies mi. Consequat nisl vel pretium lectus quam id leo in vitae. Leo duis ut diam quam nulla porttitor massa. Amet commodo nulla facilisi nullam vehicula ipsum a. Massa sapien faucibus et molestie ac feugiat sed lectus vestibulum. Dui accumsan sit amet nulla facilisi morbi. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque.\n" + 28 | "\n" + 29 | "Duis at tellus at urna condimentum mattis pellentesque id. Cursus metus aliquam eleifend mi in. Lacus luctus accumsan tortor posuere ac. Sed tempus urna et pharetra pharetra massa massa ultricies mi. Urna id volutpat lacus laoreet non curabitur. Et egestas quis ipsum suspendisse ultrices gravida dictum. Metus dictum at tempor commodo. Nullam non nisi est sit amet facilisis magna etiam tempor. Pellentesque habitant morbi tristique senectus et. Erat nam at lectus urna duis convallis convallis. Quis vel eros donec ac odio tempor orci dapibus ultrices. Maecenas sed enim ut sem viverra aliquet eget sit. Consectetur lorem donec massa sapien faucibus et molestie ac feugiat. Leo a diam sollicitudin tempor id eu nisl nunc mi. Sit amet consectetur adipiscing elit ut aliquam. Tellus at urna condimentum mattis pellentesque. Vitae turpis massa sed elementum tempus. Ultrices vitae auctor eu augue ut lectus arcu bibendum. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor posuere.\n" + 30 | "\n" + 31 | "Lobortis elementum nibh tellus molestie nunc non. Orci eu lobortis elementum nibh tellus molestie nunc non blandit. Eu nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Eu lobortis elementum nibh tellus molestie nunc non blandit massa. In massa tempor nec feugiat nisl. Cras fermentum odio eu feugiat pretium nibh. Lacus viverra vitae congue eu consequat ac. Urna porttitor rhoncus dolor purus non enim praesent elementum. Facilisi etiam dignissim diam quis enim lobortis. Varius morbi enim nunc faucibus. Egestas congue quisque egestas diam in arcu cursus. Vulputate ut pharetra sit amet." 32 | ) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | 40 | @Preview 41 | @Composable 42 | fun LoremIpsumPreview() { 43 | News.Content() 44 | } 45 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/PhotosOfAlbum.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.lazy.items 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.platform.LocalDensity 13 | import androidx.compose.ui.res.imageResource 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import androidx.compose.ui.unit.dp 17 | import com.example.lifelike.R 18 | import com.example.lifelike.entity.Album 19 | import com.example.lifelike.entity.Photo 20 | import com.example.lifelike.entity.albums 21 | 22 | 23 | interface PhotosOfAlbum { 24 | 25 | companion object { 26 | @Composable 27 | fun Content(album: Album, onPhotoSelected: (Photo) -> Unit) { 28 | if (album.photos.isEmpty()) { 29 | EmptyView() 30 | } else { 31 | AlbumView(album = album, onPhotoSelected = onPhotoSelected) 32 | } 33 | } 34 | 35 | @Composable 36 | fun EmptyView() { 37 | Box(modifier = Modifier.fillMaxSize()) { 38 | Text("No photos yet") 39 | } 40 | } 41 | 42 | @Composable 43 | fun AlbumView(album: Album, onPhotoSelected: (Photo) -> Unit) { 44 | Column { 45 | AlbumTitle(album) 46 | PhotoCount(album) 47 | PhotoGrid(album = album, onPhotoSelected = onPhotoSelected) 48 | } 49 | } 50 | 51 | @Composable 52 | fun AlbumTitle(album: Album) { 53 | val typography = MaterialTheme.typography 54 | 55 | Text( 56 | text = album.name, 57 | style = typography.h4, 58 | modifier = Modifier.padding(start = 8.dp, top = 16.dp, end = 8.dp, bottom = 4.dp) 59 | ) 60 | } 61 | 62 | @Composable 63 | fun PhotoCount(album: Album) { 64 | val typography = MaterialTheme.typography 65 | 66 | Text( 67 | text = "${album.photos.size} photos", 68 | style = typography.body1, 69 | modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 16.dp) 70 | ) 71 | } 72 | 73 | @Composable 74 | fun PhotoGrid(album: Album, onPhotoSelected: (Photo) -> Unit) { 75 | val cols = 4 76 | val image = painterResource(R.drawable.placeholder) 77 | val photoRows = album.photos.chunked(cols) 78 | 79 | Box(modifier = Modifier.padding(4.dp)) { 80 | LazyColumn { 81 | items(photoRows) { row -> 82 | BoxWithConstraints { 83 | val constraints = this.constraints 84 | 85 | Row { 86 | val w = with(LocalDensity.current) { (constraints.maxWidth.toDp().value / cols).dp } 87 | row.forEach { photo -> 88 | Box(modifier = Modifier 89 | .width(w) 90 | .padding(4.dp) 91 | .clickable(onClick = { onPhotoSelected(photo) }) 92 | ) { 93 | Box(modifier = Modifier.aspectRatio(1f)) { 94 | Image(image, null) 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | 108 | @Preview 109 | @Composable 110 | fun PhotosOfAlbumPreview() { 111 | PhotosOfAlbum.Content(albums.first()) {} 112 | } 113 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedin/Profile.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedin 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import androidx.compose.ui.unit.dp 11 | import com.example.lifelike.entity.User 12 | 13 | 14 | interface Profile { 15 | 16 | companion object { 17 | @Composable 18 | fun Content(user: User, onLogout: () -> Unit) { 19 | Column( 20 | modifier = Modifier.fillMaxHeight().padding(40.dp), 21 | verticalArrangement = Arrangement.SpaceAround 22 | ) { 23 | Text( 24 | text = "You are logged in as:", 25 | style = MaterialTheme.typography.h4 26 | ) 27 | Column { 28 | Text( 29 | text = user.name, 30 | style = MaterialTheme.typography.h5 31 | ) 32 | Spacer(modifier = Modifier.height(16.dp)) 33 | Text( 34 | text = user.phone, 35 | style = MaterialTheme.typography.h5 36 | ) 37 | } 38 | Column { 39 | Button(onClick = onLogout) { 40 | Text(text = "Log out") 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | @Preview 49 | @Composable 50 | fun ProfilePreview() { 51 | Profile.Content(user = User("Demo user", "123456789"), onLogout = {}) 52 | } 53 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/RegConfirmSmsCode.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.text.BasicTextField 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.text.TextRange 9 | import androidx.compose.ui.text.input.TextFieldValue 10 | import com.example.lifelike.composable.loggedout.common.RegFlowPanel 11 | 12 | 13 | interface RegConfirmSmsCode { 14 | companion object { 15 | 16 | @ExperimentalFoundationApi 17 | @Composable 18 | fun Content(onNext: () -> Unit) { 19 | 20 | val code = remember { 21 | val initialValue = "0000" 22 | mutableStateOf(TextFieldValue(initialValue, TextRange(initialValue.length, initialValue.length))) 23 | } 24 | 25 | RegFlowPanel( 26 | "Confirm SMS code that will never arrive", 27 | { if (code.value.text.length == 4) onNext() }) { 28 | BasicTextField( 29 | value = code.value, 30 | onValueChange = { 31 | var digits = it.text.filter { it.isDigit() } 32 | digits = if (digits.length <= 4) digits else digits.substring(0, 4) 33 | code.value = TextFieldValue(digits, TextRange(digits.length, digits.length)) 34 | } 35 | ) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/RegFinal.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.example.lifelike.composable.loggedout.common.RegFlowPanel 5 | 6 | 7 | interface RegFinal { 8 | companion object { 9 | 10 | @Composable 11 | fun Content(onNext: () -> Unit) { 12 | RegFlowPanel("Welcome on board!", onNext) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/RegUserName.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.text.BasicTextField 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.text.TextRange 7 | import androidx.compose.ui.text.input.TextFieldValue 8 | import androidx.compose.ui.tooling.preview.Preview 9 | import com.example.lifelike.composable.loggedout.common.RegFlowPanel 10 | import com.example.lifelike.entity.User 11 | 12 | 13 | interface RegUserName { 14 | companion object { 15 | 16 | @OptIn(ExperimentalFoundationApi::class) 17 | @Composable 18 | fun Content(user: User, onNext: () -> Unit) { 19 | RegFlowPanel("Your fake name", onNext) { 20 | BasicTextField( 21 | value = TextFieldValue(user.name, TextRange(user.name.length,user.name.length)), 22 | onValueChange = { user.name = it.text } 23 | ) 24 | } 25 | } 26 | } 27 | } 28 | 29 | @Preview 30 | @Composable 31 | fun PreviewRegUserName() { 32 | RegUserName.Content(User("Test user", "123456"), {}) 33 | } 34 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/RegUserPhone.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.text.BasicTextField 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.text.TextRange 7 | import androidx.compose.ui.text.input.TextFieldValue 8 | import com.example.lifelike.composable.loggedout.common.RegFlowPanel 9 | import com.example.lifelike.entity.User 10 | 11 | 12 | interface RegUserPhone { 13 | companion object { 14 | @ExperimentalFoundationApi 15 | @Composable 16 | fun Content(user: User, onNext: () -> Unit) { 17 | RegFlowPanel("Your fake phone number", onNext) { 18 | BasicTextField( 19 | value = TextFieldValue(user.phone, TextRange(user.phone.length,user.phone.length)), 20 | onValueChange = { user.phone = it.text.filter { it.isDigit() } } 21 | ) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/Splash.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.text.AnnotatedString 10 | import androidx.compose.ui.text.ParagraphStyle 11 | import androidx.compose.ui.text.style.TextAlign 12 | import androidx.compose.ui.unit.dp 13 | import com.example.lifelike.composable.common.BigButton 14 | 15 | 16 | interface Splash { 17 | companion object { 18 | 19 | @Composable 20 | fun Content(onNext: () -> Unit) { 21 | Column(modifier = Modifier.padding(40.dp), verticalArrangement = Arrangement.SpaceAround) { 22 | Text( 23 | text = AnnotatedString( 24 | text = "Welcome to amazing fake app", 25 | paragraphStyle = ParagraphStyle( 26 | textAlign = TextAlign.Center 27 | ) 28 | ), 29 | style = MaterialTheme.typography.h4 30 | ) 31 | Box( 32 | modifier = Modifier.fillMaxWidth() 33 | .sizeIn(maxHeight = 48.dp) 34 | .wrapContentSize(Alignment.Center) 35 | ) { 36 | BigButton( 37 | text = "Create account", 38 | onClick = onNext 39 | ) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/composable/loggedout/common/RegFlowPanel.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.composable.loggedout.common 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import com.example.lifelike.composable.common.BigButton 11 | 12 | @Composable 13 | fun RegFlowPanel( 14 | title: String, 15 | onNext: () -> Unit, 16 | content: @Composable () -> Unit = {} 17 | ) { 18 | Column( 19 | modifier = Modifier.fillMaxSize().padding(40.dp), 20 | verticalArrangement = Arrangement.SpaceAround 21 | ) { 22 | Box(modifier = Modifier.wrapContentSize()) { 23 | Text( 24 | text = title, 25 | style = MaterialTheme.typography.h5 26 | ) 27 | } 28 | Box(modifier = Modifier.fillMaxSize().weight(1f).wrapContentHeight(Alignment.CenterVertically)) { 29 | content() 30 | } 31 | Box( 32 | modifier = Modifier 33 | .sizeIn(maxHeight = 48.dp) 34 | .wrapContentSize(Alignment.CenterEnd) 35 | ) { 36 | BigButton( 37 | text = "Next", 38 | onClick = onNext 39 | ) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/entity/Album.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.entity 2 | 3 | data class Album( 4 | val name: String, 5 | val photos: List 6 | ) 7 | 8 | val albums = listOf( 9 | Album("Trip to Budapest", MutableList(19) { i -> Photo(i) }), 10 | Album("Cats", MutableList(39) { i -> Photo(i) }), 11 | Album("Family", MutableList(162) { i -> Photo(i) }), 12 | Album("Todo", MutableList(15) { i -> Photo(i) }), 13 | Album("California road trip", MutableList(112) { i -> Photo(i) }), 14 | Album("KotlinConf", MutableList(43) { i -> Photo(i) }), 15 | Album("Summer of '19", MutableList(72) { i -> Photo(i) }) 16 | ) 17 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/entity/Photo.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.entity 2 | 3 | data class Photo( 4 | val id: Int 5 | ) { 6 | val title: String = "Photo #$id" 7 | } 8 | -------------------------------------------------------------------------------- /app-lifelike/src/main/java/com/example/lifelike/entity/User.kt: -------------------------------------------------------------------------------- 1 | package com.example.lifelike.entity 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | 7 | 8 | class User( 9 | name: String, 10 | phone: String 11 | ){ 12 | var name by mutableStateOf(name) 13 | var phone by mutableStateOf(phone) 14 | } 15 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/drawable/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/drawable/placeholder.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsoltk/compose-router/ec57fc9680f954b869f4def1ec73f24068e5244b/app-lifelike/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app-lifelike/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/values/material_colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | #FFEBEE 9 | #FFCDD2 10 | #EF9A9A 11 | #E57373 12 | #EF5350 13 | #F44336 14 | #E53935 15 | #D32F2F 16 | #C62828 17 | #B71C1C 18 | #FF8A80 19 | #FF5252 20 | #FF1744 21 | #D50000 22 | 23 | 24 | #FCE4EC 25 | #F8BBD0 26 | #F48FB1 27 | #F06292 28 | #EC407A 29 | #E91E63 30 | #D81B60 31 | #C2185B 32 | #AD1457 33 | #880E4F 34 | #FF80AB 35 | #FF4081 36 | #F50057 37 | #C51162 38 | 39 | 40 | #F3E5F5 41 | #E1BEE7 42 | #CE93D8 43 | #BA68C8 44 | #AB47BC 45 | #9C27B0 46 | #8E24AA 47 | #7B1FA2 48 | #6A1B9A 49 | #4A148C 50 | #EA80FC 51 | #E040FB 52 | #D500F9 53 | #AA00FF 54 | 55 | 56 | #EDE7F6 57 | #D1C4E9 58 | #B39DDB 59 | #9575CD 60 | #7E57C2 61 | #673AB7 62 | #5E35B1 63 | #512DA8 64 | #4527A0 65 | #311B92 66 | #B388FF 67 | #7C4DFF 68 | #651FFF 69 | #6200EA 70 | 71 | 72 | #E8EAF6 73 | #C5CAE9 74 | #9FA8DA 75 | #7986CB 76 | #5C6BC0 77 | #3F51B5 78 | #3949AB 79 | #303F9F 80 | #283593 81 | #1A237E 82 | #8C9EFF 83 | #536DFE 84 | #3D5AFE 85 | #304FFE 86 | 87 | 88 | #E3F2FD 89 | #BBDEFB 90 | #90CAF9 91 | #64B5F6 92 | #42A5F5 93 | #2196F3 94 | #1E88E5 95 | #1976D2 96 | #1565C0 97 | #0D47A1 98 | #82B1FF 99 | #448AFF 100 | #2979FF 101 | #2962FF 102 | 103 | 104 | #E1F5FE 105 | #B3E5FC 106 | #81D4fA 107 | #4fC3F7 108 | #29B6FC 109 | #03A9F4 110 | #039BE5 111 | #0288D1 112 | #0277BD 113 | #01579B 114 | #80D8FF 115 | #40C4FF 116 | #00B0FF 117 | #0091EA 118 | 119 | 120 | #E0F7FA 121 | #B2EBF2 122 | #80DEEA 123 | #4DD0E1 124 | #26C6DA 125 | #00BCD4 126 | #00ACC1 127 | #0097A7 128 | #00838F 129 | #006064 130 | #84FFFF 131 | #18FFFF 132 | #00E5FF 133 | #00B8D4 134 | 135 | 136 | #E0F2F1 137 | #B2DFDB 138 | #80CBC4 139 | #4DB6AC 140 | #26A69A 141 | #009688 142 | #00897B 143 | #00796B 144 | #00695C 145 | #004D40 146 | #A7FFEB 147 | #64FFDA 148 | #1DE9B6 149 | #00BFA5 150 | 151 | 152 | #E8F5E9 153 | #C8E6C9 154 | #A5D6A7 155 | #81C784 156 | #66BB6A 157 | #4CAF50 158 | #43A047 159 | #388E3C 160 | #2E7D32 161 | #1B5E20 162 | #B9F6CA 163 | #69F0AE 164 | #00E676 165 | #00C853 166 | 167 | 168 | #F1F8E9 169 | #DCEDC8 170 | #C5E1A5 171 | #AED581 172 | #9CCC65 173 | #8BC34A 174 | #7CB342 175 | #689F38 176 | #558B2F 177 | #33691E 178 | #CCFF90 179 | #B2FF59 180 | #76FF03 181 | #64DD17 182 | 183 | 184 | #F9FBE7 185 | #F0F4C3 186 | #E6EE9C 187 | #DCE775 188 | #D4E157 189 | #CDDC39 190 | #C0CA33 191 | #A4B42B 192 | #9E9D24 193 | #827717 194 | #F4FF81 195 | #EEFF41 196 | #C6FF00 197 | #AEEA00 198 | 199 | 200 | #FFFDE7 201 | #FFF9C4 202 | #FFF590 203 | #FFF176 204 | #FFEE58 205 | #FFEB3B 206 | #FDD835 207 | #FBC02D 208 | #F9A825 209 | #F57F17 210 | #FFFF82 211 | #FFFF00 212 | #FFEA00 213 | #FFD600 214 | 215 | 216 | #FFF8E1 217 | #FFECB3 218 | #FFE082 219 | #FFD54F 220 | #FFCA28 221 | #FFC107 222 | #FFB300 223 | #FFA000 224 | #FF8F00 225 | #FF6F00 226 | #FFE57F 227 | #FFD740 228 | #FFC400 229 | #FFAB00 230 | 231 | 232 | #FFF3E0 233 | #FFE0B2 234 | #FFCC80 235 | #FFB74D 236 | #FFA726 237 | #FF9800 238 | #FB8C00 239 | #F57C00 240 | #EF6C00 241 | #E65100 242 | #FFD180 243 | #FFAB40 244 | #FF9100 245 | #FF6D00 246 | 247 | 248 | #FBE9A7 249 | #FFCCBC 250 | #FFAB91 251 | #FF8A65 252 | #FF7043 253 | #FF5722 254 | #F4511E 255 | #E64A19 256 | #D84315 257 | #BF360C 258 | #FF9E80 259 | #FF6E40 260 | #FF3D00 261 | #DD2600 262 | 263 | 264 | #EFEBE9 265 | #D7CCC8 266 | #BCAAA4 267 | #A1887F 268 | #8D6E63 269 | #795548 270 | #6D4C41 271 | #5D4037 272 | #4E342E 273 | #3E2723 274 | 275 | 276 | #FAFAFA 277 | #F5F5F5 278 | #EEEEEE 279 | #E0E0E0 280 | #BDBDBD 281 | #9E9E9E 282 | #757575 283 | #616161 284 | #424242 285 | #212121 286 | #000000 287 | #ffffff 288 | 289 | 290 | #ECEFF1 291 | #CFD8DC 292 | #B0BBC5 293 | #90A4AE 294 | #78909C 295 | #607D8B 296 | #546E7A 297 | #455A64 298 | #37474F 299 | #263238 300 | 301 | 302 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Lifelike example 3 | 4 | -------------------------------------------------------------------------------- /app-lifelike/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 10 | 11 | 15 | 16 |