Compare commits

...

10 Commits

Author SHA1 Message Date
Jose Alcérreca
82b7dc7723
Merge pull request #117 from google-developer-training/renovate/configure
Configure Renovate
2024-12-05 12:32:46 +01:00
Jose Alcérreca
8b50e72975
Update and rename renovate.json to .github/renovate.json 2024-12-05 12:32:35 +01:00
renovate[bot]
7097a75f37
Add renovate.json 2024-12-03 17:04:48 +00:00
the-scrambler
a5118e6dfe
Merge pull request #69 from google-developer-training/main-e2e
Implement edge-to-edge on branch main
2024-01-22 21:55:07 -08:00
John Shea
8f9aedaebc Remove TODO wording from README.md 2023-11-01 11:46:02 -04:00
John Shea
5b8dab87ee Implement vertical scrolling for landscape orientation 2023-10-25 11:58:33 -04:00
John Shea
1c31d367a1 Implement edge-to-edge 2023-10-24 11:45:13 -04:00
John Shea
14f13dd912 Update versions to the latest 2023-10-23 14:34:26 -04:00
John Shea
94e3827d7c
Fix issue 25 and minor refactoring (#64) 2023-06-27 14:53:57 -04:00
John Shea
fa202ff162
Migrate to Kotlin DSL (#59) 2023-06-20 13:27:49 -04:00
14 changed files with 178 additions and 159 deletions

6
.github/renovate.json vendored Normal file
View File

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

View File

@ -5,15 +5,12 @@ 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
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

View File

@ -1,90 +0,0 @@
/*
* 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'
androidTestImplementation platform('androidx.compose:compose-bom:2023.05.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
androidTestImplementation 'androidx.navigation:navigation-testing:2.5.3'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
debugImplementation 'androidx.compose.ui:ui-tooling'
}

93
app/build.gradle.kts Normal file
View File

@ -0,0 +1,93 @@
/*
* 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 = 34
defaultConfig {
applicationId = "com.example.cupcake"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = 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.5.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose:1.8.0")
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.12.0")
implementation("androidx.lifecycle:lifecycle-livedata-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-savedstate:${rootProject.extra["lifecycle_version"]}")
implementation("androidx.navigation:navigation-compose:2.7.4")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.navigation:navigation-testing:2.7.4")
androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.compose.ui:ui-tooling")
}

View File

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

View File

@ -21,6 +21,8 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.filled.ArrowBack
import androidx.compose.material3.Icon
@ -115,7 +117,10 @@ fun CupcakeApp(
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(

View File

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

View File

@ -39,6 +39,7 @@ 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
import com.example.cupcake.ui.theme.CupcakeTheme
/**
* Composable that displays the list of items as [RadioButton] options,
@ -54,14 +55,14 @@ fun SelectOptionScreen(
onCancelButtonClicked: () -> Unit = {},
onNextButtonClicked: () -> Unit = {},
modifier: Modifier = Modifier
){
) {
var selectedValue by rememberSaveable { mutableStateOf("") }
Column(
modifier = modifier,
verticalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium))){
Column(modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium))) {
options.forEach { item ->
Row(
modifier = Modifier.selectable(
@ -72,7 +73,7 @@ fun SelectOptionScreen(
}
),
verticalAlignment = Alignment.CenterVertically
){
) {
RadioButton(
selected = selectedValue == item,
onClick = {
@ -100,12 +101,14 @@ fun SelectOptionScreen(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_medium))
.weight(1f, false),
.padding(dimensionResource(R.dimen.padding_medium)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)),
verticalAlignment = Alignment.Bottom
){
OutlinedButton(modifier = Modifier.weight(1f), onClick = onCancelButtonClicked) {
) {
OutlinedButton(
modifier = Modifier.weight(1f),
onClick = onCancelButtonClicked
) {
Text(stringResource(R.string.cancel))
}
Button(
@ -123,10 +126,12 @@ fun SelectOptionScreen(
@Preview
@Composable
fun SelectOptionPreview(){
fun SelectOptionPreview() {
CupcakeTheme {
SelectOptionScreen(
subtotal = "299.99",
options = listOf("Option 1", "Option 2", "Option 3", "Option 4"),
modifier = Modifier.fillMaxHeight()
)
}
}

View File

@ -19,9 +19,7 @@ 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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@ -41,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.cupcake.R
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
@ -52,7 +51,7 @@ fun StartOrderScreen(
quantityOptions: List<Pair<Int, Int>>,
onNextButtonClicked: (Int) -> Unit,
modifier: Modifier = Modifier
){
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.SpaceBetween
@ -75,7 +74,6 @@ fun StartOrderScreen(
)
Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_small)))
}
Row(modifier = Modifier.weight(1f, false)) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
@ -86,12 +84,12 @@ fun StartOrderScreen(
quantityOptions.forEach { item ->
SelectQuantityButton(
labelResourceId = item.first,
onClick = { onNextButtonClicked(item.second) }
onClick = { onNextButtonClicked(item.second) },
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}
}
/**
@ -103,7 +101,7 @@ fun SelectQuantityButton(
@StringRes labelResourceId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
){
) {
Button(
onClick = onClick,
modifier = modifier.widthIn(min = 250.dp)
@ -114,10 +112,14 @@ fun SelectQuantityButton(
@Preview
@Composable
fun StartOrderPreview(){
fun StartOrderPreview() {
CupcakeTheme {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {},
modifier = Modifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium))
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}

View File

@ -38,6 +38,7 @@ 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
import com.example.cupcake.ui.theme.CupcakeTheme
/**
* This composable expects [orderUiState] that represents the order state, [onCancelButtonClicked]
@ -50,7 +51,7 @@ fun OrderSummaryScreen(
onCancelButtonClicked: () -> Unit,
onSendButtonClicked: (String, String) -> Unit,
modifier: Modifier = Modifier
){
) {
val resources = LocalContext.current.resources
val numberOfCupcakes = resources.getQuantityString(
@ -97,9 +98,7 @@ fun OrderSummaryScreen(
)
}
Row(
modifier = Modifier
.weight(1f, false)
.padding(dimensionResource(R.dimen.padding_medium))
modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium))
) {
Column(
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
@ -123,11 +122,13 @@ fun OrderSummaryScreen(
@Preview
@Composable
fun OrderSummaryPreview(){
fun OrderSummaryPreview() {
CupcakeTheme {
OrderSummaryScreen(
orderUiState = OrderUiState(0, "Test", "Test", "$300.00"),
onSendButtonClicked = { subject: String, summary: String -> },
onCancelButtonClicked = {},
modifier = Modifier.fillMaxHeight()
)
}
}

View File

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

View File

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

View File

@ -28,4 +28,4 @@ dependencyResolutionManagement {
}
}
rootProject.name = "Cupcake"
include ':app'
include(":app")