Compare commits

..

No commits in common. "4fa52e9bc5b750bb85a96534483e026c95ec10f6" and "fa202ff162b3bc3bf2cb6d165603e54514837506" have entirely different histories.

11 changed files with 75 additions and 96 deletions

View File

@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>android/.github:renovate-config"
]
}

View File

@ -5,12 +5,15 @@ This app contains an order flow for cupcakes with options for quantity, flavor,
The order details get displayed on an order summary screen and can be shared to another app to The order details get displayed on an order summary screen and can be shared to another app to
send the order. send the order.
TODO
Pre-requisites Pre-requisites
-------------- --------------
* Experience with Kotlin syntax. * Experience with Kotlin syntax.
* How to create and run a project in Android Studio. * How to create and run a project in Android Studio.
* How to create composable functions * How to create composable functions
* TODO
Getting Started Getting Started

View File

@ -20,12 +20,12 @@ plugins {
android { android {
namespace = "com.example.cupcake" namespace = "com.example.cupcake"
compileSdk = 34 compileSdk = 33
defaultConfig { defaultConfig {
applicationId = "com.example.cupcake" applicationId = "com.example.cupcake"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 33
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@ -56,7 +56,7 @@ android {
compose = true compose = true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = "1.5.3" kotlinCompilerExtensionVersion = "1.4.7"
} }
packaging { packaging {
resources { resources {
@ -67,24 +67,24 @@ android {
dependencies { dependencies {
implementation(platform("androidx.compose:compose-bom:2023.10.01")) implementation(platform("androidx.compose:compose-bom:2023.05.01"))
implementation("androidx.activity:activity-compose:1.8.0") implementation("androidx.activity:activity-compose:1.7.2")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation("androidx.compose.runtime:runtime") implementation("androidx.compose.runtime:runtime")
implementation("androidx.compose.runtime:runtime-livedata") implementation("androidx.compose.runtime:runtime-livedata")
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:${rootProject.extra["lifecycle_version"]}") implementation("androidx.lifecycle:lifecycle-livedata-ktx:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}") implementation("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.extra["lifecycle_version"]}") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${rootProject.extra["lifecycle_version"]}") implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.navigation:navigation-compose:2.7.4") implementation("androidx.navigation:navigation-compose:2.5.3")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.navigation:navigation-testing:2.7.4") androidTestImplementation("androidx.navigation:navigation-testing:2.5.3")
androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")

View File

@ -76,25 +76,25 @@ class CupcakeOrderScreenTest {
@Test @Test
fun selectOptionScreen_verifyContent() { fun selectOptionScreen_verifyContent() {
// Given list of options // Given list of options
val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango") val flavours = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
// And sub total // And sub total
val subtotal = "$100" val subTotal = "$100"
// When SelectOptionScreen is loaded // When SelectOptionScreen is loaded
composeTestRule.setContent { composeTestRule.setContent {
SelectOptionScreen(subtotal = subtotal, options = flavors) SelectOptionScreen(subtotal = subTotal, options = flavours)
} }
// Then all the options are displayed on the screen. // Then all the options are displayed on the screen.
flavors.forEach { flavor -> flavours.forEach { flavour ->
composeTestRule.onNodeWithText(flavor).assertIsDisplayed() composeTestRule.onNodeWithText(flavour).assertIsDisplayed()
} }
// And then the subtotal is displayed correctly. // And then the subtotal is displayed correctly.
composeTestRule.onNodeWithText( composeTestRule.onNodeWithText(
composeTestRule.activity.getString( composeTestRule.activity.getString(
R.string.subtotal_price, R.string.subtotal_price,
subtotal subTotal
) )
).assertIsDisplayed() ).assertIsDisplayed()
@ -148,6 +148,6 @@ class CupcakeOrderScreenTest {
R.string.subtotal_price, R.string.subtotal_price,
fakeOrderUiState.price fakeOrderUiState.price
) )
).assertIsDisplayed() )
} }
} }

View File

@ -21,8 +21,6 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -117,10 +115,7 @@ fun CupcakeApp(
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = CupcakeScreen.Start.name, startDestination = CupcakeScreen.Start.name,
modifier = Modifier modifier = Modifier.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(innerPadding)
) { ) {
composable(route = CupcakeScreen.Start.name) { composable(route = CupcakeScreen.Start.name) {
StartOrderScreen( StartOrderScreen(
@ -187,11 +182,6 @@ private fun cancelOrderAndNavigateToStart(
navController.popBackStack(CupcakeScreen.Start.name, inclusive = false) navController.popBackStack(CupcakeScreen.Start.name, inclusive = false)
} }
/*
*/
/** /**
* Creates an intent to share order details * Creates an intent to share order details
*/ */

View File

@ -18,13 +18,13 @@ package com.example.cupcake
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.core.view.WindowCompat
import com.example.cupcake.ui.theme.CupcakeTheme import com.example.cupcake.ui.theme.CupcakeTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
CupcakeTheme { CupcakeTheme {
CupcakeApp() CupcakeApp()

View File

@ -39,7 +39,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.example.cupcake.R import com.example.cupcake.R
import com.example.cupcake.ui.components.FormattedPriceLabel import com.example.cupcake.ui.components.FormattedPriceLabel
import com.example.cupcake.ui.theme.CupcakeTheme
/** /**
* Composable that displays the list of items as [RadioButton] options, * Composable that displays the list of items as [RadioButton] options,
@ -101,14 +100,12 @@ fun SelectOptionScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_medium)), .padding(dimensionResource(R.dimen.padding_medium))
.weight(1f, false),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)),
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
){ ){
OutlinedButton( OutlinedButton(modifier = Modifier.weight(1f), onClick = onCancelButtonClicked) {
modifier = Modifier.weight(1f),
onClick = onCancelButtonClicked
) {
Text(stringResource(R.string.cancel)) Text(stringResource(R.string.cancel))
} }
Button( Button(
@ -127,11 +124,9 @@ fun SelectOptionScreen(
@Preview @Preview
@Composable @Composable
fun SelectOptionPreview(){ fun SelectOptionPreview(){
CupcakeTheme {
SelectOptionScreen( SelectOptionScreen(
subtotal = "299.99", subtotal = "299.99",
options = listOf("Option 1", "Option 2", "Option 3", "Option 4"), options = listOf("Option 1", "Option 2", "Option 3", "Option 4"),
modifier = Modifier.fillMaxHeight() modifier = Modifier.fillMaxHeight()
) )
} }
}

View File

@ -19,7 +19,9 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -39,7 +41,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.cupcake.R import com.example.cupcake.R
import com.example.cupcake.data.DataSource import com.example.cupcake.data.DataSource
import com.example.cupcake.ui.theme.CupcakeTheme
/** /**
* Composable that allows the user to select the desired cupcake quantity and expects * Composable that allows the user to select the desired cupcake quantity and expects
@ -74,6 +75,7 @@ fun StartOrderScreen(
) )
Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_small))) Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_small)))
} }
Row(modifier = Modifier.weight(1f, false)) {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@ -84,13 +86,13 @@ fun StartOrderScreen(
quantityOptions.forEach { item -> quantityOptions.forEach { item ->
SelectQuantityButton( SelectQuantityButton(
labelResourceId = item.first, labelResourceId = item.first,
onClick = { onNextButtonClicked(item.second) }, onClick = { onNextButtonClicked(item.second) }
modifier = Modifier.fillMaxWidth(),
) )
} }
} }
} }
} }
}
/** /**
* Customizable button composable that displays the [labelResourceId] * Customizable button composable that displays the [labelResourceId]
@ -113,13 +115,9 @@ fun SelectQuantityButton(
@Preview @Preview
@Composable @Composable
fun StartOrderPreview(){ fun StartOrderPreview(){
CupcakeTheme {
StartOrderScreen( StartOrderScreen(
quantityOptions = DataSource.quantityOptions, quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {}, onNextButtonClicked = {},
modifier = Modifier modifier = Modifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium))
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
) )
} }
}

View File

@ -38,7 +38,6 @@ import androidx.compose.ui.tooling.preview.Preview
import com.example.cupcake.R import com.example.cupcake.R
import com.example.cupcake.data.OrderUiState import com.example.cupcake.data.OrderUiState
import com.example.cupcake.ui.components.FormattedPriceLabel import com.example.cupcake.ui.components.FormattedPriceLabel
import com.example.cupcake.ui.theme.CupcakeTheme
/** /**
* This composable expects [orderUiState] that represents the order state, [onCancelButtonClicked] * This composable expects [orderUiState] that represents the order state, [onCancelButtonClicked]
@ -98,7 +97,9 @@ fun OrderSummaryScreen(
) )
} }
Row( Row(
modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) modifier = Modifier
.weight(1f, false)
.padding(dimensionResource(R.dimen.padding_medium))
) { ) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)) verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
@ -123,7 +124,6 @@ fun OrderSummaryScreen(
@Preview @Preview
@Composable @Composable
fun OrderSummaryPreview(){ fun OrderSummaryPreview(){
CupcakeTheme {
OrderSummaryScreen( OrderSummaryScreen(
orderUiState = OrderUiState(0, "Test", "Test", "$300.00"), orderUiState = OrderUiState(0, "Test", "Test", "$300.00"),
onSendButtonClicked = { subject: String, summary: String -> }, onSendButtonClicked = { subject: String, summary: String -> },
@ -131,4 +131,3 @@ fun OrderSummaryPreview() {
modifier = Modifier.fillMaxHeight() modifier = Modifier.fillMaxHeight()
) )
} }
}

View File

@ -16,11 +16,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
extra.apply { extra.apply {
set("lifecycle_version", "2.6.2") set("lifecycle_version", "2.6.1")
} }
} }
plugins { plugins {
id("com.android.application") version "8.1.2" apply false id("com.android.application") version "8.0.2" apply false
id("com.android.library") version "8.1.2" apply false id("com.android.library") version "8.0.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.10" apply false id("org.jetbrains.kotlin.android") version "1.8.21" apply false
} }

View File

@ -1,6 +1,6 @@
#Sun Mar 19 17:30:22 PDT 2023 #Sun Mar 19 17:30:22 PDT 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists