├── .github ├── CODEOWNERS └── workflows │ └── Fruitties.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Fruitties ├── .editorconfig ├── .gitignore ├── androidApp │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── fruitties │ │ │ └── android │ │ │ ├── MainActivity.kt │ │ │ ├── MyApplicationTheme.kt │ │ │ ├── di │ │ │ └── App.kt │ │ │ └── ui │ │ │ └── ListScreen.kt │ │ └── res │ │ ├── values │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── data_extraction_rules.xml │ │ └── full_backup_content.xml ├── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp │ ├── Podfile │ ├── iosApp.xcodeproj │ │ └── project.pbxproj │ └── iosApp │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── CartView.swift │ │ ├── ContentView.swift │ │ ├── Info.plist │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── iOSApp.swift ├── settings.gradle.kts └── shared │ ├── .gitignore │ ├── build.gradle.kts │ ├── schemas │ └── com.example.fruitties.database.AppDatabase │ │ └── 1.json │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── example │ │ └── fruitties │ │ └── di │ │ └── Factory.android.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── example │ │ └── fruitties │ │ ├── DataRepository.kt │ │ ├── database │ │ ├── AppDatabase.kt │ │ ├── CartDataStore.kt │ │ └── FruittieDao.kt │ │ ├── di │ │ ├── AppContainer.kt │ │ └── Factory.kt │ │ ├── model │ │ ├── Fruittie.kt │ │ └── FruittiesResponse.kt │ │ ├── network │ │ └── FruittieApi.kt │ │ └── viewmodel │ │ └── MainViewModel.kt │ └── iosMain │ └── kotlin │ └── com │ └── example │ └── fruitties │ └── di │ ├── Factory.native.kt │ └── viewmodel │ └── IOSViewModelOwner.kt ├── LICENSE ├── README.md └── renovate.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @android/kmp-devrel 2 | -------------------------------------------------------------------------------- /.github/workflows/Fruitties.yaml: -------------------------------------------------------------------------------- 1 | name: Build Fruitties sample 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - feature/* 9 | pull_request: 10 | 11 | concurrency: 12 | group: build-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build_android: 17 | name: Build Android app 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Validate Gradle Wrapper 24 | uses: gradle/wrapper-validation-action@v3 25 | 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'zulu' 30 | java-version: 17 31 | 32 | - name: Build app 33 | working-directory: ./Fruitties 34 | run: ./gradlew assemble --stacktrace 35 | 36 | build_ios: 37 | name: Build iOS app 38 | runs-on: macos-latest 39 | steps: 40 | - uses: maxim-lobanov/setup-xcode@v1 41 | with: 42 | xcode-version: latest-stable 43 | 44 | - name: Checkout 45 | uses: actions/checkout@v4 46 | 47 | - name: Validate Gradle Wrapper 48 | uses: gradle/wrapper-validation-action@v3 49 | 50 | - name: Set up JDK 17 51 | uses: actions/setup-java@v4 52 | with: 53 | distribution: 'zulu' 54 | java-version: 17 55 | 56 | - name: Build app 57 | working-directory: ./Fruitties 58 | run: xcodebuild -project iosApp/iosApp.xcodeproj -configuration Debug -scheme iosApp -sdk iphonesimulator 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Android Studio 2 | /*/build/ 3 | /*/local.properties 4 | /*/out 5 | /*/*/build 6 | /*/*/production 7 | captures/ 8 | .navigation/ 9 | *.ipr 10 | *~ 11 | *.swp 12 | *.iml 13 | .idea/caches/ 14 | .idea/libraries/ 15 | .idea/shelf/ 16 | .idea/workspace.xml 17 | .idea/tasks.xml 18 | .idea/.name 19 | .idea/compiler.xml 20 | .idea/copyright/profiles_settings.xml 21 | .idea/encodings.xml 22 | .idea/misc.xml 23 | .idea/modules.xml 24 | .idea/scopes/scope_settings.xml 25 | .idea/dictionaries 26 | .idea/vcs.xml 27 | .idea/jsLibraryMappings.xml 28 | .idea/datasources.xml 29 | .idea/dataSources.ids 30 | .idea/sqlDataSources.xml 31 | .idea/dynamic.xml 32 | .idea/uiDesigner.xml 33 | .idea/assetWizardSettings.xml 34 | .idea/gradle.xml 35 | .idea/jarRepositories.xml 36 | .idea/navEditor.xml 37 | !/gradle/wrapper/gradle-wrapper.jar 38 | 39 | ### Xcode ### 40 | *.xcodeproj/* 41 | !*.xcodeproj/project.pbxproj 42 | !*.xcodeproj/xcshareddata/ 43 | !*.xcworkspace/contents.xcworkspacedata/ 44 | /*.gcno 45 | **/xcshareddata/WorkspaceSettings.xcsettings 46 | *.xcuserstate 47 | *.xcscheme 48 | *.xcworkspace 49 | xcuserdata/ 50 | 51 | # CocoaPods 52 | Pods/ 53 | 54 | ## iOS App packaging 55 | *.ipa 56 | *.dSYM.zip 57 | *.dSYM 58 | 59 | # Android App packaging 60 | *.apk 61 | *.ap_ 62 | *.aab 63 | 64 | # macOs 65 | .DS_Store 66 | 67 | # Generated files 68 | bin/ 69 | gen/ 70 | *.class 71 | *.dex 72 | 73 | # Gradle files 74 | .gradle 75 | .gradle/ 76 | build/ 77 | 78 | # Local configuration file 79 | local.properties 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code Reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /Fruitties/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{kt,kts}] 2 | # Kotlin style typically requires functions to start with a lowercase letter. 3 | # Composable functions should start with a capital letter. 4 | ktlint_function_naming_ignore_when_annotated_with = Composable 5 | 6 | # ktlint always puts a new line after a multi-line assignment, like this: 7 | # val colors = 8 | # if (darkTheme) { 9 | # darkColorScheme( 10 | # primary = Color(0xFFBB86FC), 11 | # secondary = Color(0xFF03DAC5), 12 | # tertiary = Color(0xFF3700B3), 13 | # ) 14 | # } else { 15 | # lightColorScheme( 16 | # primary = Color(0xFF6200EE), 17 | # secondary = Color(0xFF03DAC5), 18 | # tertiary = Color(0xFF3700B3), 19 | # ) 20 | # } 21 | # But we actually prefer to keep some multi-line assignments on the same line, like this: 22 | # val colors = if (darkTheme) { 23 | # darkColorScheme( 24 | # primary = Color(0xFFBB86FC), 25 | # secondary = Color(0xFF03DAC5), 26 | # tertiary = Color(0xFF3700B3), 27 | # ) 28 | # } else { 29 | # lightColorScheme( 30 | # primary = Color(0xFF6200EE), 31 | # secondary = Color(0xFF03DAC5), 32 | # tertiary = Color(0xFF3700B3), 33 | # ) 34 | # } 35 | ktlint_standard_multiline-expression-wrapping = disabled 36 | -------------------------------------------------------------------------------- /Fruitties/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | .DS_Store 5 | build 6 | captures 7 | .externalNativeBuild 8 | .cxx 9 | local.properties 10 | xcuserdata 11 | .kotlin 12 | -------------------------------------------------------------------------------- /Fruitties/androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | plugins { 17 | alias(libs.plugins.androidApplication) 18 | alias(libs.plugins.kotlinAndroid) 19 | alias(libs.plugins.compose.compiler) 20 | } 21 | 22 | android { 23 | namespace = "com.example.fruitties.android" 24 | compileSdk = 35 25 | defaultConfig { 26 | applicationId = "com.example.fruitties.android" 27 | minSdk = 26 28 | targetSdk = 35 29 | versionCode = 1 30 | versionName = "1.0" 31 | } 32 | buildFeatures { 33 | compose = true 34 | } 35 | packaging { 36 | resources { 37 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 38 | } 39 | } 40 | buildTypes { 41 | getByName("release") { 42 | // Enables code shrinking, obfuscation, and optimization for only 43 | // your project's release build type. Make sure to use a build 44 | // variant with `isDebuggable=false`. 45 | isMinifyEnabled = true 46 | 47 | // Enables resource shrinking, which is performed by the 48 | // Android Gradle plugin. 49 | isShrinkResources = true 50 | 51 | proguardFiles( 52 | // Includes the default ProGuard rules files that are packaged with 53 | // the Android Gradle plugin. To learn more, go to the section about 54 | // R8 configuration files. 55 | getDefaultProguardFile("proguard-android-optimize.txt"), 56 | // Includes a local, custom Proguard rules file 57 | "proguard-rules.pro", 58 | ) 59 | } 60 | } 61 | compileOptions { 62 | sourceCompatibility = JavaVersion.VERSION_1_8 63 | targetCompatibility = JavaVersion.VERSION_1_8 64 | } 65 | kotlinOptions { 66 | jvmTarget = "1.8" 67 | } 68 | } 69 | 70 | dependencies { 71 | implementation(projects.shared) 72 | 73 | val composeBom = platform(libs.compose.bom) 74 | implementation(composeBom) 75 | implementation(libs.compose.ui) 76 | implementation(libs.compose.ui.tooling.preview) 77 | implementation(libs.compose.material3) 78 | implementation(libs.androidx.activity.compose) 79 | implementation(libs.androidx.paging.compose.android) 80 | implementation(libs.androidx.lifecycle.viewmodel.compose) 81 | debugImplementation(libs.compose.ui.tooling) 82 | } 83 | -------------------------------------------------------------------------------- /Fruitties/androidApp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Please add these rules to your existing keep rules in order to suppress warnings. 2 | # This is generated automatically by the Android Gradle plugin. 3 | -dontwarn org.slf4j.impl.StaticLoggerBinder -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/java/com/example/fruitties/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.fruitties.android 18 | 19 | import android.os.Bundle 20 | import androidx.activity.ComponentActivity 21 | import androidx.activity.compose.setContent 22 | import androidx.activity.enableEdgeToEdge 23 | import androidx.compose.foundation.layout.fillMaxSize 24 | import androidx.compose.material3.MaterialTheme 25 | import androidx.compose.material3.Surface 26 | import androidx.compose.ui.Modifier 27 | import com.example.fruitties.android.ui.ListScreen 28 | 29 | class MainActivity : ComponentActivity() { 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | enableEdgeToEdge() 33 | setContent { 34 | MyApplicationTheme { 35 | Surface( 36 | modifier = Modifier.fillMaxSize(), 37 | color = MaterialTheme.colorScheme.background, 38 | ) { 39 | ListScreen() 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/java/com/example/fruitties/android/MyApplicationTheme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.fruitties.android 17 | 18 | import androidx.compose.foundation.isSystemInDarkTheme 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material3.MaterialTheme 21 | import androidx.compose.material3.Shapes 22 | import androidx.compose.material3.Typography 23 | import androidx.compose.material3.darkColorScheme 24 | import androidx.compose.material3.lightColorScheme 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.graphics.Color 27 | import androidx.compose.ui.text.TextStyle 28 | import androidx.compose.ui.text.font.FontFamily 29 | import androidx.compose.ui.text.font.FontWeight 30 | import androidx.compose.ui.unit.dp 31 | import androidx.compose.ui.unit.sp 32 | 33 | @Composable 34 | fun MyApplicationTheme( 35 | darkTheme: Boolean = isSystemInDarkTheme(), 36 | content: @Composable () -> Unit, 37 | ) { 38 | val colors = if (darkTheme) { 39 | darkColorScheme( 40 | primary = Color(0xFFBB86FC), 41 | secondary = Color(0xFF03DAC5), 42 | tertiary = Color(0xFF3700B3), 43 | ) 44 | } else { 45 | lightColorScheme( 46 | primary = Color(0xFF6200EE), 47 | secondary = Color(0xFF03DAC5), 48 | tertiary = Color(0xFF3700B3), 49 | ) 50 | } 51 | val typography = Typography( 52 | bodyMedium = TextStyle( 53 | fontFamily = FontFamily.Default, 54 | fontWeight = FontWeight.Normal, 55 | fontSize = 16.sp, 56 | ), 57 | ) 58 | val shapes = Shapes( 59 | small = RoundedCornerShape(4.dp), 60 | medium = RoundedCornerShape(4.dp), 61 | large = RoundedCornerShape(0.dp), 62 | ) 63 | 64 | MaterialTheme( 65 | colorScheme = colors, 66 | typography = typography, 67 | shapes = shapes, 68 | content = content, 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/java/com/example/fruitties/android/di/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.fruitties.android.di 18 | 19 | import android.app.Application 20 | import com.example.fruitties.di.AppContainer 21 | import com.example.fruitties.di.Factory 22 | 23 | class App : Application() { 24 | /** AppContainer instance used by the rest of classes to obtain dependencies */ 25 | lateinit var container: AppContainer 26 | 27 | override fun onCreate() { 28 | super.onCreate() 29 | container = AppContainer(Factory(this)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/java/com/example/fruitties/android/ui/ListScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.fruitties.android.ui 18 | 19 | import androidx.compose.animation.AnimatedVisibility 20 | import androidx.compose.animation.core.tween 21 | import androidx.compose.animation.fadeIn 22 | import androidx.compose.animation.fadeOut 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.Column 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.Spacer 27 | import androidx.compose.foundation.layout.WindowInsets 28 | import androidx.compose.foundation.layout.WindowInsetsSides 29 | import androidx.compose.foundation.layout.fillMaxWidth 30 | import androidx.compose.foundation.layout.heightIn 31 | import androidx.compose.foundation.layout.only 32 | import androidx.compose.foundation.layout.padding 33 | import androidx.compose.foundation.layout.safeDrawing 34 | import androidx.compose.foundation.layout.systemBars 35 | import androidx.compose.foundation.layout.windowInsetsBottomHeight 36 | import androidx.compose.foundation.lazy.LazyColumn 37 | import androidx.compose.foundation.lazy.items 38 | import androidx.compose.foundation.shape.RoundedCornerShape 39 | import androidx.compose.material3.Button 40 | import androidx.compose.material3.Card 41 | import androidx.compose.material3.CardDefaults 42 | import androidx.compose.material3.CenterAlignedTopAppBar 43 | import androidx.compose.material3.ExperimentalMaterial3Api 44 | import androidx.compose.material3.MaterialTheme 45 | import androidx.compose.material3.Scaffold 46 | import androidx.compose.material3.Text 47 | import androidx.compose.material3.TopAppBarColors 48 | import androidx.compose.runtime.Composable 49 | import androidx.compose.runtime.collectAsState 50 | import androidx.compose.runtime.getValue 51 | import androidx.compose.runtime.mutableStateOf 52 | import androidx.compose.runtime.remember 53 | import androidx.compose.runtime.setValue 54 | import androidx.compose.ui.Alignment 55 | import androidx.compose.ui.Modifier 56 | import androidx.compose.ui.draw.clip 57 | import androidx.compose.ui.platform.LocalContext 58 | import androidx.compose.ui.res.stringResource 59 | import androidx.compose.ui.text.style.TextOverflow 60 | import androidx.compose.ui.tooling.preview.Preview 61 | import androidx.compose.ui.unit.dp 62 | import androidx.lifecycle.viewmodel.compose.viewModel 63 | import com.example.fruitties.android.R 64 | import com.example.fruitties.android.di.App 65 | import com.example.fruitties.model.CartItemDetails 66 | import com.example.fruitties.model.Fruittie 67 | import com.example.fruitties.viewmodel.MainViewModel 68 | 69 | @OptIn(ExperimentalMaterial3Api::class) 70 | @Composable 71 | fun ListScreen() { 72 | // Instantiate a ViewModel with a dependency on the AppContainer. 73 | // To make ViewModel compatible with KMP, the ViewModel factory must 74 | // create an instance without referencing the Android Application. 75 | // Here we put the KMP-compatible AppContainer into the extras 76 | // so it can be passed to the ViewModel factory. 77 | val app = LocalContext.current.applicationContext as App 78 | val extras = remember(app) { 79 | val container = app.container 80 | MainViewModel.newCreationExtras(container) 81 | } 82 | val viewModel: MainViewModel = viewModel( 83 | factory = MainViewModel.Factory, 84 | extras = extras, 85 | ) 86 | 87 | val uiState by viewModel.homeUiState.collectAsState() 88 | val cartState by viewModel.cartUiState.collectAsState() 89 | 90 | Scaffold( 91 | topBar = { 92 | CenterAlignedTopAppBar( 93 | title = { 94 | Text(text = stringResource(R.string.frutties)) 95 | }, 96 | colors = TopAppBarColors( 97 | containerColor = MaterialTheme.colorScheme.primary, 98 | scrolledContainerColor = MaterialTheme.colorScheme.primary, 99 | navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, 100 | titleContentColor = MaterialTheme.colorScheme.onPrimary, 101 | actionIconContentColor = MaterialTheme.colorScheme.onPrimary, 102 | ), 103 | ) 104 | }, 105 | contentWindowInsets = WindowInsets.safeDrawing.only( 106 | // Do not include Bottom so scrolled content is drawn below system bars. 107 | // Include Horizontal because some devices have camera cutouts on the side. 108 | WindowInsetsSides.Top + WindowInsetsSides.Horizontal, 109 | ), 110 | ) { paddingValues -> 111 | Column( 112 | modifier = Modifier 113 | // Support edge-to-edge (required on Android 15) 114 | // https://developer.android.com/develop/ui/compose/layouts/insets#inset-size 115 | .padding(paddingValues), 116 | ) { 117 | var expanded by remember { mutableStateOf(false) } 118 | Row(modifier = Modifier.padding(16.dp)) { 119 | val total = cartState.cartDetails.sumOf { item -> item.count } 120 | Text( 121 | text = "Cart has $total items", 122 | modifier = Modifier.weight(1f).padding(12.dp), 123 | ) 124 | Button(onClick = { expanded = !expanded }) { 125 | Text(text = if (expanded) "collapse" else "expand") 126 | } 127 | } 128 | AnimatedVisibility( 129 | visible = expanded, 130 | enter = fadeIn(animationSpec = tween(1000)), 131 | exit = fadeOut(animationSpec = tween(1000)), 132 | ) { 133 | CartDetailsView(cartState.cartDetails) 134 | } 135 | 136 | LazyColumn { 137 | items(items = uiState.fruitties, key = { it.id }) { item -> 138 | FruittieItem( 139 | item = item, 140 | onAddToCart = viewModel::addItemToCart, 141 | ) 142 | } 143 | // Support edge-to-edge (required on Android 15) 144 | // https://developer.android.com/develop/ui/compose/layouts/insets#inset-size 145 | item { 146 | Spacer( 147 | Modifier.windowInsetsBottomHeight( 148 | WindowInsets.systemBars, 149 | ), 150 | ) 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | @Composable 158 | fun FruittieItem( 159 | item: Fruittie, 160 | onAddToCart: (fruittie: Fruittie) -> Unit, 161 | modifier: Modifier = Modifier, 162 | ) { 163 | Card( 164 | modifier = modifier 165 | .padding(horizontal = 16.dp, vertical = 8.dp) 166 | .clip(RoundedCornerShape(8.dp)), 167 | shape = RoundedCornerShape(8.dp), 168 | colors = CardDefaults.cardColors( 169 | containerColor = MaterialTheme.colorScheme.surface, 170 | ), 171 | elevation = CardDefaults.cardElevation( 172 | defaultElevation = 8.dp, 173 | ), 174 | ) { 175 | Row( 176 | modifier = Modifier.fillMaxWidth(), 177 | verticalAlignment = Alignment.CenterVertically, 178 | ) { 179 | Column( 180 | modifier = Modifier 181 | .heightIn(min = 96.dp), 182 | verticalArrangement = Arrangement.Center, 183 | ) { 184 | Text( 185 | text = item.name, 186 | color = MaterialTheme.colorScheme.onBackground, 187 | style = MaterialTheme.typography.titleMedium, 188 | maxLines = 1, 189 | overflow = TextOverflow.Ellipsis, 190 | modifier = Modifier 191 | .padding(horizontal = 16.dp) 192 | .padding(top = 8.dp), 193 | ) 194 | Text( 195 | text = item.fullName, 196 | modifier = Modifier 197 | .padding(horizontal = 16.dp) 198 | .padding(bottom = 8.dp), 199 | color = MaterialTheme.colorScheme.onSurface, 200 | maxLines = 2, 201 | overflow = TextOverflow.Ellipsis, 202 | ) 203 | } 204 | Spacer(modifier = Modifier.weight(1f)) 205 | Row( 206 | modifier = Modifier 207 | .padding(horizontal = 16.dp, vertical = 8.dp), 208 | verticalAlignment = Alignment.CenterVertically, 209 | ) { 210 | Button(onClick = { onAddToCart(item) }) { 211 | Text(stringResource(R.string.add)) 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | @Composable 219 | fun CartDetailsView( 220 | cart: List, 221 | modifier: Modifier = Modifier, 222 | ) { 223 | Column( 224 | modifier.padding(horizontal = 32.dp), 225 | ) { 226 | cart.forEach { item -> 227 | Text(text = "${item.fruittie.name}: ${item.count}") 228 | } 229 | } 230 | } 231 | 232 | @Preview 233 | @Composable 234 | fun ItemPreview() { 235 | FruittieItem( 236 | Fruittie(name = "Fruit", fullName = "Fruitus Mangorus", calories = "240"), 237 | onAddToCart = {}, 238 | ) 239 | } 240 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | "Frutties" 19 | Add 20 | -------------------------------------------------------------------------------- /Fruitties/androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 |