├── .gitignore ├── .run └── desktopApp.run.xml ├── LICENSE.txt ├── README.md ├── androidApp ├── build.gradle.kts └── src │ └── androidMain │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── recipeapp │ │ └── MainActivity.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 │ └── strings.xml ├── automotiveApp ├── build.gradle.kts └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── recipe │ │ └── automotiveapp │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── recipe │ │ │ └── automotiveapp │ │ │ └── MainActivityAutomotive.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── recipe │ └── automotiveapp │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── cleanup.sh ├── desktopApp ├── build.gradle.kts └── src │ └── jvmMain │ └── kotlin │ └── main.kt ├── docs ├── 366.js ├── 39f400ec3abd9c6d43e3.wasm ├── 8433c6b69bfa201b0895.wasm ├── META-INF │ └── MANIFEST.MF ├── chef.png ├── compose-multiplatform.xml ├── drawable │ ├── 01-lemon-cheesecake-bg-lg.png │ ├── 01-lemon-cheesecake-bg.png │ ├── 01-lemon-cheesecake.png │ ├── 02-chocolate-cake-1.png │ ├── 03-chocolate-donuts.png │ ├── 04-fluffy-cake.png │ ├── 05-macaroons.png │ ├── 06-white-cream-cake.png │ ├── 07-honey-cake.png │ ├── 08-cream-cupcakes.png │ ├── 09-fruit-plate.png │ ├── 10-strawberries.png │ ├── 11-powdered-cake.png │ ├── 12-chocolate-cake-2.png │ ├── 13-strawberry-powdered-cake.png │ ├── 14-fruit-pie.png │ ├── 15-apple-pie.png │ └── chef.png ├── font │ ├── rubik_bold.ttf │ ├── rubik_light.ttf │ ├── rubik_medium.ttf │ └── rubik_regular.ttf ├── images │ └── logo.png ├── index.html ├── kotlin_skiko_mjs.js ├── load.mjs ├── manifest.json ├── recipeapp.wasm ├── skiko.js ├── skiko.mjs ├── skiko.wasm ├── webApp.js └── webApp.js.map ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp ├── Configuration │ └── Config.xcconfig ├── Podfile ├── iosApp.xcodeproj │ └── project.pbxproj └── iosApp │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── app-icon-1024.png │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── iOSApp.swift ├── kotlin-js-store └── yarn.lock ├── node_modules └── .yarn-integrity ├── readme_images ├── android_app_running.png ├── banner.png ├── desktop_app_running.png ├── edit_run_config.png ├── ios_app_running.png ├── open_project_view.png ├── run_on_android.png ├── run_on_desktop.png ├── target_device.png └── text_field_added.png ├── settings.gradle.kts ├── shared ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ ├── main.android.kt │ │ └── sensor │ │ ├── SensorDataManager.kt │ │ └── SensorManagerImpl.kt │ ├── commonMain │ ├── composeResources │ │ ├── drawable │ │ │ ├── 01-lemon-cheesecake-bg-lg.png │ │ │ ├── 01-lemon-cheesecake-bg.png │ │ │ ├── 01-lemon-cheesecake.png │ │ │ ├── 02-chocolate-cake-1.png │ │ │ ├── 03-chocolate-donuts.png │ │ │ ├── 04-fluffy-cake.png │ │ │ ├── 05-macaroons.png │ │ │ ├── 06-white-cream-cake.png │ │ │ ├── 07-honey-cake.png │ │ │ ├── 08-cream-cupcakes.png │ │ │ ├── 09-fruit-plate.png │ │ │ ├── 10-strawberries.png │ │ │ ├── 11-powdered-cake.png │ │ │ ├── 12-chocolate-cake-2.png │ │ │ ├── 13-strawberry-powdered-cake.png │ │ │ ├── 14-fruit-pie.png │ │ │ ├── 15-apple-pie.png │ │ │ └── chef.png │ │ └── font │ │ │ ├── rubik_bold.ttf │ │ │ ├── rubik_light.ttf │ │ │ ├── rubik_medium.ttf │ │ │ └── rubik_regular.ttf │ └── kotlin │ │ ├── App.kt │ │ ├── Colors.kt │ │ ├── Screen.kt │ │ ├── Typography.kt │ │ ├── details │ │ ├── AnimateInEffect.kt │ │ ├── FadeInEffect.kt │ │ ├── IngradientItem.kt │ │ ├── InstructionItem.kt │ │ ├── RecipeDetails.kt │ │ ├── RecipeDetailsLarge.kt │ │ ├── RecipeDetailsSmall.kt │ │ └── StepsAndDetails.kt │ │ ├── model │ │ ├── ExampleData.kt │ │ └── Recipe.kt │ │ ├── recipeslist │ │ ├── ImageWrapper.kt │ │ ├── RecipeImage.kt │ │ ├── RecipeListItem.kt │ │ ├── RecipeListItemWrapper.kt │ │ └── RecipesList.kt │ │ └── sensor │ │ └── SensorCallbackController.kt │ ├── desktopMain │ └── kotlin │ │ └── main.desktop.kt │ ├── iosMain │ └── kotlin │ │ ├── main.ios.kt │ │ └── sensor │ │ ├── SensorDataManager.kt │ │ └── SensorManagerImpl.kt │ └── wasmJsMain │ └── kotlin │ └── Resources.wasmJs.kt ├── tvApp ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── recipeapp │ │ └── tv │ │ └── MainActivityTV.kt │ └── res │ ├── 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 │ └── strings.xml ├── webApp ├── build.gradle.kts └── src │ ├── jsMain │ └── resources │ │ └── images │ │ └── logo.png │ └── wasmJsMain │ ├── kotlin │ └── main.kt │ └── resources │ ├── images │ └── logo.png │ ├── index.html │ ├── manifest.json │ └── styles.css └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | build/ 7 | /captures 8 | /.kotlin 9 | .externalNativeBuild 10 | .cxx 11 | iosApp/Podfile.lock 12 | iosApp/Pods/* 13 | iosApp/iosApp.xcworkspace/* 14 | iosApp/iosApp.xcodeproj/* 15 | !iosApp/iosApp.xcodeproj/project.pbxproj 16 | shared/shared.podspec 17 | -------------------------------------------------------------------------------- /.run/desktopApp.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 2020-2021 JetBrains s.r.o. and and respective authors and developers. 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 | ## Recipe App Compose Multiplatform 2 | 3 | Inspired by [Roaa Kadam](https://github.com/Roaa94) flutter [app](https://github.com/Roaa94/recipes_ui_app/), I wanted to do the same in Compose Multiplatform. There is a lot to explore in Compose Multiplatform from the aspect of this app like Heor Animation, Collapsable Toolbar, Staggered Animations, Gyroscope detection etc. 4 | 5 | > **Note** 6 | > It is still a work in progress 7 | 8 | 9 | ### Live 10 | You can find it live [here](https://seabdulbasit.github.io/recipe-app/) 11 | 12 | ## Supported Platforms 13 | - Android 14 | - iOS 15 | - Desktop 16 | - Web 17 | - Android TV 18 | 19 | ![Screenshot 2023-10-08 at 1 23 46 PM](https://github.com/SEAbdulbasit/recipe-app/assets/33172684/bf0c9376-fb57-4498-80f6-4a72300cb8e9) 20 | 21 | ![Screenshot 2024-06-25 at 11 53 05 AM](https://github.com/Atif-09/recipe-app/assets/55842938/16e66d0b-48de-4403-bb74-e2788c756cc3) 22 | 23 | 24 | ## Demo 25 | 26 | ### iOS Demo 27 | https://youtu.be/MZDgPtjTiIs 28 | 29 | ### Web Demo 30 | https://www.youtube.com/watch?v=MZDgPtjTiIs&ab_channel=AbdulBasit 31 | 32 | ### Desktop Demo 33 | https://www.youtube.com/watch?v=6mWrp_-MxW8&ab_channel=AbdulBasit 34 | 35 | 36 | 37 | Screenshot 2023-06-22 at 11 49 28 AM 38 | 39 | 40 | ## Video Demo 41 | You can watch the video demo [here](https://www.youtube.com/watch?v=99i21nB4sI0&ab_channel=AbdulBasit) 42 | 43 | -------------------------------------------------------------------------------- /androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | alias(libs.plugins.android.application) 4 | alias(libs.plugins.compose.compiler) 5 | alias(libs.plugins.compose) 6 | } 7 | 8 | kotlin { 9 | androidTarget() 10 | sourceSets { 11 | val androidMain by getting { 12 | dependencies { 13 | implementation(project(":shared")) 14 | } 15 | } 16 | } 17 | } 18 | 19 | android { 20 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 21 | namespace = "com.myapplication" 22 | 23 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 24 | 25 | defaultConfig { 26 | minSdk = (findProperty("android.minSdk") as String).toInt() 27 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | } 35 | kotlin { 36 | jvmToolchain(11) 37 | } 38 | } 39 | dependencies { 40 | implementation(libs.androidx.window) 41 | } 42 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/kotlin/com/recipeapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.recipeapp 2 | 3 | import MainView 4 | import android.os.Bundle 5 | import androidx.activity.compose.setContent 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.core.view.WindowCompat 8 | 9 | class MainActivity : AppCompatActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | WindowCompat.setDecorFitsSystemWindows(window, false) 14 | 15 | setContent { 16 | MainView() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/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 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /androidApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Recipe App 3 | -------------------------------------------------------------------------------- /automotiveApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | alias(libs.plugins.android.application) 4 | alias(libs.plugins.compose.compiler) 5 | alias(libs.plugins.compose) 6 | } 7 | 8 | kotlin { 9 | androidTarget() 10 | sourceSets { 11 | val androidMain by getting { 12 | dependencies { 13 | implementation(project(":shared")) 14 | } 15 | } 16 | } 17 | } 18 | 19 | android { 20 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 21 | namespace = "com.myapplication" 22 | 23 | sourceSets["main"].manifest.srcFile("src/main/AndroidManifest.xml") 24 | 25 | defaultConfig { 26 | minSdk = (findProperty("android.minSdk") as String).toInt() 27 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | } 35 | kotlin { 36 | jvmToolchain(11) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /automotiveApp/src/androidTest/java/com/recipe/automotiveapp/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.recipe.automotiveapp 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.recipe.automotiveapp", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /automotiveApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 14 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /automotiveApp/src/main/java/com/recipe/automotiveapp/MainActivityAutomotive.kt: -------------------------------------------------------------------------------- 1 | package com.recipe.automotiveapp 2 | 3 | import MainView 4 | import android.os.Bundle 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | 8 | class MainActivityAutomotive : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContent { 12 | MainView(isLargeScreen = true) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /automotiveApp/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 | -------------------------------------------------------------------------------- /automotiveApp/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/automotiveApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /automotiveApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Recipe App 3 | -------------------------------------------------------------------------------- /automotiveApp/src/test/java/com/recipe/automotiveapp/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.recipe.automotiveapp 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // this is necessary to avoid the plugins to be loaded multiple times 3 | // in each subproject's classloader 4 | alias(libs.plugins.kotlin.multiplatform).apply(false) 5 | alias(libs.plugins.android.application).apply(false) 6 | alias(libs.plugins.android.library).apply(false) 7 | alias(libs.plugins.compose).apply(false) 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 15 | mavenLocal() 16 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental") 17 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -rf .idea 3 | ./gradlew clean 4 | rm -rf .gradle 5 | rm -rf build 6 | rm -rf */build 7 | rm -rf iosApp/iosApp.xcworkspace 8 | rm -rf iosApp/Pods 9 | rm -rf iosApp/iosApp.xcodeproj/project.xcworkspace 10 | rm -rf iosApp/iosApp.xcodeproj/xcuserdata 11 | -------------------------------------------------------------------------------- /desktopApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.compose) 7 | } 8 | 9 | kotlin { 10 | jvm() 11 | sourceSets { 12 | val jvmMain by getting { 13 | dependencies { 14 | implementation(compose.desktop.currentOs) 15 | implementation(project(":shared")) 16 | } 17 | } 18 | } 19 | } 20 | 21 | compose.desktop { 22 | application { 23 | mainClass = "MainKt" 24 | 25 | nativeDistributions { 26 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 27 | packageName = "RecipeApp-KMP" 28 | packageVersion = "1.0.0" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /desktopApp/src/jvmMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.Window 2 | import androidx.compose.ui.window.application 3 | 4 | fun main() = application { 5 | Window(title = "Recipe App", onCloseRequest = ::exitApplication) { 6 | MainView() 7 | } 8 | } -------------------------------------------------------------------------------- /docs/39f400ec3abd9c6d43e3.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/39f400ec3abd9c6d43e3.wasm -------------------------------------------------------------------------------- /docs/8433c6b69bfa201b0895.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/8433c6b69bfa201b0895.wasm -------------------------------------------------------------------------------- /docs/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /docs/chef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/chef.png -------------------------------------------------------------------------------- /docs/compose-multiplatform.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 24 | 30 | 36 | 37 | -------------------------------------------------------------------------------- /docs/drawable/01-lemon-cheesecake-bg-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/01-lemon-cheesecake-bg-lg.png -------------------------------------------------------------------------------- /docs/drawable/01-lemon-cheesecake-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/01-lemon-cheesecake-bg.png -------------------------------------------------------------------------------- /docs/drawable/01-lemon-cheesecake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/01-lemon-cheesecake.png -------------------------------------------------------------------------------- /docs/drawable/02-chocolate-cake-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/02-chocolate-cake-1.png -------------------------------------------------------------------------------- /docs/drawable/03-chocolate-donuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/03-chocolate-donuts.png -------------------------------------------------------------------------------- /docs/drawable/04-fluffy-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/04-fluffy-cake.png -------------------------------------------------------------------------------- /docs/drawable/05-macaroons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/05-macaroons.png -------------------------------------------------------------------------------- /docs/drawable/06-white-cream-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/06-white-cream-cake.png -------------------------------------------------------------------------------- /docs/drawable/07-honey-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/07-honey-cake.png -------------------------------------------------------------------------------- /docs/drawable/08-cream-cupcakes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/08-cream-cupcakes.png -------------------------------------------------------------------------------- /docs/drawable/09-fruit-plate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/09-fruit-plate.png -------------------------------------------------------------------------------- /docs/drawable/10-strawberries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/10-strawberries.png -------------------------------------------------------------------------------- /docs/drawable/11-powdered-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/11-powdered-cake.png -------------------------------------------------------------------------------- /docs/drawable/12-chocolate-cake-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/12-chocolate-cake-2.png -------------------------------------------------------------------------------- /docs/drawable/13-strawberry-powdered-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/13-strawberry-powdered-cake.png -------------------------------------------------------------------------------- /docs/drawable/14-fruit-pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/14-fruit-pie.png -------------------------------------------------------------------------------- /docs/drawable/15-apple-pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/15-apple-pie.png -------------------------------------------------------------------------------- /docs/drawable/chef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/drawable/chef.png -------------------------------------------------------------------------------- /docs/font/rubik_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/font/rubik_bold.ttf -------------------------------------------------------------------------------- /docs/font/rubik_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/font/rubik_light.ttf -------------------------------------------------------------------------------- /docs/font/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/font/rubik_medium.ttf -------------------------------------------------------------------------------- /docs/font/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/font/rubik_regular.ttf -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/images/logo.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Recipe App KMP 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/load.mjs: -------------------------------------------------------------------------------- 1 | import { instantiate } from './recipeapp.uninstantiated.mjs'; 2 | 3 | await wasmSetup; 4 | 5 | instantiate({ skia: Module['asm'] }); 6 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Recipe App", 3 | "icons": [ 4 | { 5 | "src": "images/logo.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | } 9 | ], 10 | "start_url": "/", 11 | "display": "standalone", 12 | "background_color": "white" 13 | } -------------------------------------------------------------------------------- /docs/recipeapp.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/recipeapp.wasm -------------------------------------------------------------------------------- /docs/skiko.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/docs/skiko.wasm -------------------------------------------------------------------------------- /docs/webApp.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.webApp=t():e.webApp=t()}(this,(()=>(()=>{"use strict";var e,t,r,n,o,a,i={518:(e,t,r)=>{r.a(e,(async(e,n)=>{try{r.r(t);var o=r(665);await wasmSetup,(0,o._)({skia:Module.asm}),n()}catch(e){n(e)}}),1)},665:(e,t,r)=>{async function n(e={},t=!0){const n=new WeakMap,o=e["./skiko.mjs"]??await r.e(366).then(r.bind(r,366)),a={"kotlin.captureStackTrace":()=>(new Error).stack,"kotlin.wasm.internal.throwJsError":(e,t,r)=>{const n=new Error;throw n.message=e,n.name=t,n.stack=r,n},"kotlin.wasm.internal.stringLength":e=>e.length,"kotlin.wasm.internal.jsExportStringToWasm":(e,t,r,n)=>{const o=new Uint16Array(l.memory.buffer,n,r);let a=0,i=t;for(;a{const n=new Uint16Array(l.memory.buffer,e,t),o=String.fromCharCode.apply(null,n);return null==r?o:r+o},"kotlin.wasm.internal.getJsEmptyString":()=>"","kotlin.wasm.internal.externrefToString":e=>String(e),"kotlin.wasm.internal.externrefEquals":(e,t)=>e===t,"kotlin.wasm.internal.externrefHashCode":(()=>{const e=new DataView(new ArrayBuffer(8)),t=new WeakMap;return r=>{if(null==r)return 0;switch(typeof r){case"object":case"function":return function(e){const r=t.get(e);if(void 0===r){const r=4294967296,n=Math.random()*r|0;return t.set(e,n),n}return r}(r);case"number":return function(t){return(0|t)===t?0|t:(e.setFloat64(0,t,!0),(31*e.getInt32(0,!0)|0)+e.getInt32(4,!0)|0)}(r);case"boolean":return r?1231:1237;default:return function(e){for(var t=0,r=0;rnull==e,"kotlin.wasm.internal.getJsTrue":()=>!0,"kotlin.wasm.internal.getJsFalse":()=>!1,"kotlin.wasm.internal.tryGetOrSetExternrefBox_$external_fun":(e,t)=>function(e,t){if("object"!=typeof e)return t;const r=n.get(e);return void 0!==r?r:(n.set(e,t),t)}(e,t),"kotlin.js.jsCatch":e=>{let t=null;try{e()}catch(e){t=e}return t},"kotlin.js.__convertKotlinClosureToJsClosure_(()->Unit)":e=>()=>l["__callFunction_(()->Unit)"](e),"kotlin.js.jsThrow":e=>{throw e},"kotlin.io.printError":e=>console.error(e),"kotlin.io.printlnImpl":e=>console.log(e),"kotlin.js.jsArrayGet":(e,t)=>e[t],"kotlin.js.length_$external_prop_getter":e=>e.length,"kotlin.js.__convertKotlinClosureToJsClosure_((Js?)->Js?)":e=>t=>l["__callFunction_((Js?)->Js?)"](e,t),"kotlin.js.then_$external_fun":(e,t,r)=>e.then(t,r),"kotlin.js.__convertKotlinClosureToJsClosure_((Js)->Js?)":e=>t=>l["__callFunction_((Js)->Js?)"](e,t),"kotlin.random.initialSeed":()=>Math.random()*Math.pow(2,32)|0,"kotlin.wasm.internal.getJsClassName":e=>e.name,"kotlin.wasm.internal.instanceOf":(e,t)=>e instanceof t,"kotlin.wasm.internal.getConstructor":e=>e.constructor,"kotlinx.browser.window_$external_prop_getter":()=>window,"kotlinx.browser.document_$external_prop_getter":()=>document,"org.w3c.dom.length_$external_prop_getter":e=>e.length,"org.w3c.dom.item_$external_fun":(e,t)=>e.item(t),"org.khronos.webgl.Int8Array_$external_fun":(e,t,r,n,o)=>new Int8Array(e,n?void 0:t,o?void 0:r),"org.khronos.webgl.length_$external_prop_getter":e=>e.length,"org.w3c.dom.css.cursor_$external_prop_setter":(e,t)=>e.cursor=t,"org.w3c.dom.css.height_$external_prop_setter":(e,t)=>e.height=t,"org.w3c.dom.css.width_$external_prop_setter":(e,t)=>e.width=t,"org.w3c.dom.css.style_$external_prop_getter":e=>e.style,"org.w3c.dom.encryptedmedia.__convertKotlinClosureToJsClosure_((Js)->Unit)":e=>t=>l["__callFunction_((Js)->Unit)"](e,t),"org.w3c.dom.events.addEventListener_$external_fun":(e,t,r,n)=>e.addEventListener(t,r,n),"org.w3c.dom.events.addEventListener_$external_fun_1":(e,t,r)=>e.addEventListener(t,r),"org.w3c.dom.events.removeEventListener_$external_fun":(e,t,r)=>e.removeEventListener(t,r),"org.w3c.dom.events.type_$external_prop_getter":e=>e.type,"org.w3c.dom.events.preventDefault_$external_fun":e=>e.preventDefault(),"org.w3c.dom.events.Event_$external_class_instanceof":e=>e instanceof Event,"org.w3c.dom.events.ctrlKey_$external_prop_getter":e=>e.ctrlKey,"org.w3c.dom.events.shiftKey_$external_prop_getter":e=>e.shiftKey,"org.w3c.dom.events.altKey_$external_prop_getter":e=>e.altKey,"org.w3c.dom.events.metaKey_$external_prop_getter":e=>e.metaKey,"org.w3c.dom.events.button_$external_prop_getter":e=>e.button,"org.w3c.dom.events.buttons_$external_prop_getter":e=>e.buttons,"org.w3c.dom.events.offsetX_$external_prop_getter":e=>e.offsetX,"org.w3c.dom.events.offsetY_$external_prop_getter":e=>e.offsetY,"org.w3c.dom.events.MouseEvent_$external_class_instanceof":e=>e instanceof MouseEvent,"org.w3c.dom.events.key_$external_prop_getter":e=>e.key,"org.w3c.dom.events.code_$external_prop_getter":e=>e.code,"org.w3c.dom.events.ctrlKey_$external_prop_getter_1":e=>e.ctrlKey,"org.w3c.dom.events.shiftKey_$external_prop_getter_1":e=>e.shiftKey,"org.w3c.dom.events.altKey_$external_prop_getter_1":e=>e.altKey,"org.w3c.dom.events.metaKey_$external_prop_getter_1":e=>e.metaKey,"org.w3c.dom.events.KeyboardEvent_$external_class_instanceof":e=>e instanceof KeyboardEvent,"org.w3c.dom.events.deltaX_$external_prop_getter":e=>e.deltaX,"org.w3c.dom.events.deltaY_$external_prop_getter":e=>e.deltaY,"org.w3c.dom.events.WheelEvent_$external_class_instanceof":e=>e instanceof WheelEvent,"org.w3c.dom.AddEventListenerOptions_js_code":(e,t,r)=>({passive:e,once:t,capture:r}),"org.w3c.dom.navigator_$external_prop_getter":e=>e.navigator,"org.w3c.dom.devicePixelRatio_$external_prop_getter":e=>e.devicePixelRatio,"org.w3c.dom.requestAnimationFrame_$external_fun":(e,t)=>e.requestAnimationFrame(t),"org.w3c.dom.__convertKotlinClosureToJsClosure_((Double)->Unit)":e=>t=>l["__callFunction_((Double)->Unit)"](e,t),"org.w3c.dom.matchMedia_$external_fun":(e,t)=>e.matchMedia(t),"org.w3c.dom.matches_$external_prop_getter":e=>e.matches,"org.w3c.dom.addListener_$external_fun":(e,t)=>e.addListener(t),"org.w3c.dom.clearTimeout_$external_fun":(e,t,r)=>e.clearTimeout(r?void 0:t),"org.w3c.dom.fetch_$external_fun":(e,t,r,n)=>e.fetch(t,n?void 0:r),"org.w3c.dom.documentElement_$external_prop_getter":e=>e.documentElement,"org.w3c.dom.head_$external_prop_getter":e=>e.head,"org.w3c.dom.createElement_$external_fun":(e,t,r,n)=>e.createElement(t,n?void 0:r),"org.w3c.dom.createTextNode_$external_fun":(e,t)=>e.createTextNode(t),"org.w3c.dom.hasFocus_$external_fun":e=>e.hasFocus(),"org.w3c.dom.getElementById_$external_fun":(e,t)=>e.getElementById(t),"org.w3c.dom.namespaceURI_$external_prop_getter":e=>e.namespaceURI,"org.w3c.dom.localName_$external_prop_getter":e=>e.localName,"org.w3c.dom.clientWidth_$external_prop_getter":e=>e.clientWidth,"org.w3c.dom.clientHeight_$external_prop_getter":e=>e.clientHeight,"org.w3c.dom.getAttribute_$external_fun":(e,t)=>e.getAttribute(t),"org.w3c.dom.getAttributeNS_$external_fun":(e,t,r)=>e.getAttributeNS(t,r),"org.w3c.dom.setAttribute_$external_fun":(e,t,r)=>e.setAttribute(t,r),"org.w3c.dom.getElementsByTagName_$external_fun":(e,t)=>e.getElementsByTagName(t),"org.w3c.dom.getBoundingClientRect_$external_fun":e=>e.getBoundingClientRect(),"org.w3c.dom.Element_$external_class_instanceof":e=>e instanceof Element,"org.w3c.dom.language_$external_prop_getter":e=>e.language,"org.w3c.dom.nodeName_$external_prop_getter":e=>e.nodeName,"org.w3c.dom.childNodes_$external_prop_getter":e=>e.childNodes,"org.w3c.dom.textContent_$external_prop_setter":(e,t)=>e.textContent=t,"org.w3c.dom.lookupPrefix_$external_fun":(e,t)=>e.lookupPrefix(t),"org.w3c.dom.appendChild_$external_fun":(e,t)=>e.appendChild(t),"org.w3c.dom.item_$external_fun_1":(e,t)=>e.item(t),"org.w3c.dom.item_$external_fun_2":(e,t)=>e.item(t),"org.w3c.dom.identifier_$external_prop_getter":e=>e.identifier,"org.w3c.dom.clientX_$external_prop_getter":e=>e.clientX,"org.w3c.dom.clientY_$external_prop_getter":e=>e.clientY,"org.w3c.dom.top_$external_prop_getter":e=>e.top,"org.w3c.dom.left_$external_prop_getter":e=>e.left,"org.w3c.dom.HTMLTitleElement_$external_class_instanceof":e=>e instanceof HTMLTitleElement,"org.w3c.dom.type_$external_prop_setter":(e,t)=>e.type=t,"org.w3c.dom.HTMLStyleElement_$external_class_instanceof":e=>e instanceof HTMLStyleElement,"org.w3c.dom.width_$external_prop_setter":(e,t)=>e.width=t,"org.w3c.dom.height_$external_prop_setter":(e,t)=>e.height=t,"org.w3c.dom.HTMLCanvasElement_$external_class_instanceof":e=>e instanceof HTMLCanvasElement,"org.w3c.dom.changedTouches_$external_prop_getter":e=>e.changedTouches,"org.w3c.dom.TouchEvent_$external_class_instanceof":e=>e instanceof TouchEvent,"org.w3c.dom.matches_$external_prop_getter_1":e=>e.matches,"org.w3c.dom.MediaQueryListEvent_$external_class_instanceof":e=>e instanceof MediaQueryListEvent,"org.w3c.dom.parsing.DOMParser_$external_fun":()=>new DOMParser,"org.w3c.dom.parsing.parseFromString_$external_fun":(e,t,r)=>e.parseFromString(t,r),"org.w3c.fetch.ok_$external_prop_getter":e=>e.ok,"org.w3c.fetch.blob_$external_fun":e=>e.blob(),"kotlinx.coroutines.tryGetProcess":()=>"undefined"!=typeof process&&"function"==typeof process.nextTick?process:null,"kotlinx.coroutines.tryGetWindow":()=>"undefined"!=typeof window&&null!=window&&"function"==typeof window.addEventListener?window:null,"kotlinx.coroutines.nextTick_$external_fun":(e,t)=>e.nextTick(t),"kotlinx.coroutines.error_$external_fun":(e,t)=>e.error(t),"kotlinx.coroutines.console_$external_prop_getter":()=>console,"kotlinx.coroutines.createScheduleMessagePoster":e=>()=>Promise.resolve(0).then(e),"kotlinx.coroutines.__callJsClosure_(()->Unit)":e=>e(),"kotlinx.coroutines.createRescheduleMessagePoster":e=>()=>e.postMessage("dispatchCoroutine","*"),"kotlinx.coroutines.subscribeToWindowMessages":(e,t)=>{e.addEventListener("message",(r=>{r.source==e&&"dispatchCoroutine"==r.data&&(r.stopPropagation(),t())}),!0)},"kotlinx.coroutines.setTimeout":(e,t,r)=>e.setTimeout(t,r),"kotlinx.coroutines.clearTimeout":e=>{"undefined"!=typeof clearTimeout&&clearTimeout(e)},"kotlinx.coroutines.setTimeout_$external_fun":(e,t)=>setTimeout(e,t),"org.jetbrains.skiko.w3c.language_$external_prop_getter":e=>e.language,"org.jetbrains.skiko.w3c.userAgent_$external_prop_getter":e=>e.userAgent,"org.jetbrains.skiko.w3c.navigator_$external_prop_getter":e=>e.navigator,"org.jetbrains.skiko.w3c.performance_$external_prop_getter":e=>e.performance,"org.jetbrains.skiko.w3c.requestAnimationFrame_$external_fun":(e,t)=>e.requestAnimationFrame(t),"org.jetbrains.skiko.w3c.window_$external_object_getInstance":()=>window,"org.jetbrains.skiko.w3c.now_$external_fun":e=>e.now(),"org.jetbrains.skiko.w3c.width_$external_prop_getter":e=>e.width,"org.jetbrains.skiko.w3c.height_$external_prop_getter":e=>e.height,"org.jetbrains.skiko.w3c.HTMLCanvasElement_$external_class_instanceof":e=>e instanceof HTMLCanvasElement,"org.jetbrains.skia.impl.FinalizationRegistry_$external_fun":e=>new FinalizationRegistry(e),"org.jetbrains.skia.impl.register_$external_fun":(e,t,r)=>e.register(t,r),"org.jetbrains.skia.impl.unregister_$external_fun":(e,t)=>e.unregister(t),"org.jetbrains.skia.impl._releaseLocalCallbackScope_$external_fun":()=>o._releaseLocalCallbackScope(),"org.jetbrains.skiko.getNavigatorInfo":()=>navigator.userAgentData?navigator.userAgentData.platform:navigator.platform,"org.jetbrains.skiko.wasm.createContext_$external_fun":(e,t,r)=>e.createContext(t,r),"org.jetbrains.skiko.wasm.makeContextCurrent_$external_fun":(e,t)=>e.makeContextCurrent(t),"org.jetbrains.skiko.wasm.GL_$external_object_getInstance":()=>o.GL,"org.jetbrains.skiko.wasm.createDefaultContextAttributes":()=>({alpha:1,depth:1,stencil:8,antialias:0,premultipliedAlpha:1,preserveDrawingBuffer:0,preferLowPowerToHighPerformance:0,failIfMajorPerformanceCaveat:0,enableExtensionsByDefault:1,explicitSwapControl:0,renderViaOffscreenBackBuffer:0,majorVersion:2}),"androidx.compose.ui.text.intl.getUserPreferredLanguagesAsArray":()=>window.navigator.languages,"androidx.compose.ui.text.intl.parseLanguageTagToIntlLocale":e=>new Intl.Locale(e),"androidx.compose.ui.text.intl.language_$external_prop_getter":e=>e.language,"androidx.compose.ui.text.intl.region_$external_prop_getter":e=>e.region,"androidx.compose.ui.text.intl.baseName_$external_prop_getter":e=>e.baseName,"androidx.compose.ui.window.isMatchMediaSupported":()=>null!=window.matchMedia,"androidx.compose.ui.window.force_$external_prop_getter":e=>e.force,"org.jetbrains.compose.resources.Locale_$external_fun":e=>new Intl.Locale(e),"org.jetbrains.compose.resources.language_$external_prop_getter":e=>e.language,"org.jetbrains.compose.resources.region_$external_prop_getter":e=>e.region,"org.jetbrains.compose.resources.jsExportBlobAsArrayBuffer":e=>e.arrayBuffer(),"org.jetbrains.compose.resources.jsExportInt8ArrayToWasm":(e,t,r)=>{new Int8Array(l.memory.buffer,r,t).set(e)}};let i,s,l;const c="undefined"!=typeof process&&"node"===process.release.name,_=!c&&("undefined"!=typeof d8||"undefined"!=typeof inIon||"undefined"!=typeof jscOptions),p=!c&&!_&&"undefined"!=typeof window;if(!c&&!_&&!p)throw"Supported JS engine not detected";const d="./recipeapp.wasm",g={js_code:a,"./skiko.mjs":e["./skiko.mjs"]??await r.e(366).then(r.bind(r,366))};try{if(c){s=(await import("node:module")).default.createRequire("file:///Users/abdulbasit/AndroidStudioProjects/recipe-app/build/js/packages/recipeapp/kotlin/recipeapp.uninstantiated.mjs");const e=s("fs"),t=s("path"),r=s("url").fileURLToPath("file:///Users/abdulbasit/AndroidStudioProjects/recipe-app/build/js/packages/recipeapp/kotlin/recipeapp.uninstantiated.mjs"),n=t.dirname(r),o=e.readFileSync(t.resolve(n,d)),a=new WebAssembly.Module(o);i=new WebAssembly.Instance(a,g)}if(_){const e=read(d,"binary"),t=new WebAssembly.Module(e);i=new WebAssembly.Instance(t,g)}p&&(i=(await WebAssembly.instantiateStreaming(fetch(d),g)).instance)}catch(e){if(e instanceof WebAssembly.CompileError){let e="Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling proposals.\nFor more information, see https://kotl.in/wasm-help\n";if(p)console.error(e);else{const t="\n"+e;"undefined"!=typeof console&&void 0!==console.log?console.log(t):print(t)}}throw e}return l=i.exports,t&&l._initialize(),{instance:i,exports:l}}r.d(t,{_:()=>n})}},s={};function l(e){var t=s[e];if(void 0!==t)return t.exports;var r=s[e]={exports:{}};return i[e](r,r.exports,l),r.exports}return l.m=i,e="function"==typeof Symbol?Symbol("webpack queues"):"__webpack_queues__",t="function"==typeof Symbol?Symbol("webpack exports"):"__webpack_exports__",r="function"==typeof Symbol?Symbol("webpack error"):"__webpack_error__",n=e=>{e&&!e.d&&(e.d=1,e.forEach((e=>e.r--)),e.forEach((e=>e.r--?e.r++:e())))},l.a=(o,a,i)=>{var s;i&&((s=[]).d=1);var l,c,_,p=new Set,d=o.exports,g=new Promise(((e,t)=>{_=t,c=e}));g[t]=d,g[e]=e=>(s&&e(s),p.forEach(e),g.catch((e=>{}))),o.exports=g,a((o=>{var a;l=(o=>o.map((o=>{if(null!==o&&"object"==typeof o){if(o[e])return o;if(o.then){var a=[];a.d=0,o.then((e=>{i[t]=e,n(a)}),(e=>{i[r]=e,n(a)}));var i={};return i[e]=e=>e(a),i}}var s={};return s[e]=e=>{},s[t]=o,s})))(o);var i=()=>l.map((e=>{if(e[r])throw e[r];return e[t]})),c=new Promise((t=>{(a=()=>t(i)).r=0;var r=e=>e!==s&&!p.has(e)&&(p.add(e),e&&!e.d&&(a.r++,e.push(a)));l.map((t=>t[e](r)))}));return a.r?c:i()}),(e=>(e?_(g[r]=e):c(d),n(s)))),s&&(s.d=0)},l.d=(e,t)=>{for(var r in t)l.o(t,r)&&!l.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},l.f={},l.e=e=>Promise.all(Object.keys(l.f).reduce(((t,r)=>(l.f[r](e,t),t)),[])),l.u=e=>e+".js",l.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),l.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o={},a="webApp:",l.l=(e,t,r,n)=>{if(o[e])o[e].push(t);else{var i,s;if(void 0!==r)for(var c=document.getElementsByTagName("script"),_=0;_{i.onerror=i.onload=null,clearTimeout(g);var n=o[e];if(delete o[e],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach((e=>e(r))),t)return t(r)},g=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),s&&document.head.appendChild(i)}},l.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;l.g.importScripts&&(e=l.g.location+"");var t=l.g.document;if(!e&&t&&(t.currentScript&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var n=r.length-1;n>-1&&!e;)e=r[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),l.p=e})(),(()=>{l.b=document.baseURI||self.location.href;var e={179:0};l.f.j=(t,r)=>{var n=l.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else{var o=new Promise(((r,o)=>n=e[t]=[r,o]));r.push(n[2]=o);var a=l.p+l.u(t),i=new Error;l.l(a,(r=>{if(l.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var o=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;i.message="Loading chunk "+t+" failed.\n("+o+": "+a+")",i.name="ChunkLoadError",i.type=o,i.request=a,n[1](i)}}),"chunk-"+t,t)}};var t=(t,r)=>{var n,o,[a,i,s]=r,c=0;if(a.some((t=>0!==e[t]))){for(n in i)l.o(i,n)&&(l.m[n]=i[n]);s&&s(l)}for(t&&t(r);c '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.myapplication.MyApplication 3 | APP_NAME=My application 4 | -------------------------------------------------------------------------------- /iosApp/Podfile: -------------------------------------------------------------------------------- 1 | target 'iosApp' do 2 | use_frameworks! 3 | platform :ios, '14.1' 4 | pod 'shared', :path => '../shared' 5 | end -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 11 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 12 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 13 | 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; 14 | CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 19 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 20 | 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 21 | 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; 22 | 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 7555FF7B242A565900829871 /* My application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My application.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 27 | FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | F85CB1118929364A9C6EFABC /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */, 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 058557D7273AAEEB004C7B11 /* Preview Content */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, 46 | ); 47 | path = "Preview Content"; 48 | sourceTree = ""; 49 | }; 50 | 42799AB246E5F90AF97AA0EF /* Frameworks */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */, 54 | ); 55 | name = Frameworks; 56 | sourceTree = ""; 57 | }; 58 | 7555FF72242A565900829871 = { 59 | isa = PBXGroup; 60 | children = ( 61 | AB1DB47929225F7C00F7AF9C /* Configuration */, 62 | 7555FF7D242A565900829871 /* iosApp */, 63 | 7555FF7C242A565900829871 /* Products */, 64 | FEFF387C0A8D172AA4D59CAE /* Pods */, 65 | 42799AB246E5F90AF97AA0EF /* Frameworks */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | 7555FF7C242A565900829871 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 7555FF7B242A565900829871 /* My application.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | 7555FF7D242A565900829871 /* iosApp */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 058557BA273AAA24004C7B11 /* Assets.xcassets */, 81 | 7555FF82242A565900829871 /* ContentView.swift */, 82 | 7555FF8C242A565B00829871 /* Info.plist */, 83 | 2152FB032600AC8F00CF470E /* iOSApp.swift */, 84 | 058557D7273AAEEB004C7B11 /* Preview Content */, 85 | ); 86 | path = iosApp; 87 | sourceTree = ""; 88 | }; 89 | AB1DB47929225F7C00F7AF9C /* Configuration */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | AB3632DC29227652001CCB65 /* Config.xcconfig */, 93 | ); 94 | path = Configuration; 95 | sourceTree = ""; 96 | }; 97 | FEFF387C0A8D172AA4D59CAE /* Pods */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */, 101 | FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */, 102 | ); 103 | path = Pods; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 7555FF7A242A565900829871 /* iosApp */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; 112 | buildPhases = ( 113 | 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */, 114 | 7555FF77242A565900829871 /* Sources */, 115 | 7555FF79242A565900829871 /* Resources */, 116 | F85CB1118929364A9C6EFABC /* Frameworks */, 117 | 3858583145BE3627C171F808 /* [CP] Copy Pods Resources */, 118 | ); 119 | buildRules = ( 120 | ); 121 | dependencies = ( 122 | ); 123 | name = iosApp; 124 | productName = iosApp; 125 | productReference = 7555FF7B242A565900829871 /* My application.app */; 126 | productType = "com.apple.product-type.application"; 127 | }; 128 | /* End PBXNativeTarget section */ 129 | 130 | /* Begin PBXProject section */ 131 | 7555FF73242A565900829871 /* Project object */ = { 132 | isa = PBXProject; 133 | attributes = { 134 | LastSwiftUpdateCheck = 1130; 135 | LastUpgradeCheck = 1130; 136 | ORGANIZATIONNAME = orgName; 137 | TargetAttributes = { 138 | 7555FF7A242A565900829871 = { 139 | CreatedOnToolsVersion = 11.3.1; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; 144 | compatibilityVersion = "Xcode 9.3"; 145 | developmentRegion = en; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | en, 149 | Base, 150 | ); 151 | mainGroup = 7555FF72242A565900829871; 152 | productRefGroup = 7555FF7C242A565900829871 /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | 7555FF7A242A565900829871 /* iosApp */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | 7555FF79242A565900829871 /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, 167 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXResourcesBuildPhase section */ 172 | 173 | /* Begin PBXShellScriptBuildPhase section */ 174 | 3858583145BE3627C171F808 /* [CP] Copy Pods Resources */ = { 175 | isa = PBXShellScriptBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | ); 179 | inputFileListPaths = ( 180 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", 181 | ); 182 | name = "[CP] Copy Pods Resources"; 183 | outputFileListPaths = ( 184 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | shellPath = /bin/sh; 188 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; 189 | showEnvVarsInLog = 0; 190 | }; 191 | 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */ = { 192 | isa = PBXShellScriptBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | inputFileListPaths = ( 197 | ); 198 | inputPaths = ( 199 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 200 | "${PODS_ROOT}/Manifest.lock", 201 | ); 202 | name = "[CP] Check Pods Manifest.lock"; 203 | outputFileListPaths = ( 204 | ); 205 | outputPaths = ( 206 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | shellPath = /bin/sh; 210 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 211 | showEnvVarsInLog = 0; 212 | }; 213 | /* End PBXShellScriptBuildPhase section */ 214 | 215 | /* Begin PBXSourcesBuildPhase section */ 216 | 7555FF77242A565900829871 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, 221 | 7555FF83242A565900829871 /* ContentView.swift in Sources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXSourcesBuildPhase section */ 226 | 227 | /* Begin XCBuildConfiguration section */ 228 | 7555FFA3242A565B00829871 /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_ENABLE_OBJC_WEAK = YES; 240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_COMMA = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 247 | CLANG_WARN_EMPTY_BODY = YES; 248 | CLANG_WARN_ENUM_CONVERSION = YES; 249 | CLANG_WARN_INFINITE_RECURSION = YES; 250 | CLANG_WARN_INT_CONVERSION = YES; 251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 257 | CLANG_WARN_STRICT_PROTOTYPES = YES; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | COPY_PHASE_STRIP = NO; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | ENABLE_TESTABILITY = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu11; 267 | GCC_DYNAMIC_NO_PIC = NO; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_OPTIMIZATION_LEVEL = 0; 270 | GCC_PREPROCESSOR_DEFINITIONS = ( 271 | "DEBUG=1", 272 | "$(inherited)", 273 | ); 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 281 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 282 | MTL_FAST_MATH = YES; 283 | ONLY_ACTIVE_ARCH = YES; 284 | SDKROOT = iphoneos; 285 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 286 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 287 | }; 288 | name = Debug; 289 | }; 290 | 7555FFA4242A565B00829871 /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_ANALYZER_NONNULL = YES; 296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_ENABLE_OBJC_WEAK = YES; 302 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 303 | CLANG_WARN_BOOL_CONVERSION = YES; 304 | CLANG_WARN_COMMA = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | COPY_PHASE_STRIP = NO; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu11; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 337 | MTL_ENABLE_DEBUG_INFO = NO; 338 | MTL_FAST_MATH = YES; 339 | SDKROOT = iphoneos; 340 | SWIFT_COMPILATION_MODE = wholemodule; 341 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 342 | VALIDATE_PRODUCT = YES; 343 | }; 344 | name = Release; 345 | }; 346 | 7555FFA6242A565B00829871 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | baseConfigurationReference = 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */; 349 | buildSettings = { 350 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 351 | CODE_SIGN_IDENTITY = "Apple Development"; 352 | CODE_SIGN_STYLE = Automatic; 353 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 354 | DEVELOPMENT_TEAM = "${TEAM_ID}"; 355 | ENABLE_PREVIEWS = YES; 356 | INFOPLIST_FILE = iosApp/Info.plist; 357 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 358 | LD_RUNPATH_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "@executable_path/Frameworks", 361 | ); 362 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; 363 | PRODUCT_NAME = "${APP_NAME}"; 364 | PROVISIONING_PROFILE_SPECIFIER = ""; 365 | SWIFT_VERSION = 5.0; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | 7555FFA7242A565B00829871 /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | baseConfigurationReference = FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | CODE_SIGN_IDENTITY = "Apple Development"; 376 | CODE_SIGN_STYLE = Automatic; 377 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 378 | DEVELOPMENT_TEAM = "${TEAM_ID}"; 379 | ENABLE_PREVIEWS = YES; 380 | INFOPLIST_FILE = iosApp/Info.plist; 381 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 382 | LD_RUNPATH_SEARCH_PATHS = ( 383 | "$(inherited)", 384 | "@executable_path/Frameworks", 385 | ); 386 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; 387 | PRODUCT_NAME = "${APP_NAME}"; 388 | PROVISIONING_PROFILE_SPECIFIER = ""; 389 | SWIFT_VERSION = 5.0; 390 | TARGETED_DEVICE_FAMILY = "1,2"; 391 | }; 392 | name = Release; 393 | }; 394 | /* End XCBuildConfiguration section */ 395 | 396 | /* Begin XCConfigurationList section */ 397 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | 7555FFA3242A565B00829871 /* Debug */, 401 | 7555FFA4242A565B00829871 /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { 407 | isa = XCConfigurationList; 408 | buildConfigurations = ( 409 | 7555FFA6242A565B00829871 /* Debug */, 410 | 7555FFA7242A565B00829871 /* Release */, 411 | ); 412 | defaultConfigurationIsVisible = 0; 413 | defaultConfigurationName = Release; 414 | }; 415 | /* End XCConfigurationList section */ 416 | }; 417 | rootObject = 7555FF73242A565900829871 /* Project object */; 418 | } 419 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import shared 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | Main_iosKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea() 17 | } 18 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | CADisableMinimumFrameDurationOnPhone 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /node_modules/.yarn-integrity: -------------------------------------------------------------------------------- 1 | { 2 | "systemParams": "darwin-arm64-108", 3 | "modulesFolders": [], 4 | "flags": [], 5 | "linkedModules": [], 6 | "topLevelPatterns": [], 7 | "lockfileEntries": {}, 8 | "files": [], 9 | "artifacts": {} 10 | } -------------------------------------------------------------------------------- /readme_images/android_app_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/android_app_running.png -------------------------------------------------------------------------------- /readme_images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/banner.png -------------------------------------------------------------------------------- /readme_images/desktop_app_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/desktop_app_running.png -------------------------------------------------------------------------------- /readme_images/edit_run_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/edit_run_config.png -------------------------------------------------------------------------------- /readme_images/ios_app_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/ios_app_running.png -------------------------------------------------------------------------------- /readme_images/open_project_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/open_project_view.png -------------------------------------------------------------------------------- /readme_images/run_on_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/run_on_android.png -------------------------------------------------------------------------------- /readme_images/run_on_desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/run_on_desktop.png -------------------------------------------------------------------------------- /readme_images/target_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/target_device.png -------------------------------------------------------------------------------- /readme_images/text_field_added.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/readme_images/text_field_added.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "RecipeAppKMP" 2 | 3 | include(":androidApp") 4 | include(":shared") 5 | include(":desktopApp") 6 | include(":webApp") 7 | include(":tvApp") 8 | include(":automotiveApp") 9 | 10 | 11 | pluginManagement { 12 | repositories { 13 | gradlePluginPortal() 14 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 15 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental") 16 | google{ 17 | mavenContent { 18 | includeGroupAndSubgroups("androidx") 19 | includeGroupAndSubgroups("com.android") 20 | includeGroupAndSubgroups("com.google") 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | 3 | plugins { 4 | kotlin("native.cocoapods") 5 | alias(libs.plugins.kotlin.multiplatform) 6 | alias(libs.plugins.android.library) 7 | alias(libs.plugins.compose.compiler) 8 | alias(libs.plugins.compose) 9 | } 10 | 11 | kotlin { 12 | androidTarget() 13 | 14 | jvm("desktop") 15 | 16 | js(IR) { 17 | browser() 18 | } 19 | 20 | @OptIn(ExperimentalWasmDsl::class) 21 | wasmJs { 22 | browser() 23 | } 24 | 25 | iosX64() 26 | iosArm64() 27 | iosSimulatorArm64() 28 | 29 | cocoapods { 30 | version = "1.0.0" 31 | summary = "Some description for the Shared Module" 32 | homepage = "Link to the Shared Module homepage" 33 | ios.deploymentTarget = "14.1" 34 | podfile = project.file("../iosApp/Podfile") 35 | framework { 36 | baseName = "shared" 37 | isStatic = true 38 | } 39 | //extraSpecAttributes["resources"] = "['src/commonMain/resources/**', 'src/iosMain/resources/**']" 40 | } 41 | 42 | sourceSets { 43 | val commonMain by getting { 44 | dependencies { 45 | implementation(compose.runtime) 46 | implementation(compose.foundation) 47 | implementation(compose.material) 48 | @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) 49 | implementation(compose.components.resources) 50 | implementation(libs.navigation.compose) 51 | } 52 | } 53 | 54 | androidMain { 55 | dependencies { 56 | api(libs.androidx.activity.compose) 57 | api(libs.androidx.appcompat) 58 | api(libs.androidx.core.ktx) 59 | } 60 | } 61 | 62 | val desktopMain by getting { 63 | dependencies { 64 | implementation(compose.desktop.common) 65 | dependencies { 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | android { 73 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 74 | namespace = "com.recipeapp.common" 75 | 76 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 77 | sourceSets["main"].res.srcDirs("src/androidMain/res") 78 | sourceSets["main"].resources.srcDirs("src/commonMain/resources") 79 | sourceSets["main"].res.srcDirs("src/androidMain/res", "src/commonMain/resources") 80 | 81 | defaultConfig { 82 | minSdk = (findProperty("android.minSdk") as String).toInt() 83 | testOptions.targetSdk = (findProperty("android.targetSdk") as String).toInt() 84 | } 85 | compileOptions { 86 | sourceCompatibility = JavaVersion.VERSION_11 87 | targetCompatibility = JavaVersion.VERSION_11 88 | } 89 | kotlin { 90 | jvmToolchain(11) 91 | } 92 | } -------------------------------------------------------------------------------- /shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/main.android.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import androidx.compose.runtime.DisposableEffect 3 | import androidx.compose.runtime.rememberCoroutineScope 4 | import androidx.compose.ui.platform.LocalContext 5 | import kotlinx.coroutines.flow.collect 6 | import kotlinx.coroutines.flow.onEach 7 | import kotlinx.coroutines.flow.receiveAsFlow 8 | import kotlinx.coroutines.launch 9 | import sensor.SensorDataManager 10 | import sensor.SensorManagerImpl 11 | 12 | @Composable 13 | fun MainView(isLargeScreen: Boolean = false) { 14 | 15 | val sensorManager = SensorManagerImpl() 16 | 17 | val context = LocalContext.current 18 | val scope = rememberCoroutineScope() 19 | 20 | DisposableEffect(Unit) { 21 | val dataManager = SensorDataManager(context) 22 | dataManager.init() 23 | 24 | val job = scope.launch { 25 | dataManager.data 26 | .receiveAsFlow() 27 | .onEach { sensorManager.listener?.onUpdate(it) } 28 | .collect() 29 | } 30 | 31 | onDispose { 32 | dataManager.cancel() 33 | job.cancel() 34 | } 35 | } 36 | 37 | App(sensorManager, isLargeScreen) 38 | } 39 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/sensor/SensorDataManager.kt: -------------------------------------------------------------------------------- 1 | package sensor 2 | 3 | import android.content.Context 4 | import android.hardware.Sensor 5 | import android.hardware.SensorEvent 6 | import android.hardware.SensorEventListener 7 | import android.hardware.SensorManager 8 | import android.util.Log 9 | import kotlinx.coroutines.channels.Channel 10 | import kotlin.math.PI 11 | 12 | 13 | /** 14 | * Created by abdulbasit on 22/07/2023. 15 | */ 16 | class SensorDataManager(context: Context) : SensorEventListener { 17 | 18 | private val sensorManager by lazy { 19 | context.getSystemService(Context.SENSOR_SERVICE) as SensorManager 20 | } 21 | 22 | fun init() { 23 | val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) 24 | val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) 25 | 26 | sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI) 27 | sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI) 28 | } 29 | 30 | private var gravity: FloatArray? = null 31 | private var geomagnetic: FloatArray? = null 32 | 33 | val data: Channel = Channel(Channel.UNLIMITED) 34 | 35 | override fun onSensorChanged(event: SensorEvent?) { 36 | if (event?.sensor?.type == Sensor.TYPE_GRAVITY) 37 | gravity = event.values 38 | 39 | if (event?.sensor?.type == Sensor.TYPE_MAGNETIC_FIELD) 40 | geomagnetic = event.values 41 | 42 | if (gravity != null && geomagnetic != null) { 43 | var r = FloatArray(9) 44 | var i = FloatArray(9) 45 | 46 | if (SensorManager.getRotationMatrix(r, i, gravity, geomagnetic)) { 47 | var orientation = FloatArray(3) 48 | SensorManager.getOrientation(r, orientation) 49 | val adjustedPitch = orientation[1] - (PI.toFloat() / 2) 50 | 51 | Log.d( 52 | "Sensor Values ", 53 | "Sensor values are ${orientation[2]} and pitch is ${orientation[1] - 1.50}" 54 | ) 55 | 56 | data.trySend( 57 | SensorData( 58 | roll = orientation[2], 59 | pitch = orientation[1] 60 | ) 61 | ) 62 | } 63 | } 64 | } 65 | 66 | fun cancel() { 67 | sensorManager.unregisterListener(this) 68 | } 69 | 70 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} 71 | } 72 | 73 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/sensor/SensorManagerImpl.kt: -------------------------------------------------------------------------------- 1 | package sensor 2 | 3 | class SensorManagerImpl : SensorManager { 4 | var listener: Listener? = null 5 | 6 | override fun registerListener(listener: Listener) { 7 | this.listener = listener 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake-bg-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake-bg-lg.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake-bg.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/01-lemon-cheesecake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/02-chocolate-cake-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/02-chocolate-cake-1.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/03-chocolate-donuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/03-chocolate-donuts.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/04-fluffy-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/04-fluffy-cake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/05-macaroons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/05-macaroons.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/06-white-cream-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/06-white-cream-cake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/07-honey-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/07-honey-cake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/08-cream-cupcakes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/08-cream-cupcakes.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/09-fruit-plate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/09-fruit-plate.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/10-strawberries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/10-strawberries.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/11-powdered-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/11-powdered-cake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/12-chocolate-cake-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/12-chocolate-cake-2.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/13-strawberry-powdered-cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/13-strawberry-powdered-cake.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/14-fruit-pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/14-fruit-pie.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/15-apple-pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/15-apple-pie.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/drawable/chef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/drawable/chef.png -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/font/rubik_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/font/rubik_bold.ttf -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/font/rubik_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/font/rubik_light.ttf -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/font/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/font/rubik_medium.ttf -------------------------------------------------------------------------------- /shared/src/commonMain/composeResources/font/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/shared/src/commonMain/composeResources/font/rubik_regular.ttf -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.ExperimentalSharedTransitionApi 2 | import androidx.compose.animation.SharedTransitionLayout 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.Modifier 10 | import androidx.navigation.compose.NavHost 11 | import androidx.navigation.compose.composable 12 | import androidx.navigation.compose.rememberNavController 13 | import details.RecipeDetails 14 | import model.recipesList 15 | import recipeslist.RecipesListScreen 16 | import sensor.SensorManager 17 | 18 | @OptIn(ExperimentalSharedTransitionApi::class) 19 | @Composable 20 | fun App(sensorManager: SensorManager?, isLarge: Boolean = false) { 21 | 22 | val fontFamily = getFontFamily() 23 | val navController = rememberNavController() 24 | 25 | MaterialTheme(typography = getTypography(fontFamily)) { 26 | val items by remember { mutableStateOf(recipesList) } 27 | var currentRecipe = items.first() 28 | 29 | SharedTransitionLayout { 30 | val sharedTransitionScope = this 31 | NavHost( 32 | navController = navController, 33 | startDestination = RecipeAppScreen.List.name, 34 | modifier = Modifier.fillMaxSize() 35 | ) { 36 | composable(route = RecipeAppScreen.List.name) { 37 | RecipesListScreen(animatedVisibilityScope = this, 38 | sharedTransactionScope = sharedTransitionScope, 39 | isLarge = isLarge, 40 | items = items, 41 | onClick = { recipe -> 42 | currentRecipe = recipe 43 | navController.navigate(RecipeAppScreen.Details.name) 44 | }) 45 | } 46 | composable(route = RecipeAppScreen.Details.name) { 47 | RecipeDetails(animatedVisibilityScope = this, 48 | sharedTransactionScope = sharedTransitionScope, 49 | isLarge = isLarge, 50 | sensorManager = sensorManager, 51 | recipe = currentRecipe, 52 | goBack = { navController.popBackStack() }) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | enum class RecipeAppScreen { 60 | List, Details, 61 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/Colors.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.graphics.Color 2 | 3 | /** 4 | * Created by abdulbasit on 21/06/2023. 5 | */ 6 | val primary = Color(0xff7AC5C1); 7 | val primaryLighter = Color(0xffE6F9FF); 8 | val white = Color(0xffffffff); 9 | val realBlack = Color(0xff000000); 10 | val text = Color(0xff0F1E31); 11 | val black = Color(0xff0F1E31); 12 | val blackLight = Color(0xff1B2C41); 13 | val orangeDark = Color(0xffCE5A01); 14 | val yellow = Color(0xffFFEF7D); 15 | val sugar = Color(0xffFBF5E9); 16 | val honey = Color(0xffDA7C16); 17 | val pinkLight = Color(0xffF9B7B6); 18 | val green = Color(0xffADBE56); 19 | val red = Color(0xffCF252F); 20 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/Screen.kt: -------------------------------------------------------------------------------- 1 | import model.Recipe 2 | 3 | /** 4 | * Created by abdulbasit on 23/06/2023. 5 | */ 6 | sealed interface Screens { 7 | data object RecipesList : Screens 8 | data class RecipeDetails( 9 | val recipe: Recipe 10 | ) : Screens 11 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/Typography.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.material.Typography 2 | import androidx.compose.runtime.Composable 3 | import androidx.compose.ui.text.TextStyle 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | import androidx.compose.ui.unit.sp 7 | import org.jetbrains.compose.resources.Font 8 | import recipeappkmp.shared.generated.resources.Res 9 | import recipeappkmp.shared.generated.resources.rubik_bold 10 | import recipeappkmp.shared.generated.resources.rubik_light 11 | import recipeappkmp.shared.generated.resources.rubik_medium 12 | import recipeappkmp.shared.generated.resources.rubik_regular 13 | 14 | /** 15 | * Created by abdulbasit on 04/07/2024. 16 | */ 17 | 18 | @Composable 19 | fun getFontFamily(): FontFamily { 20 | val rubikFamily = FontFamily( 21 | listOf( 22 | Font(Res.font.rubik_light), 23 | Font(Res.font.rubik_medium), 24 | Font(Res.font.rubik_regular), 25 | Font(Res.font.rubik_bold) 26 | ) 27 | ) 28 | return rubikFamily 29 | } 30 | 31 | fun getTypography(fontFamily: FontFamily): Typography { 32 | return Typography( 33 | h1 = TextStyle( 34 | fontWeight = FontWeight.Light, 35 | fontSize = 45.sp, 36 | letterSpacing = (-1.5).sp, 37 | fontFamily = fontFamily 38 | ), 39 | h2 = TextStyle( 40 | fontWeight = FontWeight.Light, 41 | fontSize = 35.sp, 42 | letterSpacing = (-0.5).sp, 43 | fontFamily = fontFamily 44 | ), 45 | h3 = TextStyle( 46 | fontWeight = FontWeight.Normal, 47 | fontSize = 30.sp, 48 | letterSpacing = 0.sp, 49 | fontFamily = fontFamily 50 | ), 51 | h4 = TextStyle( 52 | fontWeight = FontWeight.Normal, 53 | fontSize = 25.sp, 54 | letterSpacing = 0.25.sp, 55 | fontFamily = fontFamily 56 | ), 57 | h5 = TextStyle( 58 | fontWeight = FontWeight.Normal, 59 | fontSize = 20.sp, 60 | letterSpacing = 0.sp, 61 | fontFamily = fontFamily 62 | ), 63 | h6 = TextStyle( 64 | fontWeight = FontWeight.Medium, 65 | fontSize = 20.sp, 66 | letterSpacing = 0.15.sp, 67 | fontFamily = fontFamily 68 | ), 69 | subtitle1 = TextStyle( 70 | fontWeight = FontWeight.Normal, 71 | fontSize = 16.sp, 72 | letterSpacing = 0.15.sp, 73 | fontFamily = fontFamily 74 | ), 75 | subtitle2 = TextStyle( 76 | fontWeight = FontWeight.Medium, 77 | fontSize = 14.sp, 78 | letterSpacing = 0.1.sp, 79 | fontFamily = fontFamily 80 | ), 81 | body1 = TextStyle( 82 | fontWeight = FontWeight.Normal, 83 | fontSize = 16.sp, 84 | letterSpacing = 0.5.sp, 85 | fontFamily = fontFamily 86 | ), 87 | body2 = TextStyle( 88 | fontWeight = FontWeight.Normal, 89 | fontSize = 14.sp, 90 | letterSpacing = 0.25.sp, 91 | fontFamily = fontFamily 92 | ), 93 | button = TextStyle( 94 | fontWeight = FontWeight.Medium, 95 | fontSize = 14.sp, 96 | letterSpacing = 1.25.sp, 97 | fontFamily = fontFamily 98 | ), 99 | caption = TextStyle( 100 | fontWeight = FontWeight.Normal, 101 | fontSize = 12.sp, 102 | letterSpacing = 0.4.sp, 103 | fontFamily = fontFamily 104 | ), 105 | overline = TextStyle( 106 | fontWeight = FontWeight.Normal, 107 | fontSize = 10.sp, 108 | letterSpacing = 1.5.sp, 109 | fontFamily = fontFamily 110 | ) 111 | ) 112 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/AnimateInEffect.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.core.Animatable 2 | import androidx.compose.animation.core.LinearEasing 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.graphicsLayer 10 | import kotlinx.coroutines.delay 11 | import model.Recipe 12 | 13 | @Composable 14 | fun AnimateInEffect( 15 | intervalStart: Float = 0f, 16 | content: @Composable () -> Unit, 17 | recipe: Recipe 18 | ) { 19 | val visibility = remember { Animatable(0f) } 20 | val offset = remember { Animatable(30f) } 21 | 22 | LaunchedEffect(recipe) { 23 | delay((intervalStart * 1000).toLong()) 24 | visibility.animateTo( 25 | targetValue = 1f, 26 | animationSpec = tween( 27 | durationMillis = 300, 28 | easing = LinearEasing 29 | ) 30 | ) 31 | } 32 | LaunchedEffect(recipe) { 33 | delay((intervalStart * 1000).toLong()) 34 | delay(intervalStart.toLong()) 35 | offset.animateTo( 36 | targetValue = 0f, 37 | animationSpec = tween( 38 | durationMillis = 300, 39 | easing = LinearEasing 40 | ) 41 | ) 42 | } 43 | 44 | Box(modifier = Modifier.graphicsLayer { 45 | this.translationY = offset.value 46 | this.alpha = visibility.value 47 | }) { 48 | content() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/FadeInEffect.kt: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | import androidx.compose.animation.ExperimentalAnimationApi 4 | import androidx.compose.animation.core.Animatable 5 | import androidx.compose.animation.core.LinearEasing 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.LaunchedEffect 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.alpha 13 | 14 | 15 | /** 16 | * Created by abdulbasit on 25/06/2023. 17 | */ 18 | 19 | @ExperimentalAnimationApi 20 | @Composable 21 | fun FadeInEffect( 22 | intervalStart: Int = 500, 23 | content: @Composable () -> Unit 24 | ) { 25 | 26 | val alpha = remember { Animatable(0f) } 27 | 28 | LaunchedEffect(Unit) { 29 | alpha.animateTo( 30 | targetValue = 1f, 31 | animationSpec = tween(durationMillis = intervalStart, easing = LinearEasing) 32 | ) 33 | } 34 | 35 | Box(modifier = Modifier.alpha(alpha.value)) { 36 | content() 37 | } 38 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/IngradientItem.kt: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | 4 | /** 5 | * Created by abdulbasit on 25/06/2023. 6 | */ 7 | import androidx.compose.foundation.Image 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.border 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.shape.CircleShape 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.material.MaterialTheme 17 | import androidx.compose.material.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.draw.rotate 21 | import androidx.compose.ui.draw.shadow 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.graphics.ColorFilter 24 | import androidx.compose.ui.graphics.luminance 25 | import androidx.compose.ui.text.style.TextOverflow 26 | import androidx.compose.ui.unit.dp 27 | import model.Recipe 28 | import org.jetbrains.compose.resources.ExperimentalResourceApi 29 | import org.jetbrains.compose.resources.painterResource 30 | import recipeappkmp.shared.generated.resources.Res 31 | import recipeappkmp.shared.generated.resources.chef 32 | 33 | @OptIn(ExperimentalResourceApi::class) 34 | @Composable 35 | fun IngredientItem(recipe: Recipe, ingredient: String) { 36 | Box(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp)) { 37 | Box( 38 | modifier = Modifier 39 | .fillMaxWidth() 40 | .padding(top = 8.dp) 41 | .border( 42 | width = 2.dp, 43 | color = recipe.bgColor, 44 | shape = RoundedCornerShape(35.dp) 45 | ) 46 | ) { 47 | Text( 48 | text = ingredient, 49 | style = MaterialTheme.typography.body2, 50 | modifier = Modifier 51 | .fillMaxWidth() 52 | .padding(start = 55.dp, end = 8.dp, top = 16.dp, bottom = 16.dp), 53 | maxLines = 1, 54 | overflow = TextOverflow.Ellipsis 55 | ) 56 | } 57 | 58 | Box( 59 | modifier = Modifier.padding(start = 4.dp) 60 | ) { 61 | Box( 62 | modifier = Modifier 63 | .size(45.dp) 64 | .shadow(elevation = 10.dp, shape = CircleShape) 65 | .background( 66 | recipe.bgColor, 67 | CircleShape 68 | ), 69 | ) { 70 | Image( 71 | painter = painterResource(Res.drawable.chef), 72 | contentDescription = null, 73 | modifier = Modifier.padding(12.dp).rotate(-30f), 74 | colorFilter = ColorFilter.tint(if (recipe.bgColor.luminance() > 0.3) Color.Companion.Black else Color.White) 75 | ) 76 | 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/InstructionItem.kt: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | 4 | /** 5 | * Created by abdulbasit on 25/06/2023. 6 | */ 7 | 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.border 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.fillMaxHeight 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.foundation.shape.CircleShape 16 | import androidx.compose.foundation.shape.RoundedCornerShape 17 | import androidx.compose.material.MaterialTheme 18 | import androidx.compose.material.Text 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.draw.rotate 23 | import androidx.compose.ui.draw.shadow 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.text.font.FontWeight 26 | import androidx.compose.ui.text.style.LineHeightStyle 27 | import androidx.compose.ui.text.style.TextOverflow 28 | import androidx.compose.ui.unit.dp 29 | import androidx.compose.ui.unit.sp 30 | import model.Recipe 31 | 32 | @Composable 33 | fun InstructionItem(recipe: Recipe, index: Int) { 34 | Box(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp)) { 35 | Box( 36 | modifier = Modifier 37 | .fillMaxWidth() 38 | .padding(top = 8.dp) 39 | .border( 40 | width = 2.dp, 41 | color = recipe.bgColor, 42 | shape = RoundedCornerShape(35.dp) 43 | ) 44 | ) { 45 | Text( 46 | text = recipe.instructions[index], 47 | style = MaterialTheme.typography.body1.copy( 48 | letterSpacing = 1.2.sp, 49 | ), 50 | modifier = Modifier 51 | .fillMaxWidth().fillMaxHeight() 52 | .padding(start = 70.dp, end = 20.dp, top = 20.dp, bottom = 20.dp), 53 | ) 54 | } 55 | 56 | Box( 57 | modifier = Modifier 58 | ) { 59 | Box( 60 | modifier = Modifier 61 | .size(50.dp) 62 | .shadow(elevation = 10.dp, shape = CircleShape) 63 | .background( 64 | recipe.bgColor, 65 | CircleShape 66 | ), 67 | contentAlignment = Alignment.Center 68 | ) { 69 | Text( 70 | text = "${index + 1}", 71 | style = MaterialTheme.typography.h5.copy( 72 | lineHeightStyle = LineHeightStyle( 73 | alignment = LineHeightStyle.Alignment.Center, 74 | trim = LineHeightStyle.Trim.None 75 | ) 76 | ), 77 | color = Color.Black, 78 | fontWeight = FontWeight.W600, 79 | modifier = Modifier.padding(5.dp).rotate(-30f), 80 | maxLines = 1, 81 | overflow = TextOverflow.Ellipsis 82 | ) 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/RecipeDetails.kt: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | import RecipeDetailsLarge 4 | import RecipeDetailsSmall 5 | import androidx.compose.animation.AnimatedContentScope 6 | import androidx.compose.animation.ExperimentalSharedTransitionApi 7 | import androidx.compose.animation.SharedTransitionScope 8 | import androidx.compose.runtime.Composable 9 | import model.Recipe 10 | import sensor.SensorManager 11 | 12 | /** 13 | * Created by abdulbasit on 29/07/2023. 14 | */ 15 | 16 | @OptIn(ExperimentalSharedTransitionApi::class) 17 | @Composable 18 | fun RecipeDetails( 19 | recipe: Recipe, 20 | goBack: () -> Unit, 21 | sensorManager: SensorManager?, 22 | isLarge: Boolean, 23 | animatedVisibilityScope: AnimatedContentScope, 24 | sharedTransactionScope: SharedTransitionScope 25 | ) { 26 | if (isLarge) RecipeDetailsLarge( 27 | animatedVisibilityScope = animatedVisibilityScope, 28 | sharedTransactionScope = sharedTransactionScope, 29 | recipe = recipe, 30 | goBack = goBack, 31 | sensorManager = sensorManager 32 | ) 33 | else RecipeDetailsSmall( 34 | animatedVisibilityScope = animatedVisibilityScope, 35 | sharedTransactionScope = sharedTransactionScope, 36 | recipe = recipe, 37 | goBack = goBack, 38 | sensorManager = sensorManager 39 | ) 40 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/RecipeDetailsLarge.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.AnimatedContentScope 2 | import androidx.compose.animation.ExperimentalSharedTransitionApi 3 | import androidx.compose.animation.SharedTransitionScope 4 | import androidx.compose.animation.core.animateIntOffsetAsState 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.foundation.Image 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.clickable 9 | import androidx.compose.foundation.layout.Arrangement 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.PaddingValues 12 | import androidx.compose.foundation.layout.Row 13 | import androidx.compose.foundation.layout.Spacer 14 | import androidx.compose.foundation.layout.aspectRatio 15 | import androidx.compose.foundation.layout.fillMaxSize 16 | import androidx.compose.foundation.layout.offset 17 | import androidx.compose.foundation.layout.padding 18 | import androidx.compose.foundation.layout.size 19 | import androidx.compose.foundation.lazy.LazyColumn 20 | import androidx.compose.foundation.lazy.rememberLazyListState 21 | import androidx.compose.foundation.shape.RoundedCornerShape 22 | import androidx.compose.material.Card 23 | import androidx.compose.material.Icon 24 | import androidx.compose.material.MaterialTheme 25 | import androidx.compose.material.Text 26 | import androidx.compose.material.icons.Icons 27 | import androidx.compose.material.icons.automirrored.filled.ArrowBack 28 | import androidx.compose.material.icons.filled.ArrowBack 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.runtime.derivedStateOf 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.ui.Alignment 35 | import androidx.compose.ui.Modifier 36 | import androidx.compose.ui.draw.blur 37 | import androidx.compose.ui.draw.clip 38 | import androidx.compose.ui.draw.rotate 39 | import androidx.compose.ui.geometry.Offset 40 | import androidx.compose.ui.graphics.Color 41 | import androidx.compose.ui.graphics.ColorFilter 42 | import androidx.compose.ui.graphics.graphicsLayer 43 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection 44 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource 45 | import androidx.compose.ui.input.nestedscroll.nestedScroll 46 | import androidx.compose.ui.input.pointer.PointerEventType 47 | import androidx.compose.ui.input.pointer.pointerInput 48 | import androidx.compose.ui.layout.ContentScale 49 | import androidx.compose.ui.layout.onGloballyPositioned 50 | import androidx.compose.ui.unit.IntOffset 51 | import androidx.compose.ui.unit.IntSize 52 | import androidx.compose.ui.unit.Velocity 53 | import androidx.compose.ui.unit.dp 54 | import details.StepsAndDetails 55 | import model.Recipe 56 | import org.jetbrains.compose.resources.painterResource 57 | import sensor.Listener 58 | import sensor.SensorData 59 | import sensor.SensorManager 60 | import kotlin.math.PI 61 | 62 | 63 | @OptIn(ExperimentalSharedTransitionApi::class) 64 | @Composable 65 | fun RecipeDetailsLarge( 66 | recipe: Recipe, 67 | goBack: () -> Unit, 68 | sensorManager: SensorManager?, 69 | animatedVisibilityScope: AnimatedContentScope, 70 | sharedTransactionScope: SharedTransitionScope, 71 | ) { 72 | val imageRotation = remember { mutableStateOf(0) } 73 | val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) } 74 | val roll by derivedStateOf { (sensorDataLive.value.roll * 20).coerceIn(-4f, 4f) } 75 | val pitch by derivedStateOf { (sensorDataLive.value.pitch * 20).coerceIn(-4f, 4f) } 76 | 77 | val tweenDuration = 300 78 | 79 | sensorManager?.registerListener(object : Listener { 80 | override fun onUpdate(sensorData: SensorData) { 81 | sensorDataLive.value = sensorData 82 | } 83 | }) 84 | 85 | val backgroundShadowOffset = animateIntOffsetAsState( 86 | targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()), 87 | animationSpec = tween(tweenDuration) 88 | ) 89 | val backgroundImageOffset = animateIntOffsetAsState( 90 | targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration) 91 | ) 92 | 93 | val nestedScrollConnection = remember { 94 | object : NestedScrollConnection { 95 | override fun onPreScroll( 96 | available: Offset, source: NestedScrollSource 97 | ): Offset { 98 | imageRotation.value += (available.y * 0.5).toInt() 99 | return Offset.Zero 100 | } 101 | 102 | override fun onPostScroll( 103 | consumed: Offset, available: Offset, source: NestedScrollSource 104 | ): Offset { 105 | val delta = available.y 106 | imageRotation.value += ((delta * PI / 180) * 10).toInt() 107 | return super.onPostScroll(consumed, available, source) 108 | } 109 | 110 | override suspend fun onPreFling(available: Velocity): Velocity { 111 | imageRotation.value += available.y.toInt() 112 | return super.onPreFling(available) 113 | } 114 | } 115 | } 116 | 117 | with(sharedTransactionScope) { 118 | sharedTransactionScope.isTransitionActive 119 | Box( 120 | modifier = Modifier.fillMaxSize() 121 | .background(if (recipe.bgColor == sugar) yellow else sugar) 122 | ) { 123 | val size = mutableStateOf(IntSize(0, 0)) 124 | Row { 125 | Box(modifier = Modifier.fillMaxSize().onGloballyPositioned { 126 | size.value = it.size 127 | }.weight(1f).pointerInput(Unit) { 128 | awaitPointerEventScope { 129 | while (true) { 130 | val event = awaitPointerEvent() 131 | // on every relayout Compose will send synthetic Move event, 132 | // so we skip it to avoid event spam 133 | if (event.type == PointerEventType.Move) { 134 | val position = event.changes.first().position 135 | sensorDataLive.value = SensorData( 136 | roll = position.x - size.value.height / 4, 137 | pitch = (position.y - size.value.width / 4) 138 | ) 139 | } 140 | } 141 | 142 | } 143 | }) { 144 | Card( 145 | modifier = Modifier 146 | .clip(RoundedCornerShape(topEnd = 35.dp, bottomEnd = 35.dp)).then( 147 | Modifier.sharedElement( 148 | rememberSharedContentState( 149 | key = "item-container-${recipe.id}" 150 | ), 151 | animatedVisibilityScope, 152 | ) 153 | ), 154 | shape = RoundedCornerShape( 155 | topEnd = 35.dp, 156 | bottomEnd = 35.dp, 157 | // topStart = if (sharedTransactionScope.isTransitionActive) 35.dp else 0.dp, 158 | // bottomStart = if (sharedTransactionScope.isTransitionActive) 35.dp else 0.dp 159 | ), 160 | ) { 161 | // background image + its shadow 162 | Box(modifier = Modifier.fillMaxSize().background(recipe.bgColor)) { 163 | if (recipe.bgImageLarge != null) { 164 | val painter = painterResource(recipe.bgImageLarge) 165 | Image(painter = painter, 166 | contentDescription = null, 167 | contentScale = ContentScale.Crop, 168 | modifier = Modifier.offset { 169 | backgroundShadowOffset.value 170 | }.graphicsLayer { 171 | scaleX = 1.050f 172 | scaleY = 1.050f 173 | }.blur(radius = 8.dp), 174 | colorFilter = ColorFilter.tint( 175 | orangeDark.copy(alpha = 0.3f) 176 | ) 177 | ) 178 | 179 | Image( 180 | painter = painter, 181 | contentDescription = null, 182 | contentScale = ContentScale.Crop, 183 | modifier = Modifier.background( 184 | Color.Transparent, 185 | RoundedCornerShape( 186 | bottomEnd = 35.dp, bottomStart = 35.dp 187 | ), 188 | ).offset { 189 | backgroundImageOffset.value 190 | }.graphicsLayer { 191 | shadowElevation = 8f 192 | scaleX = 1.050f 193 | scaleY = 1.050f 194 | }, 195 | ) 196 | } 197 | 198 | // image shadows and image 199 | Box( 200 | modifier = Modifier.aspectRatio(1f).padding(32.dp) 201 | .align(Alignment.Center) 202 | ) { 203 | Box(modifier = Modifier.padding(32.dp)) { 204 | Image( 205 | painter = painterResource(recipe.image), 206 | contentDescription = null, 207 | modifier = Modifier.aspectRatio(1f).align(Alignment.Center) 208 | .padding(16.dp).rotate(imageRotation.value.toFloat()) 209 | .sharedBounds( 210 | rememberSharedContentState(key = "item-image-${recipe.id}"), 211 | animatedVisibilityScope = animatedVisibilityScope, 212 | ) 213 | // .shadow( 214 | // elevation = 16.dp, 215 | // shape = CircleShape, 216 | // clip = false, 217 | // ambientColor = orangeDark.copy(alpha = 0.5f), 218 | // spotColor = Color.Red, 219 | // ) 220 | ) 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | Box( 228 | modifier = Modifier.fillMaxSize() 229 | .background(if (recipe.bgColor == sugar) yellow else sugar) 230 | .pointerInput(Unit) { 231 | awaitPointerEventScope { 232 | while (true) { 233 | val event = awaitPointerEvent() 234 | if (event.type == PointerEventType.Scroll) { 235 | val position = event.changes.first().position 236 | // on every relayout Compose will send synthetic Move event, 237 | // so we skip it to avoid event spam 238 | imageRotation.value = 239 | (imageRotation.value + position.getDistance() 240 | .toInt() * 0.010).toInt() 241 | } 242 | } 243 | } 244 | }.weight(1f) 245 | ) { 246 | val listState = rememberLazyListState() 247 | 248 | Box( 249 | modifier = Modifier.fillMaxSize() 250 | ) { 251 | LazyColumn( 252 | contentPadding = PaddingValues(64.dp), 253 | userScrollEnabled = true, 254 | verticalArrangement = Arrangement.Absolute.spacedBy(16.dp), 255 | modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection), 256 | state = listState 257 | ) { 258 | StepsAndDetails( 259 | animatedVisibilityScope = animatedVisibilityScope, 260 | sharedTransactionScope = sharedTransactionScope, 261 | recipe = recipe 262 | ) 263 | } 264 | } 265 | } 266 | } 267 | 268 | BackButton(goBack) 269 | 270 | } 271 | } 272 | } 273 | 274 | @Composable 275 | fun BackButton(goBack: () -> Unit) { 276 | Box( 277 | modifier = Modifier.padding(start = 32.dp, top = 16.dp).clip( 278 | RoundedCornerShape(50) 279 | ).clickable { 280 | goBack() 281 | }.background( 282 | color = Color.Black, shape = RoundedCornerShape(50) 283 | ).padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 16.dp) 284 | ) { 285 | Row(verticalAlignment = Alignment.CenterVertically) { 286 | Icon( 287 | imageVector = Icons.AutoMirrored.Default.ArrowBack, 288 | contentDescription = null, 289 | tint = Color.White, 290 | modifier = Modifier.size(20.dp) 291 | ) 292 | Spacer(Modifier.padding(start = 8.dp)) 293 | Text( 294 | text = "Back to Recipes", 295 | style = MaterialTheme.typography.subtitle2, 296 | color = Color.White 297 | ) 298 | } 299 | } 300 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/RecipeDetailsSmall.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.animation.* 2 | import androidx.compose.animation.core.animateIntOffsetAsState 3 | import androidx.compose.animation.core.tween 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.Image 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.clickable 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.foundation.lazy.rememberLazyListState 11 | import androidx.compose.foundation.shape.CircleShape 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material.Icon 14 | import androidx.compose.material.icons.Icons 15 | import androidx.compose.material.icons.automirrored.filled.ArrowBack 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.derivedStateOf 18 | import androidx.compose.runtime.getValue 19 | import androidx.compose.runtime.mutableStateOf 20 | import androidx.compose.runtime.remember 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.draw.* 24 | import androidx.compose.ui.geometry.Offset 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.graphics.graphicsLayer 27 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection 28 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource 29 | import androidx.compose.ui.input.nestedscroll.nestedScroll 30 | import androidx.compose.ui.layout.ContentScale 31 | import androidx.compose.ui.unit.IntOffset 32 | import androidx.compose.ui.unit.Velocity 33 | import androidx.compose.ui.unit.dp 34 | import details.StepsAndDetails 35 | import model.Recipe 36 | import org.jetbrains.compose.resources.painterResource 37 | import sensor.Listener 38 | import sensor.SensorData 39 | import sensor.SensorManager 40 | import kotlin.math.PI 41 | 42 | 43 | @OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class) 44 | @Composable 45 | fun RecipeDetailsSmall( 46 | recipe: Recipe, 47 | goBack: () -> Unit, 48 | sensorManager: SensorManager?, 49 | animatedVisibilityScope: AnimatedContentScope, 50 | sharedTransactionScope: SharedTransitionScope 51 | ) { 52 | val imageRotation = remember { mutableStateOf(0) } 53 | val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) } 54 | val roll by derivedStateOf { (sensorDataLive.value.roll).coerceIn(-3f, 3f) } 55 | val pitch by derivedStateOf { (sensorDataLive.value.pitch).coerceIn(-2f, 2f) } 56 | 57 | val tweenDuration = 300 58 | 59 | sensorManager?.registerListener(object : Listener { 60 | override fun onUpdate(sensorData: SensorData) { 61 | sensorDataLive.value = sensorData 62 | } 63 | }) 64 | 65 | val backgroundShadowOffset = animateIntOffsetAsState( 66 | targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()), 67 | animationSpec = tween(tweenDuration) 68 | ) 69 | val backgroundImageOffset = animateIntOffsetAsState( 70 | targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration) 71 | ) 72 | 73 | val toolbarOffsetHeightPx = remember { mutableStateOf(340f) } 74 | val nestedScrollConnection = remember { 75 | object : NestedScrollConnection { 76 | override fun onPreScroll( 77 | available: Offset, source: NestedScrollSource 78 | ): Offset { 79 | val delta = available.y 80 | val newOffset = toolbarOffsetHeightPx.value + delta 81 | toolbarOffsetHeightPx.value = newOffset.coerceIn(0f, 340f) 82 | imageRotation.value += (available.y * 0.5).toInt() 83 | return Offset.Zero 84 | } 85 | 86 | override fun onPostScroll( 87 | consumed: Offset, available: Offset, source: NestedScrollSource 88 | ): Offset { 89 | val delta = available.y 90 | imageRotation.value += ((delta * PI / 180) * 10).toInt() 91 | return super.onPostScroll(consumed, available, source) 92 | } 93 | 94 | override suspend fun onPreFling(available: Velocity): Velocity { 95 | imageRotation.value += available.y.toInt() 96 | return super.onPreFling(available) 97 | } 98 | } 99 | } 100 | 101 | val candidateHeight = maxOf(toolbarOffsetHeightPx.value, 300f) 102 | val listState = rememberLazyListState() 103 | val (fraction, setFraction) = remember { mutableStateOf(1f) } 104 | 105 | with(sharedTransactionScope) { 106 | 107 | if (sharedTransactionScope.isTransitionActive.not()) { 108 | setFraction(0f) 109 | } 110 | 111 | Box( 112 | modifier = Modifier.fillMaxSize() 113 | .background(color = if (recipe.bgColor == sugar) yellow else sugar) 114 | ) { 115 | LazyColumn( 116 | modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection), 117 | state = listState 118 | ) { 119 | 120 | stickyHeader { 121 | Box( 122 | modifier = Modifier.shadow( 123 | elevation = if (fraction < 0.05) { 124 | ((1 - fraction) * 16).dp 125 | } else 0.dp, 126 | clip = false, 127 | ambientColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f), 128 | spotColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f) 129 | ).alpha(if (fraction < 0.2) 1f - fraction else 0f).fillMaxWidth() 130 | .background( 131 | recipe.bgColor, 132 | RoundedCornerShape( 133 | bottomEnd = 35.dp, bottomStart = 35.dp 134 | ), 135 | ).clip(RoundedCornerShape(bottomEnd = 35.dp, bottomStart = 35.dp)) 136 | .height(candidateHeight.dp).then( 137 | Modifier.sharedElement( 138 | rememberSharedContentState( 139 | key = "item-container-${recipe.id}" 140 | ), 141 | animatedVisibilityScope, 142 | ) 143 | ), 144 | ) { 145 | Box( 146 | modifier = Modifier.fillMaxSize() 147 | ) { 148 | 149 | //bg image and shadow 150 | recipe.bgImage?.let { 151 | Image(painter = painterResource(it), 152 | contentDescription = null, 153 | contentScale = ContentScale.FillWidth, 154 | modifier = Modifier.offset { 155 | backgroundShadowOffset.value 156 | }.graphicsLayer { 157 | scaleX = 1.050f 158 | scaleY = 1.050f 159 | }.blur(radius = 8.dp), 160 | colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( 161 | orangeDark.copy(alpha = 0.3f) 162 | ) 163 | ) 164 | Image(painter = painterResource(it), 165 | contentDescription = null, 166 | contentScale = ContentScale.FillWidth, 167 | modifier = Modifier.background( 168 | Color.Transparent, 169 | RoundedCornerShape( 170 | bottomEnd = 35.dp, bottomStart = 35.dp 171 | ), 172 | ).offset { 173 | backgroundImageOffset.value 174 | }.graphicsLayer { 175 | shadowElevation = 8f 176 | scaleX = 1.050f 177 | scaleY = 1.050f 178 | }, 179 | alpha = 1 - fraction 180 | ) 181 | } 182 | 183 | Box( 184 | modifier = Modifier.aspectRatio(1f).align(Alignment.Center) 185 | ) { 186 | Box { 187 | //image rounded shadow 188 | // Box(modifier = Modifier.offset { 189 | // IntOffset( 190 | // x = (roll * 2).dp.roundToPx(), 191 | // y = -(pitch * 2).dp.roundToPx() 192 | // ) 193 | // }) { 194 | // 195 | // Image( 196 | // painter = painterResource(recipe.image), 197 | // contentDescription = null, 198 | // modifier = Modifier.aspectRatio(1f) 199 | // .align(Alignment.Center).padding(16.dp).shadow( 200 | // elevation = 16.dp, 201 | // shape = CircleShape, 202 | // clip = false, 203 | // ambientColor = Color.Red, 204 | // spotColor = Color.Red, 205 | // ), 206 | // colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( 207 | // orangeDark.copy(alpha = 0.0f) 208 | // ) 209 | // ) 210 | // } 211 | 212 | Image( 213 | painter = painterResource(recipe.image), 214 | contentDescription = null, 215 | modifier = Modifier.aspectRatio(1f).align(Alignment.Center) 216 | .windowInsetsPadding(WindowInsets.systemBars) 217 | .padding(16.dp).rotate(imageRotation.value.toFloat()) 218 | .background( 219 | Color.Transparent, 220 | CircleShape, 221 | ).sharedBounds( 222 | rememberSharedContentState(key = "item-image-${recipe.id}"), 223 | animatedVisibilityScope = animatedVisibilityScope, 224 | enter = fadeIn(), 225 | exit = fadeOut(), 226 | resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() 227 | ) 228 | ) 229 | } 230 | } 231 | } 232 | } 233 | } 234 | 235 | StepsAndDetails( 236 | sharedTransactionScope = sharedTransactionScope, 237 | animatedVisibilityScope = animatedVisibilityScope, 238 | recipe = recipe 239 | ) 240 | } 241 | 242 | Box(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars).size(50.dp) 243 | .padding(10.dp).alpha( 244 | alpha = if (fraction <= 0) 1f else 0f, 245 | ).background( 246 | color = Color.Black, shape = RoundedCornerShape(50) 247 | ).shadow(elevation = 16.dp).padding(5.dp).clickable { 248 | goBack() 249 | }) { 250 | Icon( 251 | imageVector = Icons.AutoMirrored.Default.ArrowBack, 252 | contentDescription = null, 253 | tint = recipe.bgColor, 254 | modifier = Modifier.size(30.dp) 255 | ) 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/details/StepsAndDetails.kt: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | import AnimateInEffect 4 | import androidx.compose.animation.AnimatedContentScope 5 | import androidx.compose.animation.ExperimentalSharedTransitionApi 6 | import androidx.compose.animation.SharedTransitionScope 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.lazy.LazyListScope 9 | import androidx.compose.foundation.lazy.itemsIndexed 10 | import androidx.compose.material.MaterialTheme 11 | import androidx.compose.material.Text 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | import model.Recipe 16 | 17 | 18 | /** 19 | * Created by abdulbasit on 29/07/2023. 20 | */ 21 | 22 | @OptIn(ExperimentalSharedTransitionApi::class) 23 | internal fun LazyListScope.StepsAndDetails( 24 | animatedVisibilityScope: AnimatedContentScope, 25 | sharedTransactionScope: SharedTransitionScope, 26 | recipe: Recipe 27 | ) { 28 | with(sharedTransactionScope) { 29 | item { 30 | 31 | Text( 32 | text = recipe.title, 33 | style = MaterialTheme.typography.h5, 34 | fontWeight = FontWeight.W700, 35 | modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp).then( 36 | Modifier.sharedElement( 37 | rememberSharedContentState( 38 | key = "recipe-title-${recipe.id}" 39 | ), 40 | animatedVisibilityScope, 41 | ) 42 | ) 43 | ) 44 | 45 | Text( 46 | text = recipe.description, 47 | style = MaterialTheme.typography.body2, 48 | modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp).then( 49 | Modifier.sharedElement( 50 | rememberSharedContentState( 51 | key = "recipe-description-${recipe.id}" 52 | ), 53 | animatedVisibilityScope, 54 | ) 55 | ) 56 | ) 57 | 58 | AnimateInEffect( 59 | recipe = recipe, 60 | intervalStart = 0 / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(), 61 | content = { 62 | Text( 63 | text = "INGREDIENTS", 64 | style = MaterialTheme.typography.h6, 65 | fontWeight = FontWeight.W700, 66 | modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) 67 | ) 68 | }) 69 | } 70 | 71 | itemsIndexed(recipe.ingredients) { index, value -> 72 | AnimateInEffect( 73 | intervalStart = (index + 1) / (recipe.instructions.size + recipe.ingredients.size + 1).toFloat(), 74 | recipe = recipe, 75 | content = { 76 | IngredientItem(recipe, value) 77 | } 78 | ) 79 | } 80 | 81 | item { 82 | AnimateInEffect( 83 | recipe = recipe, 84 | intervalStart = (recipe.ingredients.size + 1) / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(), 85 | content = { 86 | Text( 87 | text = "STEPS", 88 | style = MaterialTheme.typography.h6, 89 | fontWeight = FontWeight.W700, 90 | modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp) 91 | ) 92 | } 93 | ) 94 | } 95 | 96 | itemsIndexed(recipe.instructions) { index, _ -> 97 | AnimateInEffect( 98 | recipe = recipe, 99 | intervalStart = (recipe.ingredients.size + index + 1) / (recipe.instructions.size + recipe.ingredients.size + 1).toFloat(), 100 | content = { 101 | InstructionItem(recipe, index) 102 | }) 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/model/Recipe.kt: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import org.jetbrains.compose.resources.DrawableResource 5 | import org.jetbrains.compose.resources.ExperimentalResourceApi 6 | 7 | 8 | /** 9 | * Created by abdulbasit on 18/06/2023. 10 | */ 11 | 12 | data class Recipe @OptIn(ExperimentalResourceApi::class) constructor( 13 | val id: Int, 14 | val title: String, 15 | val description: String, 16 | val ingredients: List, 17 | val instructions: List, 18 | val image: DrawableResource, 19 | val bgImage: DrawableResource? = null, 20 | val bgImageLarge: DrawableResource? = null, 21 | val bgColor: Color 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/recipeslist/ImageWrapper.kt: -------------------------------------------------------------------------------- 1 | package recipeslist 2 | 3 | /** 4 | * Created by abdulbasit on 21/06/2023. 5 | */ 6 | 7 | 8 | import androidx.compose.animation.core.Animatable 9 | import androidx.compose.animation.core.FastOutSlowInEasing 10 | import androidx.compose.animation.core.Spring.DampingRatioLowBouncy 11 | import androidx.compose.animation.core.spring 12 | import androidx.compose.animation.core.tween 13 | import androidx.compose.foundation.layout.Box 14 | import androidx.compose.foundation.layout.offset 15 | import androidx.compose.foundation.layout.wrapContentSize 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.LaunchedEffect 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.draw.rotate 21 | import androidx.compose.ui.draw.scale 22 | import androidx.compose.ui.graphics.graphicsLayer 23 | import androidx.compose.ui.unit.dp 24 | 25 | @Composable 26 | fun RecipeListItemImageWrapper( 27 | modifier: Modifier, 28 | child: @Composable () -> Unit, 29 | ) { 30 | val animationDuration = 700 31 | val scale = remember { Animatable(0.3f) } 32 | val rotation = remember { Animatable(20f) } 33 | val offset = remember { Animatable(0f) } 34 | 35 | LaunchedEffect(Unit) { 36 | scale.animateTo( 37 | targetValue = 1f, animationSpec = spring( 38 | dampingRatio = 0.6f, stiffness = 200f 39 | ) 40 | ) 41 | } 42 | 43 | LaunchedEffect(Unit) { 44 | rotation.animateTo(0f, animationSpec = tween(durationMillis = animationDuration)) 45 | } 46 | 47 | LaunchedEffect(Unit) { 48 | offset.animateTo( 49 | 60f, animationSpec = tween( 50 | durationMillis = animationDuration / 2, easing = FastOutSlowInEasing 51 | ) 52 | ) 53 | offset.animateTo( 54 | targetValue = 0f, animationSpec = spring( 55 | dampingRatio = DampingRatioLowBouncy, stiffness = 200f 56 | ) 57 | ) 58 | } 59 | 60 | Box(modifier = modifier.offset(x = offset.value.dp).graphicsLayer { 61 | this.rotationZ = rotation.value 62 | }) { 63 | Box( 64 | modifier = Modifier.wrapContentSize().scale(scale.value).rotate(rotation.value) 65 | ) { 66 | child() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/recipeslist/RecipeImage.kt: -------------------------------------------------------------------------------- 1 | package recipeslist 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.aspectRatio 7 | import androidx.compose.foundation.shape.CircleShape 8 | import androidx.compose.foundation.shape.RoundedCornerShape 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | import androidx.compose.ui.graphics.Color 13 | import org.jetbrains.compose.resources.DrawableResource 14 | import org.jetbrains.compose.resources.ExperimentalResourceApi 15 | import org.jetbrains.compose.resources.painterResource 16 | 17 | /** 18 | * Created by abdulbasit on 20/06/2023. 19 | */ 20 | 21 | @OptIn(ExperimentalResourceApi::class) 22 | @Composable 23 | fun RecipeImage(imageBitmap: DrawableResource, modifier: Modifier) { 24 | Box(modifier = Modifier) { 25 | Box( 26 | modifier = Modifier 27 | .clip(RoundedCornerShape(50)) 28 | .background( 29 | color = Color.Transparent, 30 | shape = RoundedCornerShape(50) 31 | ) 32 | ) 33 | Image( 34 | painter = painterResource(imageBitmap), 35 | contentDescription = null, 36 | modifier = modifier.background( 37 | color = Color(0xffCE5A01).copy(alpha = 0.2f), 38 | shape = CircleShape, 39 | ).aspectRatio(1f) 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/recipeslist/RecipeListItem.kt: -------------------------------------------------------------------------------- 1 | package recipeslist 2 | 3 | /** 4 | * Created by abdulbasit on 18/06/2023. 5 | */ 6 | 7 | import androidx.compose.animation.AnimatedVisibilityScope 8 | import androidx.compose.animation.ExperimentalSharedTransitionApi 9 | import androidx.compose.animation.SharedTransitionScope 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.clickable 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.Row 15 | import androidx.compose.foundation.layout.Spacer 16 | import androidx.compose.foundation.layout.aspectRatio 17 | import androidx.compose.foundation.layout.fillMaxHeight 18 | import androidx.compose.foundation.layout.fillMaxWidth 19 | import androidx.compose.foundation.layout.padding 20 | import androidx.compose.foundation.shape.RoundedCornerShape 21 | import androidx.compose.material.Card 22 | import androidx.compose.material.MaterialTheme 23 | import androidx.compose.material.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Alignment 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.draw.clip 28 | import androidx.compose.ui.draw.shadow 29 | import androidx.compose.ui.graphics.Color 30 | import androidx.compose.ui.text.style.TextOverflow 31 | import androidx.compose.ui.unit.dp 32 | import model.Recipe 33 | 34 | @OptIn(ExperimentalSharedTransitionApi::class) 35 | @Composable 36 | fun RecipeListItem( 37 | sharedTransitionScope: SharedTransitionScope, 38 | animatedVisibilityScope: AnimatedVisibilityScope, 39 | recipe: Recipe, 40 | onClick: (recipe: Recipe) -> Unit, 41 | ) { 42 | Box(modifier = Modifier) { 43 | Box(modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 16.dp) 44 | .fillMaxWidth().aspectRatio(1.5f) 45 | .shadow( 46 | elevation = 16.dp, 47 | shape = RoundedCornerShape(35.dp), 48 | clip = true, 49 | ambientColor = Color(0xffCE5A01), 50 | spotColor = Color(0xffCE5A01) 51 | ) 52 | .background(recipe.bgColor, RoundedCornerShape(35.dp)).fillMaxHeight().clickable { 53 | onClick(recipe) 54 | }) { 55 | with(sharedTransitionScope) { 56 | Card( 57 | backgroundColor = recipe.bgColor, 58 | shape = RoundedCornerShape(35.dp), 59 | modifier = Modifier.clip(RoundedCornerShape(35.dp)).sharedElement( 60 | state = rememberSharedContentState( 61 | key = "item-container-${recipe.id}" 62 | ), 63 | animatedVisibilityScope = animatedVisibilityScope, 64 | ) 65 | ) { 66 | Box( 67 | modifier = Modifier.fillMaxWidth().aspectRatio(1.5f) 68 | ) { 69 | Row( 70 | modifier = Modifier.fillMaxHeight().padding(16.dp).fillMaxWidth(0.55f), 71 | verticalAlignment = Alignment.Bottom 72 | ) { 73 | Column(modifier = Modifier.align(Alignment.Bottom)) { 74 | Text( 75 | text = recipe.title, 76 | style = MaterialTheme.typography.h4, 77 | modifier = Modifier.sharedElement( 78 | state = rememberSharedContentState( 79 | key = "item-title-${recipe.id}" 80 | ), 81 | animatedVisibilityScope = animatedVisibilityScope, 82 | ) 83 | ) 84 | 85 | Text( 86 | recipe.description, 87 | style = MaterialTheme.typography.subtitle1, 88 | maxLines = 3, 89 | overflow = TextOverflow.Ellipsis, 90 | modifier = Modifier.padding(top = 8.dp).sharedElement( 91 | state = rememberSharedContentState( 92 | key = "recipe-description-${recipe.id}" 93 | ), 94 | animatedVisibilityScope = animatedVisibilityScope, 95 | ) 96 | ) 97 | } 98 | Spacer(modifier = Modifier.weight(1f)) 99 | } 100 | } 101 | RecipeListItemImageWrapper(modifier = Modifier.align(Alignment.BottomEnd) 102 | .fillMaxWidth(0.45f).aspectRatio(1f), child = { 103 | RecipeImage( 104 | imageBitmap = recipe.image, modifier = Modifier.sharedElement( 105 | state = rememberSharedContentState(key = "item-image-${recipe.id}"), 106 | animatedVisibilityScope = animatedVisibilityScope, 107 | ) 108 | ) 109 | }) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/recipeslist/RecipeListItemWrapper.kt: -------------------------------------------------------------------------------- 1 | package recipeslist 2 | 3 | /** 4 | * Created by abdulbasit on 18/06/2023. 5 | */ 6 | 7 | import androidx.compose.animation.core.Animatable 8 | import androidx.compose.animation.core.CubicBezierEasing 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.LaunchedEffect 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.graphicsLayer 17 | 18 | const val perspectiveValue = 0.004 19 | const val rotateX = 9f 20 | 21 | 22 | @Composable 23 | fun RecipeListItemWrapper( 24 | child: @Composable () -> Unit, 25 | scrollDirection: Boolean 26 | ) { 27 | val scaleAnimatable = remember { Animatable(initialValue = 0.75f) } 28 | val rotateXAnimatable = 29 | remember { Animatable(initialValue = if (scrollDirection) rotateX else -rotateX) } 30 | 31 | // Observe changes to scrollDirection and update rotateXAnimatable accordingly 32 | LaunchedEffect(scrollDirection) { 33 | // Animate from 0 to either 60 or -60 34 | rotateXAnimatable.animateTo( 35 | if (scrollDirection) rotateX else -rotateX, 36 | animationSpec = tween( 37 | durationMillis = 100, 38 | easing = CubicBezierEasing(0f, 0.5f, 0.5f, 1f) 39 | ) 40 | ) 41 | // Animate from either 60 or -60 to 0 42 | rotateXAnimatable.animateTo( 43 | targetValue = 0f, 44 | animationSpec = tween( 45 | durationMillis = 500, 46 | easing = CubicBezierEasing(0f, 0.5f, 0.5f, 1f) 47 | ) 48 | ) 49 | } 50 | 51 | LaunchedEffect(Unit) { 52 | scaleAnimatable.animateTo( 53 | 1f, 54 | animationSpec = tween( 55 | durationMillis = 700, 56 | easing = CubicBezierEasing(0f, 0.5f, 0.5f, 1f) 57 | ) 58 | ) 59 | } 60 | 61 | Box( 62 | modifier = Modifier 63 | .fillMaxSize() 64 | .graphicsLayer { 65 | scaleX = scaleAnimatable.value 66 | scaleY = scaleAnimatable.value 67 | rotationX = rotateXAnimatable.value 68 | } 69 | ) { 70 | child() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/recipeslist/RecipesList.kt: -------------------------------------------------------------------------------- 1 | package recipeslist 2 | 3 | import androidx.compose.animation.AnimatedVisibilityScope 4 | import androidx.compose.animation.ExperimentalSharedTransitionApi 5 | import androidx.compose.animation.SharedTransitionScope 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.foundation.lazy.grid.GridCells 9 | import androidx.compose.foundation.lazy.grid.LazyGridState 10 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 11 | import androidx.compose.foundation.lazy.grid.rememberLazyGridState 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Modifier 14 | import model.Recipe 15 | import sugar 16 | 17 | 18 | /** 19 | * Created by abdulbasit on 25/06/2023. 20 | */ 21 | 22 | @OptIn(ExperimentalSharedTransitionApi::class) 23 | @Composable 24 | fun RecipesListScreen( 25 | items: List, 26 | onClick: (recipe: Recipe) -> Unit, 27 | isLarge: Boolean, 28 | sharedTransactionScope: SharedTransitionScope, 29 | animatedVisibilityScope: AnimatedVisibilityScope, 30 | ) { 31 | Box( 32 | modifier = Modifier.fillMaxSize().background(sugar) 33 | ) { 34 | val listState = rememberLazyGridState() 35 | LazyVerticalGrid( 36 | state = listState, columns = GridCells.Fixed(if (isLarge) 3 else 1) 37 | ) { 38 | if (isLarge.not()) 39 | item { 40 | Spacer(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) 41 | } 42 | items(items.size) { item -> 43 | val recipe = items[item] 44 | RecipeListItemWrapper( 45 | scrollDirection = listState.isScrollingUp(), 46 | child = { 47 | RecipeListItem( 48 | recipe = recipe, 49 | onClick = onClick, 50 | sharedTransitionScope = sharedTransactionScope, 51 | animatedVisibilityScope = animatedVisibilityScope, 52 | ) 53 | } 54 | ) 55 | } 56 | } 57 | } 58 | } 59 | 60 | @Composable 61 | private fun LazyGridState.isScrollingUp(): Boolean { 62 | var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) } 63 | var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) } 64 | return remember(this) { 65 | derivedStateOf { 66 | if (previousIndex != firstVisibleItemIndex) { 67 | previousIndex > firstVisibleItemIndex 68 | } else { 69 | previousScrollOffset >= firstVisibleItemScrollOffset 70 | }.also { 71 | previousIndex = firstVisibleItemIndex 72 | previousScrollOffset = firstVisibleItemScrollOffset 73 | } 74 | } 75 | }.value 76 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/sensor/SensorCallbackController.kt: -------------------------------------------------------------------------------- 1 | package sensor 2 | 3 | fun interface SensorManager { 4 | fun registerListener(listener: Listener) 5 | } 6 | 7 | 8 | interface Listener { 9 | fun onUpdate(sensorData: SensorData) 10 | } 11 | 12 | data class SensorData( 13 | val roll: Float, 14 | val pitch: Float 15 | ) -------------------------------------------------------------------------------- /shared/src/desktopMain/kotlin/main.desktop.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | 3 | 4 | @Composable 5 | fun MainView() { 6 | App(null, true) 7 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/main.ios.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.DisposableEffect 2 | import androidx.compose.runtime.rememberCoroutineScope 3 | import androidx.compose.ui.window.ComposeUIViewController 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.onEach 6 | import kotlinx.coroutines.flow.receiveAsFlow 7 | import kotlinx.coroutines.launch 8 | import sensor.SensorDataManager 9 | import sensor.SensorManagerImpl 10 | 11 | 12 | fun MainViewController() = 13 | ComposeUIViewController { 14 | val sensorManager = SensorManagerImpl() 15 | val scope = rememberCoroutineScope() 16 | 17 | DisposableEffect(Unit) { 18 | val dataManager = SensorDataManager() 19 | dataManager.startGyros() 20 | 21 | val job = scope.launch { 22 | dataManager.data 23 | .receiveAsFlow() 24 | .onEach { sensorManager.listener?.onUpdate(it) } 25 | .collect() 26 | } 27 | 28 | onDispose { 29 | dataManager.stopGyros() 30 | job.cancel() 31 | } 32 | } 33 | App(sensorManager) 34 | } 35 | -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/sensor/SensorDataManager.kt: -------------------------------------------------------------------------------- 1 | package sensor 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import platform.CoreMotion.CMMotionManager 5 | import platform.Foundation.NSOperationQueue 6 | 7 | 8 | /** 9 | * Created by abdulbasit on 23/07/2023. 10 | */ 11 | 12 | class SensorDataManager() { 13 | var motion = CMMotionManager() 14 | val data: Channel = Channel(Channel.UNLIMITED) 15 | 16 | fun startGyros() { 17 | if (motion.isGyroAvailable()) { 18 | motion.gyroUpdateInterval = 1.0 / 50.0 19 | motion.startGyroUpdates() 20 | motion.startDeviceMotionUpdatesToQueue(NSOperationQueue.currentQueue!!) { motion, error -> 21 | if (motion != null) { 22 | val attitude = motion.attitude 23 | data.trySend( 24 | SensorData( 25 | roll = attitude.roll.toFloat(), 26 | pitch = attitude.pitch.toFloat() 27 | ) 28 | ) 29 | } 30 | } 31 | } 32 | } 33 | 34 | fun stopGyros() { 35 | motion.stopGyroUpdates() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/sensor/SensorManagerImpl.kt: -------------------------------------------------------------------------------- 1 | package sensor 2 | 3 | class SensorManagerImpl : SensorManager { 4 | var listener: Listener? = null 5 | 6 | override fun registerListener(listener: Listener) { 7 | this.listener = listener 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /shared/src/wasmJsMain/kotlin/Resources.wasmJs.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by abdulbasit on 08/10/2023. 3 | */ 4 | /* 5 | @OptIn(ExperimentalResourceApi::class) 6 | actual suspend fun font( 7 | name: String, 8 | res: String, 9 | weight: FontWeight, 10 | style: FontStyle, 11 | context: PlatformContext 12 | ): Font { 13 | return androidx.compose.ui.text.platform.Font( 14 | identity = name, 15 | data = resource("font/$res.ttf").readBytes(), 16 | weight = weight, 17 | style = style 18 | ) 19 | }*/ 20 | -------------------------------------------------------------------------------- /tvApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | alias(libs.plugins.android.application) 4 | alias(libs.plugins.compose.compiler) 5 | alias(libs.plugins.compose) 6 | } 7 | 8 | kotlin { 9 | androidTarget() 10 | sourceSets { 11 | val androidMain by getting { 12 | dependencies { 13 | implementation(project(":shared")) 14 | } 15 | } 16 | } 17 | } 18 | 19 | android { 20 | compileSdk = (findProperty("android.compileSdk") as String).toInt() 21 | namespace = "com.myapplication" 22 | 23 | sourceSets["main"].manifest.srcFile("src/main/AndroidManifest.xml") 24 | 25 | defaultConfig { 26 | minSdk = (findProperty("android.minSdk") as String).toInt() 27 | targetSdk = (findProperty("android.targetSdk") as String).toInt() 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | } 35 | kotlin { 36 | jvmToolchain(11) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tvApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | 12 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tvApp/src/main/java/com/recipeapp/tv/MainActivityTV.kt: -------------------------------------------------------------------------------- 1 | package com.recipeapp.tv 2 | 3 | import MainView 4 | import android.os.Bundle 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | 8 | class MainActivityTV : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContent { 12 | MainView(isLargeScreen = true) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tvApp/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/tvApp/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tvApp/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/tvApp/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tvApp/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/tvApp/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tvApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/tvApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tvApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/tvApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tvApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Recipe App 3 | -------------------------------------------------------------------------------- /webApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 3 | 4 | plugins { 5 | alias(libs.plugins.kotlin.multiplatform) 6 | alias(libs.plugins.compose) 7 | alias(libs.plugins.compose.compiler) 8 | } 9 | 10 | kotlin { 11 | @OptIn(ExperimentalWasmDsl::class) 12 | wasmJs { 13 | moduleName = "composeApp" 14 | browser { 15 | val projectDirPath = project.projectDir.path 16 | commonWebpackConfig { 17 | outputFileName = "webApp.js" 18 | devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { 19 | static = (static ?: mutableListOf()).apply { 20 | // Serve sources to debug inside browser 21 | add(projectDirPath) 22 | } 23 | } 24 | } 25 | } 26 | binaries.executable() 27 | } 28 | 29 | sourceSets { 30 | 31 | commonMain.dependencies { 32 | implementation(compose.runtime) 33 | implementation(compose.foundation) 34 | implementation(compose.material) 35 | implementation(compose.ui) 36 | implementation(compose.components.resources) 37 | implementation(compose.components.uiToolingPreview) 38 | implementation(project(":shared")) 39 | } 40 | } 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /webApp/src/jsMain/resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/webApp/src/jsMain/resources/images/logo.png -------------------------------------------------------------------------------- /webApp/src/wasmJsMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.ComposeViewport 3 | import kotlinx.browser.document 4 | 5 | @OptIn(ExperimentalComposeUiApi::class) 6 | fun main() { 7 | ComposeViewport(document.body!!) { 8 | App(null, true) 9 | } 10 | } -------------------------------------------------------------------------------- /webApp/src/wasmJsMain/resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SEAbdulbasit/recipe-app/eb7ebbbd394b23839cd150e3a66d895c9f8cce4d/webApp/src/wasmJsMain/resources/images/logo.png -------------------------------------------------------------------------------- /webApp/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Recipe App KMP 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webApp/src/wasmJsMain/resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Recipe App", 3 | "icons": [ 4 | { 5 | "src": "images/logo.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | } 9 | ], 10 | "start_url": "/", 11 | "display": "standalone", 12 | "background_color": "white" 13 | } -------------------------------------------------------------------------------- /webApp/src/wasmJsMain/resources/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------