Skip to content

Commit

Permalink
Display coupon preview
Browse files Browse the repository at this point in the history
  • Loading branch information
Maruchin1 committed Apr 11, 2023
1 parent e984dea commit 7c6e48d
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package com.maruchin.domaindrivenandroid
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.maruchin.domaindrivenandroid.ui.couponPreview.couponPreviewScreen
import com.maruchin.domaindrivenandroid.ui.couponPreview.navigateToCouponPreview
import com.maruchin.domaindrivenandroid.ui.home.HOME_ROUTE
import com.maruchin.domaindrivenandroid.ui.home.homeScreen

@Composable
fun MainNavGraph() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = HOME_ROUTE) {
homeScreen()
homeScreen(
onOpenCoupon = { navController.navigateToCouponPreview(it) }
)
couponPreviewScreen(
onBack = { navController.navigateUp() }
)
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/maruchin/domaindrivenandroid/data/ID.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.maruchin.domaindrivenandroid.data

@JvmInline
value class ID(val value: String)
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
package com.maruchin.domaindrivenandroid.data.coupon

import com.maruchin.domaindrivenandroid.data.ID
import java.net.URL
import java.util.Currency

data class Coupon(
val id: String,
val id: ID,
val name: String,
val price: Money,
val image: URL,
)

val sampleCoupons = listOf(
Coupon(
id = "1",
id = ID("1"),
name = "Cheesburger with fries",
price = Money(value = 17.99, currency = Currency.getInstance("USD")),
image = URL("https://raw.githubusercontent.com/Maruchin1/domain-driven-android/master/images/cheesburger_with_fries_coupon.jpeg"),
),
Coupon(
id = "2",
id = ID("2"),
name = "Chicekburger with fries",
price = Money(value = 15.99, currency = Currency.getInstance("USD")),
image = URL("https://raw.githubusercontent.com/Maruchin1/domain-driven-android/master/images/chickenburger_with_fries_coupon.jpeg"),
),
Coupon(
id = "3",
id = ID("3"),
name = "Chicken nuggets with fries",
price = Money(value = 20.99, currency = Currency.getInstance("USD")),
image = URL("https://raw.githubusercontent.com/Maruchin1/domain-driven-android/master/images/chicken_nuggets_with_fries_coupon.jpeg"),
),
Coupon(
id = "4",
id = ID("4"),
name = "2 x Milkshake",
price = Money(value = 8.99, currency = Currency.getInstance("USD")),
image = URL("https://raw.githubusercontent.com/Maruchin1/domain-driven-android/master/images/two_milkshakes_coupon.jpeg"),
),
Coupon(
id = "5",
id = ID("5"),
name = "2 x Soda drink",
price = Money(value = 6.99, currency = Currency.getInstance("USD")),
image = URL("https://raw.githubusercontent.com/Maruchin1/domain-driven-android/master/images/two_soda_drinks_coupon.jpeg"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.maruchin.domaindrivenandroid.data.coupon

import com.maruchin.domaindrivenandroid.data.ID
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -9,4 +10,8 @@ class CouponsRepository @Inject constructor() {
suspend fun getAllCoupons(): List<Coupon> {
return sampleCoupons
}

suspend fun getCoupon(id: ID): Coupon? {
return sampleCoupons.find { it.id == id }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.maruchin.domaindrivenandroid.ui.couponPreview

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.maruchin.domaindrivenandroid.data.ID

const val COUPON_PREVIEW_ROUTE = "coupon-preview"
const val COUPON_IN = "couponId"

fun NavGraphBuilder.couponPreviewScreen(onBack: () -> Unit) {
composable("$COUPON_PREVIEW_ROUTE/{$COUPON_IN}") {
val couponId = ID(it.arguments?.getString(COUPON_IN) ?: "")
val viewModel = hiltViewModel<CouponPreviewViewModel>()
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.selectCoupon(couponId)
}
CouponPreviewScreen(state = state, onBack = onBack, onCollect = {})
}
}

fun NavController.navigateToCouponPreview(couponId: ID) {
navigate("$COUPON_PREVIEW_ROUTE/${couponId.value}")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.maruchin.domaindrivenandroid.ui.couponPreview

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CouponPreviewScreen(state: CouponPreviewUiState, onBack: () -> Unit, onCollect: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "My Coupons") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.Outlined.ArrowBack,
contentDescription = "Navigate up",
)
}
},
)
}
) { paddingValues ->
when (state) {
CouponPreviewUiState.Loading -> {}
is CouponPreviewUiState.Ready -> {
Column(modifier = Modifier.padding(paddingValues)) {
OutlinedCard(modifier = Modifier.padding(12.dp)) {
AsyncImage(
model = state.imageUrl,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
)
}
Text(
text = state.couponName,
style = MaterialTheme.typography.displaySmall,
modifier = Modifier.padding(vertical = 12.dp, horizontal = 20.dp)
)
Text(
text = state.price,
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 12.dp, horizontal = 20.dp),
)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = onCollect,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 12.dp),
) {
Text(text = "Collect".uppercase())
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.maruchin.domaindrivenandroid.ui.couponPreview

import androidx.compose.runtime.Immutable
import com.maruchin.domaindrivenandroid.data.coupon.Coupon

@Immutable
sealed class CouponPreviewUiState {

object Loading : CouponPreviewUiState()

class Ready(
val imageUrl: String,
val couponName: String,
val price: String,
) : CouponPreviewUiState() {

constructor(coupon: Coupon) : this(
imageUrl = coupon.image.toString(),
couponName = coupon.name,
price = coupon.price.toString(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.maruchin.domaindrivenandroid.ui.couponPreview

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.maruchin.domaindrivenandroid.data.ID
import com.maruchin.domaindrivenandroid.data.coupon.CouponsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class CouponPreviewViewModel @Inject constructor(
private val couponsRepository: CouponsRepository,
) : ViewModel() {

private val _uiState = MutableStateFlow<CouponPreviewUiState>(CouponPreviewUiState.Loading)
val uiState = _uiState.asStateFlow()

fun selectCoupon(couponId: ID) = viewModelScope.launch {
couponsRepository.getCoupon(couponId)?.let { coupon ->
_uiState.update {
CouponPreviewUiState.Ready(coupon)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.maruchin.domaindrivenandroid.data.ID

const val HOME_ROUTE = "home"

fun NavGraphBuilder.homeScreen() {
fun NavGraphBuilder.homeScreen(onOpenCoupon: (ID) -> Unit) {
composable(HOME_ROUTE) {
val viewModel = hiltViewModel<HomeViewModel>()
val state by viewModel.uiState.collectAsState()
HomeScreen(state = state)
HomeScreen(state = state, onOpenCoupon = onOpenCoupon)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.maruchin.domaindrivenandroid.data.coupon.Coupon
import com.maruchin.domaindrivenandroid.data.coupon.sampleCoupons
import com.maruchin.domaindrivenandroid.ui.theme.DomainDrivenAndroidTheme
import com.maruchin.domaindrivenandroid.data.ID

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(state: HomeUiState) {
fun HomeScreen(state: HomeUiState, onOpenCoupon: (ID) -> Unit) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
topBar = {
Expand All @@ -53,28 +48,28 @@ fun HomeScreen(state: HomeUiState) {
.nestedScroll(scrollBehavior.nestedScrollConnection),
) {
items(state.coupons) { coupon ->
CouponView(coupon = coupon)
CouponView(state = coupon, onClick = { onOpenCoupon(coupon.id) })
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CouponView(coupon: Coupon) {
private fun CouponView(state: CouponUiState, onClick: () -> Unit) {
val density = LocalDensity.current.density
var couponNamePadding by remember { mutableStateOf(0.dp) }
OutlinedCard(onClick = { /*TODO*/ }, modifier = Modifier.padding(6.dp)) {
OutlinedCard(onClick = onClick, modifier = Modifier.padding(6.dp)) {
Column {
AsyncImage(
model = coupon.image.toString(),
model = state.imageUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f / 1f),
)
Text(
text = coupon.name,
text = state.couponName,
style = MaterialTheme.typography.titleMedium,
maxLines = 2,
modifier = Modifier
Expand All @@ -87,7 +82,7 @@ private fun CouponView(coupon: Coupon) {
}
)
Text(
text = coupon.price.toString(),
text = state.price,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold,
Expand All @@ -96,18 +91,3 @@ private fun CouponView(coupon: Coupon) {
}
}
}

@Preview
@Composable
private fun HomeScreenPreview(@PreviewParameter(HomeUiStateProvider::class) state: HomeUiState) {
DomainDrivenAndroidTheme {
HomeScreen(state = state)
}
}

class HomeUiStateProvider : PreviewParameterProvider<HomeUiState> {
override val values = sequenceOf(
HomeUiState(),
HomeUiState(coupons = sampleCoupons, loading = false)
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
package com.maruchin.domaindrivenandroid.ui.home

import androidx.compose.runtime.Immutable
import com.maruchin.domaindrivenandroid.data.ID
import com.maruchin.domaindrivenandroid.data.coupon.Coupon

@Immutable
data class HomeUiState(
val coupons: List<Coupon> = emptyList(),
val coupons: List<CouponUiState> = emptyList(),
val loading: Boolean = true,
)

@Immutable
data class CouponUiState(
val id: ID,
val imageUrl: String,
val couponName: String,
val price: String,
) {

constructor(coupon: Coupon) : this(
id = coupon.id,
imageUrl = coupon.image.toString(),
couponName = coupon.name,
price = coupon.price.toString(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class HomeViewModel @Inject constructor(
}
val coupons = couponsRepository.getAllCoupons()
_uiState.update {
it.copy(loading = false, coupons = coupons)
it.copy(loading = false, coupons = coupons.map(::CouponUiState))
}
}
}

0 comments on commit 7c6e48d

Please sign in to comment.