Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step2: 페미먼츠(카드목록) #44

Open
wants to merge 24 commits into
base: ironelder
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ dependencies {
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
9 changes: 3 additions & 6 deletions app/src/main/java/nextstep/payments/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import nextstep.payments.ui.screen.NewCardScreen
import nextstep.payments.ui.screen.NewCardViewModel
import androidx.navigation.compose.rememberNavController
import nextstep.payments.ui.navigation.PaymentsNavigationHost
import nextstep.payments.ui.theme.PaymentsTheme

class MainActivity : ComponentActivity() {
Expand All @@ -24,7 +21,7 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NewCardScreen()
PaymentsNavigationHost(navHostController = rememberNavController())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nextstep.payments.domain

import nextstep.payments.ui.model.PaymentCardModel

object PaymentCardsRepository {

private val _cards = mutableListOf<PaymentCardModel>()
val cards: List<PaymentCardModel> get() = _cards.toList()

fun addCard(card: PaymentCardModel) {
_cards.add(card)
}
}
140 changes: 129 additions & 11 deletions app/src/main/java/nextstep/payments/ui/component/PaymentCard.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,157 @@
package nextstep.payments.ui.component

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun PaymentCard(
fun AddPaymentCard(
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
DefaultCard(
modifier = modifier,
color =Color(0xFFE5E5E5),
content = {
Box(
modifier = Modifier
.fillMaxSize()
.clickable { onClick() },
contentAlignment = Alignment.Center
Comment on lines +36 to +40

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클릭 효과가 도형의 모양과 일치하지 않고 직사각형으로 잡히고 있어요.
눌렀을 때 ripple 효과가 어떻게 보이는지 확인하면 됩니다.

Surface 컴포넌트와 같은 곳에서 제공하는 shape 파라미터가 어떻게 동작하는지 살펴볼 수 있습니다.
https://developer.android.com/develop/ui/compose/modifiers?hl=ko#order-modifier-matters

) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Add",
tint = Color.Black,
)
}
}
)
}

@Composable
fun DefaultPaymentCard(
modifier: Modifier = Modifier,
) {
DefaultCard(
modifier = modifier,
content = {
CardIcChip(Modifier.padding(start = 14.dp, bottom = 10.dp))
}
)
}

@Composable
fun RegisteredPaymentCard(
modifier: Modifier = Modifier,
) {
Comment on lines +64 to +67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카드 밑부분이 잘리는 것 같네요!

DefaultCard(
modifier = modifier,
content = {

Column(
Modifier
.fillMaxSize()
.padding(start = 14.dp, end = 14.dp, top = 44.dp, bottom = 16.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
CardIcChip()
Text(
modifier = modifier.fillMaxWidth(),
text = "1234-5678-1234-5678",
fontSize = 12.sp,
color = Color.White,
maxLines = 1
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "JINHYUK JANG",
color = Color.White,
fontSize = 12.sp
)
Text(
modifier = modifier,
text = "00/00",
fontSize = 12.sp,
color = Color.White,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

색상이 반복된다면, ContentColor를 이용하여 제공하는 방법도 좋습니다.
이는 여러가지 컴포넌트에서 제공하는 contentColor가 동작하는 방식과 동일합니다.

CompositionLocalProvider(
   LocalContentColor provides Color.White
) { ... }

https://developer.android.com/develop/ui/compose/compositionlocal?hl=ko

)
}
}
}
)
}

@Composable
fun DefaultCard(
modifier: Modifier = Modifier,
color:Color = Color(0xFF333333),
content: @Composable () -> Unit = {}
) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = modifier
.shadow(8.dp)
.size(width = 208.dp, height = 124.dp)
.background(
color = Color(0xFF333333),
color = color,
shape = RoundedCornerShape(5.dp),
)
) {
Box(
modifier = Modifier
.padding(start = 14.dp, bottom = 10.dp)
.size(width = 40.dp, height = 26.dp)
.background(
color = Color(0xFFCBBA64),
shape = RoundedCornerShape(4.dp),
)
)
content()
}
}

@Composable
fun CardIcChip(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.size(width = 40.dp, height = 26.dp)
.background(
color = Color(0xFFCBBA64),
shape = RoundedCornerShape(4.dp),
)
)
}

@Preview(showBackground = true)
@Composable
private fun AddPaymentCardPreview() {
AddPaymentCard()
}

@Preview(showBackground = true)
@Composable
private fun DefaultPaymentCardPreview() {
DefaultPaymentCard()
}

