commit 51e4cc11ff005575478e3d5f8ed8db4777f4959c Author: John Shea Date: Tue May 9 17:22:38 2023 -0400 Initial starter_m3 branch commit diff --git a/.github/ISSUE_TEMPLATE/android-basics-cupcake-app.md b/.github/ISSUE_TEMPLATE/android-basics-cupcake-app.md new file mode 100644 index 0000000..778f841 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/android-basics-cupcake-app.md @@ -0,0 +1,32 @@ +--- +name: Navigate between screens with Compose issue template +about: Issue template for Navigate between screens with Compose +title: 'Navigate between screens with Compose: Android Basics with Compose' +labels: '' +assignees: '' + +--- + +**URL of codelab** + + +**In which task and step of the codelab can this issue be found?** + + +**Describe the problem** + + + + +**Steps to reproduce?** +1. Go to... +2. Click on... +3. See error... + +**Versions** +_Android Studio version:_ +_API version of the emulator:_ + + +**Additional information** +_Include screenshots if they would be useful in clarifying the problem._ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a2358d --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# built application files +*.apk +*.ap_ + +# Mac files +.DS_Store + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Ignore gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ +proguard-project.txt + +# Eclipse files +.project +.classpath +.settings/ + +# Android Studio/IDEA +*.iml +.idea \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b16a466 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement (CLA). You (or your employer) retain the copyright to your +contribution; this simply gives us permission to use and redistribute your +contributions as part of the project. Head over to + to see your current agreements on file or +to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e6e77b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae1ae2e --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +Cupcake app +================================= + +This app contains an order flow for cupcakes with options for quantity, flavor, and pickup date. +The order details get displayed on an order summary screen and can be shared to another app to +send the order. + +TODO + + +Pre-requisites +-------------- +* Experience with Kotlin syntax. +* How to create and run a project in Android Studio. +* How to create composable functions +* TODO + + +Getting Started +--------------- +1. Install Android Studio, if you don't already have it. +2. Download the sample. +3. Import the sample into Android Studio. +4. Build and run the sample. diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..75e5ace --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.example.cupcake' + compileSdk 33 + + defaultConfig { + applicationId "com.example.cupcake" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + freeCompilerArgs += "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.4.7' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation platform('androidx.compose:compose-bom:2023.05.00') + implementation 'androidx.activity:activity-compose:1.7.1' + implementation 'androidx.compose.material3:material3' + implementation 'androidx.compose.runtime:runtime' + implementation 'androidx.compose.runtime:runtime-livedata' + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.core:core-ktx:1.10.0' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" + implementation 'androidx.navigation:navigation-compose:2.5.3' + + debugImplementation 'androidx.compose.ui:ui-test-manifest' + debugImplementation 'androidx.compose.ui:ui-tooling' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..42c95fa --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/example/cupcake/CupcakeScreen.kt b/app/src/main/java/com/example/cupcake/CupcakeScreen.kt new file mode 100644 index 0000000..53e047f --- /dev/null +++ b/app/src/main/java/com/example/cupcake/CupcakeScreen.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.example.cupcake.ui.OrderViewModel + +/** + * Composable that displays the topBar and displays back button if back navigation is possible. + */ +@Composable +fun CupcakeAppBar( + canNavigateBack: Boolean, + navigateUp: () -> Unit, + modifier: Modifier = Modifier +) { + TopAppBar( + title = { Text(stringResource(id = R.string.app_name)) }, + colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + modifier = modifier, + navigationIcon = { + if (canNavigateBack) { + IconButton(onClick = navigateUp) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource(R.string.back_button) + ) + } + } + } + ) +} + +@Composable +fun CupcakeApp( + viewModel: OrderViewModel = viewModel(), + navController: NavHostController = rememberNavController() +) { + + Scaffold( + topBar = { + CupcakeAppBar( + canNavigateBack = false, + navigateUp = { /* TODO: implement back navigation */ } + ) + } + ) { innerPadding -> + val uiState by viewModel.uiState.collectAsState() + + } +} diff --git a/app/src/main/java/com/example/cupcake/MainActivity.kt b/app/src/main/java/com/example/cupcake/MainActivity.kt new file mode 100644 index 0000000..22b231a --- /dev/null +++ b/app/src/main/java/com/example/cupcake/MainActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.core.view.WindowCompat +import com.example.cupcake.ui.theme.CupcakeTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + setContent { + CupcakeTheme { + CupcakeApp() + } + } + } +} diff --git a/app/src/main/java/com/example/cupcake/data/DataSource.kt b/app/src/main/java/com/example/cupcake/data/DataSource.kt new file mode 100644 index 0000000..d6fb063 --- /dev/null +++ b/app/src/main/java/com/example/cupcake/data/DataSource.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.data + +import com.example.cupcake.R + +object DataSource { + val flavors = listOf( + R.string.vanilla, + R.string.chocolate, + R.string.red_velvet, + R.string.salted_caramel, + R.string.coffee + ) + + val quantityOptions = listOf( + Pair(R.string.one_cupcake, 1), + Pair(R.string.six_cupcakes, 6), + Pair(R.string.twelve_cupcakes, 12) + ) +} diff --git a/app/src/main/java/com/example/cupcake/data/OrderUiState.kt b/app/src/main/java/com/example/cupcake/data/OrderUiState.kt new file mode 100644 index 0000000..56fec2e --- /dev/null +++ b/app/src/main/java/com/example/cupcake/data/OrderUiState.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.data + +/** + * Data class that represents the current UI state in terms of [quantity], [flavor], + * [dateOptions], selected pickup [date] and [price] + */ +data class OrderUiState( + /** Selected cupcake quantity (1, 6, 12) */ + val quantity: Int = 0, + /** Flavor of the cupcakes in the order (such as "Chocolate", "Vanilla", etc..) */ + val flavor: String = "", + /** Selected date for pickup (such as "Jan 1") */ + val date: String = "", + /** Total price for the order */ + val price: String = "", + /** Available pickup dates for the order*/ + val pickupOptions: List = listOf() +) diff --git a/app/src/main/java/com/example/cupcake/ui/OrderViewModel.kt b/app/src/main/java/com/example/cupcake/ui/OrderViewModel.kt new file mode 100644 index 0000000..3f23859 --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/OrderViewModel.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui + +import androidx.lifecycle.ViewModel +import com.example.cupcake.data.OrderUiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import java.text.NumberFormat +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +/** Price for a single cupcake */ +private const val PRICE_PER_CUPCAKE = 2.00 + +/** Additional cost for same day pickup of an order */ +private const val PRICE_FOR_SAME_DAY_PICKUP = 3.00 + +/** + * [OrderViewModel] holds information about a cupcake order in terms of quantity, flavor, and + * pickup date. It also knows how to calculate the total price based on these order details. + */ +class OrderViewModel : ViewModel() { + + /** + * Cupcake state for this order + */ + private val _uiState = MutableStateFlow(OrderUiState(pickupOptions = pickupOptions())) + val uiState: StateFlow = _uiState.asStateFlow() + + /** + * Set the quantity [numberCupcakes] of cupcakes for this order's state and update the price + */ + fun setQuantity(numberCupcakes: Int) { + _uiState.update { currentState -> + currentState.copy( + quantity = numberCupcakes, + price = calculatePrice(quantity = numberCupcakes) + ) + } + } + + /** + * Set the [desiredFlavor] of cupcakes for this order's state. + * Only 1 flavor can be selected for the whole order. + */ + fun setFlavor(desiredFlavor: String) { + _uiState.update { currentState -> + currentState.copy(flavor = desiredFlavor) + } + } + + /** + * Set the [pickupDate] for this order's state and update the price + */ + fun setDate(pickupDate: String) { + _uiState.update { currentState -> + currentState.copy( + date = pickupDate, + price = calculatePrice(pickupDate = pickupDate) + ) + } + } + + /** + * Reset the order state + */ + fun resetOrder() { + _uiState.value = OrderUiState(pickupOptions = pickupOptions()) + } + + /** + * Returns the calculated price based on the order details. + */ + private fun calculatePrice( + quantity: Int = _uiState.value.quantity, + pickupDate: String = _uiState.value.date + ): String { + var calculatedPrice = quantity * PRICE_PER_CUPCAKE + // If the user selected the first option (today) for pickup, add the surcharge + if (pickupOptions()[0] == pickupDate) { + calculatedPrice += PRICE_FOR_SAME_DAY_PICKUP + } + val formattedPrice = NumberFormat.getCurrencyInstance().format(calculatedPrice) + return formattedPrice + } + + /** + * Returns a list of date options starting with the current date and the following 3 dates. + */ + private fun pickupOptions(): List { + val dateOptions = mutableListOf() + val formatter = SimpleDateFormat("E MMM d", Locale.getDefault()) + val calendar = Calendar.getInstance() + // add current date and the following 3 dates. + repeat(4) { + dateOptions.add(formatter.format(calendar.time)) + calendar.add(Calendar.DATE, 1) + } + return dateOptions + } +} diff --git a/app/src/main/java/com/example/cupcake/ui/SelectOptionScreen.kt b/app/src/main/java/com/example/cupcake/ui/SelectOptionScreen.kt new file mode 100644 index 0000000..19821ef --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/SelectOptionScreen.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.selection.selectable +import androidx.compose.material3.Button +import androidx.compose.material3.Divider +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.example.cupcake.R +import com.example.cupcake.ui.components.FormattedPriceLabel + +/** + * Composable that displays the list of items as [RadioButton] options, + * [onSelectionChanged] lambda that notifies the parent composable when a new value is selected, + * [onCancelButtonClicked] lambda that cancels the order when user clicks cancel and + * [onNextButtonClicked] lambda that triggers the navigation to next screen + */ +@Composable +fun SelectOptionScreen( + subtotal: String, + options: List, + onSelectionChanged: (String) -> Unit = {}, + modifier: Modifier = Modifier +){ + var selectedValue by rememberSaveable { mutableStateOf("") } + + Column( + modifier = modifier, + verticalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium))){ + options.forEach { item -> + Row( + modifier = Modifier.selectable( + selected = selectedValue == item, + onClick = { + selectedValue = item + onSelectionChanged(item) + } + ), + verticalAlignment = Alignment.CenterVertically + ){ + RadioButton( + selected = selectedValue == item, + onClick = { + selectedValue = item + onSelectionChanged(item) + } + ) + Text(item) + } + } + Divider( + thickness = dimensionResource(R.dimen.thickness_divider), + modifier = Modifier.padding(bottom = dimensionResource(R.dimen.padding_medium)) + ) + FormattedPriceLabel( + subtotal = subtotal, + modifier = Modifier + .align(Alignment.End) + .padding( + top = dimensionResource(R.dimen.padding_medium), + bottom = dimensionResource(R.dimen.padding_medium) + ) + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(R.dimen.padding_medium)) + .weight(1f, false), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)), + verticalAlignment = Alignment.Bottom + ){ + OutlinedButton(modifier = Modifier.weight(1f), onClick = {}) { + Text(stringResource(R.string.cancel)) + } + Button( + modifier = Modifier.weight(1f), + // the button is enabled when the user makes a selection + enabled = selectedValue.isNotEmpty(), + onClick = {} + ) { + Text(stringResource(R.string.next)) + } + } + } + +} + +@Preview +@Composable +fun SelectOptionPreview(){ + SelectOptionScreen( + subtotal = "299.99", + options = listOf("Option 1", "Option 2", "Option 3", "Option 4"), + modifier = Modifier.fillMaxHeight() + ) +} diff --git a/app/src/main/java/com/example/cupcake/ui/StartOrderScreen.kt b/app/src/main/java/com/example/cupcake/ui/StartOrderScreen.kt new file mode 100644 index 0000000..64408bb --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/StartOrderScreen.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui + +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.cupcake.R +import com.example.cupcake.data.DataSource + +/** + * Composable that allows the user to select the desired cupcake quantity and expects + * [onNextButtonClicked] lambda that expects the selected quantity and triggers the navigation to + * next screen + */ +@Composable +fun StartOrderScreen( + quantityOptions: List>, + modifier: Modifier = Modifier +){ + Column( + modifier = modifier, + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)) + ) { + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + Image( + painter = painterResource(R.drawable.cupcake), + contentDescription = null, + modifier = Modifier.width(300.dp) + ) + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + Text( + text = stringResource(R.string.order_cupcakes), + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_small))) + } + Row(modifier = Modifier.weight(1f, false)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy( + dimensionResource(id = R.dimen.padding_medium) + ) + ) { + quantityOptions.forEach { item -> + SelectQuantityButton( + labelResourceId = item.first, + onClick = {} + ) + } + } + } + } +} + +/** + * Customizable button composable that displays the [labelResourceId] + * and triggers [onClick] lambda when this composable is clicked + */ +@Composable +fun SelectQuantityButton( + @StringRes labelResourceId: Int, + onClick: () -> Unit, + modifier: Modifier = Modifier +){ + Button( + onClick = onClick, + modifier = modifier.widthIn(min = 250.dp) + ) { + Text(stringResource(labelResourceId)) + } +} + +@Preview +@Composable +fun StartOrderPreview(){ + StartOrderScreen( + quantityOptions = DataSource.quantityOptions, + modifier = Modifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium)) + ) +} diff --git a/app/src/main/java/com/example/cupcake/ui/SummaryScreen.kt b/app/src/main/java/com/example/cupcake/ui/SummaryScreen.kt new file mode 100644 index 0000000..97fedbe --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/SummaryScreen.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Divider +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import com.example.cupcake.R +import com.example.cupcake.data.OrderUiState +import com.example.cupcake.ui.components.FormattedPriceLabel + +/** + * This composable expects [orderUiState] that represents the order state, [onCancelButtonClicked] + * lambda that triggers canceling the order and passes the final order to [onSendButtonClicked] + * lambda + */ +@Composable +fun OrderSummaryScreen( + orderUiState: OrderUiState, + modifier: Modifier = Modifier +){ + val resources = LocalContext.current.resources + + val numberOfCupcakes = resources.getQuantityString( + R.plurals.cupcakes, + orderUiState.quantity, + orderUiState.quantity + ) + //Load and format a string resource with the parameters. + val orderSummary = stringResource( + R.string.order_details, + numberOfCupcakes, + orderUiState.flavor, + orderUiState.date, + orderUiState.quantity + ) + val newOrder = stringResource(R.string.new_cupcake_order) + //Create a list of order summary to display + val items = listOf( + // Summary line 1: display selected quantity + Pair(stringResource(R.string.quantity), numberOfCupcakes), + // Summary line 2: display selected flavor + Pair(stringResource(R.string.flavor), orderUiState.flavor), + // Summary line 3: display selected pickup date + Pair(stringResource(R.string.pickup_date), orderUiState.date) + ) + + Column( + modifier = modifier, + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)), + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)) + ) { + items.forEach { item -> + Text(item.first.uppercase()) + Text(text = item.second, fontWeight = FontWeight.Bold) + Divider(thickness = dimensionResource(R.dimen.thickness_divider)) + } + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_small))) + FormattedPriceLabel( + subtotal = orderUiState.price, + modifier = Modifier.align(Alignment.End) + ) + } + Row( + modifier = Modifier + .weight(1f, false) + .padding(dimensionResource(R.dimen.padding_medium)) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)) + ) { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) { + Text(stringResource(R.string.send)) + } + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = {} + ) { + Text(stringResource(R.string.cancel)) + } + } + } + } +} + +@Preview +@Composable +fun OrderSummaryPreview(){ + OrderSummaryScreen( + orderUiState = OrderUiState(0, "Test", "Test", "$300.00"), + modifier = Modifier.fillMaxHeight() + ) +} diff --git a/app/src/main/java/com/example/cupcake/ui/components/CommonUi.kt b/app/src/main/java/com/example/cupcake/ui/components/CommonUi.kt new file mode 100644 index 0000000..825635b --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/components/CommonUi.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui.components + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.example.cupcake.R + +/** + * Composable that displays formatted [price] that will be formatted and displayed on screen + */ +@Composable +fun FormattedPriceLabel(subtotal: String, modifier: Modifier = Modifier) { + Text( + text = stringResource(R.string.subtotal_price, subtotal), + modifier = modifier, + style = MaterialTheme.typography.headlineSmall + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cupcake/ui/theme/Color.kt b/app/src/main/java/com/example/cupcake/ui/theme/Color.kt new file mode 100644 index 0000000..f387523 --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/theme/Color.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui.theme + +import androidx.compose.ui.graphics.Color + +val md_theme_light_primary = Color(0xFF984062) +val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_primaryContainer = Color(0xFFFFD9E2) +val md_theme_light_onPrimaryContainer = Color(0xFF3E001E) +val md_theme_light_secondary = Color(0xFF74565F) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFFFD9E2) +val md_theme_light_onSecondaryContainer = Color(0xFF2B151C) +val md_theme_light_tertiary = Color(0xFF7C5635) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFFFFDCC2) +val md_theme_light_onTertiaryContainer = Color(0xFF2E1500) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFFFFBFF) +val md_theme_light_onBackground = Color(0xFF201A1B) +val md_theme_light_surface = Color(0xFFFFFBFF) +val md_theme_light_onSurface = Color(0xFF201A1B) +val md_theme_light_surfaceVariant = Color(0xFFF2DDE2) +val md_theme_light_onSurfaceVariant = Color(0xFF514347) +val md_theme_light_outline = Color(0xFF837377) +val md_theme_light_inverseOnSurface = Color(0xFFFAEEEF) +val md_theme_light_inverseSurface = Color(0xFF352F30) +val md_theme_light_inversePrimary = Color(0xFFFFB0C9) +val md_theme_light_surfaceTint = Color(0xFF984062) +val md_theme_light_outlineVariant = Color(0xFFD5C2C6) +val md_theme_light_scrim = Color(0xFF000000) + +val md_theme_dark_primary = Color(0xFFFFB0C9) +val md_theme_dark_onPrimary = Color(0xFF5E1133) +val md_theme_dark_primaryContainer = Color(0xFF7B294A) +val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9E2) +val md_theme_dark_secondary = Color(0xFFE2BDC7) +val md_theme_dark_onSecondary = Color(0xFF422931) +val md_theme_dark_secondaryContainer = Color(0xFF5A3F47) +val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9E2) +val md_theme_dark_tertiary = Color(0xFFEFBD94) +val md_theme_dark_onTertiary = Color(0xFF48290C) +val md_theme_dark_tertiaryContainer = Color(0xFF623F20) +val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCC2) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF201A1B) +val md_theme_dark_onBackground = Color(0xFFEBE0E1) +val md_theme_dark_surface = Color(0xFF201A1B) +val md_theme_dark_onSurface = Color(0xFFEBE0E1) +val md_theme_dark_surfaceVariant = Color(0xFF514347) +val md_theme_dark_onSurfaceVariant = Color(0xFFD5C2C6) +val md_theme_dark_outline = Color(0xFF9E8C90) +val md_theme_dark_inverseOnSurface = Color(0xFF201A1B) +val md_theme_dark_inverseSurface = Color(0xFFEBE0E1) +val md_theme_dark_inversePrimary = Color(0xFF984062) +val md_theme_dark_surfaceTint = Color(0xFFFFB0C9) +val md_theme_dark_outlineVariant = Color(0xFF514347) +val md_theme_dark_scrim = Color(0xFF000000) diff --git a/app/src/main/java/com/example/cupcake/ui/theme/Theme.kt b/app/src/main/java/com/example/cupcake/ui/theme/Theme.kt new file mode 100644 index 0000000..df5f5f0 --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/theme/Theme.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + outlineVariant = md_theme_light_outlineVariant, + scrim = md_theme_light_scrim, +) + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + outlineVariant = md_theme_dark_outlineVariant, + scrim = md_theme_dark_scrim, +) + +@Composable +fun CupcakeTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ but turned off for training purposes + dynamicColor: Boolean = false, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColors + else -> LightColors + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/com/example/cupcake/ui/theme/Type.kt b/app/src/main/java/com/example/cupcake/ui/theme/Type.kt new file mode 100644 index 0000000..1085d85 --- /dev/null +++ b/app/src/main/java/com/example/cupcake/ui/theme/Type.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.cupcake.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..a04a601 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/cupcake.xml b/app/src/main/res/drawable/cupcake.xml new file mode 100644 index 0000000..eebcbb7 --- /dev/null +++ b/app/src/main/res/drawable/cupcake.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..3c506ae --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..faf6f98 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..faf6f98 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..509f5eb --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,21 @@ + + + + 8dp + 16dp + 1dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..8d8fcaa --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,46 @@ + + + Cupcake + Order Cupcakes + One Cupcake + Six Cupcakes + Twelve Cupcakes + Choose Flavor + Vanilla + Chocolate + Red Velvet + Salted Caramel + Coffee + Special Flavor + Cancel + Next + Choose Pickup Date + Order Summary + Send Order to Another App + Quantity + Flavor + Pickup date + Subtotal %s + Total %s + New Cupcake Order + Quantity: %1$s \nFlavor: %2$s \nPickup date: %3$s \nTotal: %4$s \n\nThank you! + Back + + %d cupcake + %d cupcakes + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..0f77a7e --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,19 @@ + + + +