@Preview(showBackground = true)
@Composable
private fun RegisteredPaymentCardPreview() {
RegisteredPaymentCard()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.payments.ui.model

data class PaymentCardModel(
val cardNumber: String,
val ownerName: String,
val expiredDate: String,
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nextstep.payments.ui.navigation

sealed class NavigationModel(val route: String) {
data object AddPaymentCard : NavigationModel("AddCard")
data object PaymentCards : NavigationModel("PaymentCards")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.payments.ui.navigation

import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import nextstep.payments.ui.screen.NewCardScreenRoute
import nextstep.payments.ui.screen.PaymentCardsScreenRoute

@Composable
fun PaymentsNavigationHost(
navHostController: NavHostController
) {
NavHost(
navController = navHostController,
startDestination = NavigationModel.PaymentCards.route
) {
composable(NavigationModel.PaymentCards.route) { navBackResult ->
PaymentCardsScreenRoute(
onAddCardClick = { navHostController.navigate(NavigationModel.AddPaymentCard.route) },
)
Comment on lines +19 to +21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로운 카드가 추가되었을 때 카드 목록이 업데이트 되어야 한다. 이러한 상황에서 rememberLauncherForActivityResult()를 활용하면 업데이트 상태를 UI에 반영할 수 있다.

카드를 추가하더라도 항상 더미 데이터로 출력을 하고 있어요.
혹시 의도하신 내용일까요?

}
composable(NavigationModel.AddPaymentCard.route) {
NewCardScreenRoute(
onBackClick = { navHostController.popBackStack() },
onAddComplete = { navHostController.popBackStack() }
)
}
}
}
33 changes: 29 additions & 4 deletions app/src/main/java/nextstep/payments/ui/screen/NewCardScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,32 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import nextstep.payments.ui.component.DefaultPaymentCard
import nextstep.payments.ui.component.NewCardTopBar
import nextstep.payments.ui.component.PaymentCard
import nextstep.payments.ui.theme.PaymentsTheme

@Composable
fun NewCardScreenRoute(
onBackClick: () -> Unit,
onAddComplete: () -> Unit,
viewModel: NewCardViewModel = viewModel(),
) {
NewCardScreen(
onBackClick = onBackClick,
onSaveClick = {
viewModel.addCard()
onAddComplete()
Comment on lines +34 to +35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ViewModel의 cardAdded를 활용할 수 있지 않을까요?
강의자료에 있는 내용을 참고 해보셔도 좋을 것 같아요

},
viewModel = viewModel,
)
}

//Stateful한 NewCardScreen
@Composable
internal fun NewCardScreen(
modifier: Modifier = Modifier,
onBackClick: () -> Unit,
onSaveClick: () -> Unit,
viewModel: NewCardViewModel = viewModel(),
) {
val cardNumber by viewModel.cardNumber.collectAsStateWithLifecycle()
Expand All @@ -44,6 +61,8 @@ internal fun NewCardScreen(
setOwnerName = viewModel::setOwnerName,
setPassword = viewModel::setPassword,
modifier = modifier,
onBackClick = onBackClick,
onSaveClick = onSaveClick,
)
}

Expand All @@ -59,9 +78,11 @@ private fun NewCardScreen(
setOwnerName: (String) -> Unit,
setPassword: (String) -> Unit,
modifier: Modifier = Modifier,
onBackClick: () -> Unit,
onSaveClick: () -> Unit,
) {
Scaffold(
topBar = { NewCardTopBar(onBackClick = { TODO() }, onSaveClick = { TODO() }) },
topBar = { NewCardTopBar(onBackClick = { onBackClick() }, onSaveClick = { onSaveClick() }) },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마이너한 의견입니다.
람다의 타입이 동일한다면 별도로 람다를 생성하는 대신 그대로 전달할 수 있습니다.

onBackClick = onBackClick,
onSaveClick = onSaveClick

modifier = modifier
) { innerPadding ->
Column(
Expand All @@ -73,7 +94,7 @@ private fun NewCardScreen(
) {
Spacer(modifier = Modifier.height(14.dp))

PaymentCard()
DefaultPaymentCard()

Spacer(modifier = Modifier.height(10.dp))

Expand Down Expand Up @@ -125,7 +146,9 @@ fun NewCardScreenPreview() {
setExpiredDate("00 / 00")
setOwnerName("홍길동")
setPassword("password")
}
},
onBackClick = { },
onSaveClick = { }
)
}
}
Expand All @@ -144,6 +167,8 @@ fun StatelessNewCardScreenPreview() {
setExpiredDate = { },
setOwnerName = { },
setPassword = { },
onBackClick = { },
onSaveClick = { }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import nextstep.payments.domain.PaymentCardsRepository
import nextstep.payments.ui.model.PaymentCardModel

class NewCardViewModel : ViewModel() {
class NewCardViewModel(
private val repository: PaymentCardsRepository = PaymentCardsRepository
) : ViewModel() {

private val _cardAdded = MutableStateFlow<Boolean>(false)
val cardAdded: StateFlow<Boolean> = _cardAdded.asStateFlow()

private val _cardNumber = MutableStateFlow("")
val cardNumber: StateFlow<String> = _cardNumber.asStateFlow()
Expand Down Expand Up @@ -34,4 +41,16 @@ class NewCardViewModel : ViewModel() {
fun setPassword(password: String) {
_password.value = password
}

fun addCard() {
repository.addCard(
PaymentCardModel(
cardNumber = cardNumber.value,
expiredDate = expiredDate.value,
ownerName = ownerName.value,
password = password.value
)
)
_cardAdded.value = true
}
}
Loading