Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
- ์นด๋“œ ์ถ”๊ฐ€ ํ™”๋ฉด
- [x] ์ ‘์†์‹œ ์นด๋“œ์‚ฌ ์„ ํƒ bottom sheet ๋ณด์—ฌ์ฃผ๊ธฐ
- [x] ์นด๋“œ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ์— ์นด๋“œ์‚ฌ ์ •๋ณด ํ‘œ์‹œํ•˜๊ธฐ

## Step4

- [x] ์นด๋“œ ๋ชฉ๋ก์—์„œ ์นด๋“œ๋ฅผ ์„ ํƒํ•˜๋ฉด ์นด๋“œ ์ˆ˜์ • ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•œ๋‹ค.
- [x] ์นด๋“œ ์ˆ˜์ • ํ™”๋ฉด์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฉด ์ˆ˜์ •์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
- [x] ์นด๋“œ๊ฐ€ ์ˆ˜์ •๋˜๋ฉด ์นด๋“œ ๋ชฉ๋ก ํ™”๋ฉด์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋œ๋‹ค.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.parcelize)
}

android {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class CardListScreenTest {
cards = CreditCardUiState.Empty
),
navigateToNewCard = {},
navigateToEditCard = {},
)
}

Expand All @@ -41,6 +42,7 @@ class CardListScreenTest {
)
),
navigateToNewCard = {},
navigateToEditCard = {},
)
}

Expand Down Expand Up @@ -70,6 +72,7 @@ class CardListScreenTest {
)
),
navigateToNewCard = {},
navigateToEditCard = {},
)
}

Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/nextstep/payments/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.activity.viewModels
import nextstep.payments.ui.card_list.CardListScreenRoot
import nextstep.payments.ui.card_list.CardListViewModel
import nextstep.payments.ui.new_card.NewCardActivity
import nextstep.payments.ui.new_card.NewCardViewModel
import nextstep.payments.ui.theme.PaymentsTheme

class MainActivity : ComponentActivity() {
Expand All @@ -33,7 +34,13 @@ class MainActivity : ComponentActivity() {
navigateToNewCard = {
val intent = Intent(this, NewCardActivity::class.java)
launcher.launch(intent)
}
},
navigateToEditCard = {
val intent = Intent(this, NewCardActivity::class.java).apply {
putExtra(NewCardViewModel.CARD_INFO_KEY, it)
}
launcher.launch(intent)
},
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/nextstep/payments/data/card/CardEntity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package nextstep.payments.data.card

import java.util.UUID

data class CardEntity(
val id: UUID = UUID.randomUUID(),
val cardNumber: String = "",
val expiredDate: String = "",
val ownerName: String = "",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
package nextstep.payments.data.card

import java.util.UUID

object PaymentCardRepository {

private val _cards = mutableListOf<CardEntity>()
private val cardById = linkedMapOf<UUID, CardEntity>()
val cards: List<CardEntity>
get() = _cards.toList()
get() = cardById.values.toList()

private val idByCardNumber = hashMapOf<String, UUID>()

fun addCard(cardEntity: CardEntity): Boolean {
/**
* ์ „๋‹ฌ๋œ uuid๊ฐ€ ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋ฉด ์‚ฌ์šฉ ์ค‘์ด์ง€ ์•Š์€ uuid๋กœ ๋ณ€๊ฒฝํ•ด์„œ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
*/
fun addCard(card: CardEntity): Boolean {
// ์นด๋“œ๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ์ „์— ์ด๋ฏธ ๋“ฑ๋ก๋œ ์นด๋“œ ๋ฒˆํ˜ธ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
// LazyList์—์„œ key๋ฅผ ์นด๋“œ๋ฒˆํ˜ธ๋กœ ์„ค์ •ํ•ด๋’€๋Š”๋ฐ, ์ค‘๋ณต๋œ key๋ฅผ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
if (_cards.any { it.cardNumber == cardEntity.cardNumber }) {
if (card.cardNumber in idByCardNumber) {
return false
}
_cards.add(cardEntity)
var id = card.id
while (id in cardById) {
id = UUID.randomUUID()
}

cardById[id] = card.copy(id = id)
idByCardNumber[card.cardNumber] = id
return true
}

fun editCard(id: UUID, newCard: CardEntity): Boolean {
val oldCard = cardById[id] ?: return false

if (newCard.cardNumber != oldCard.cardNumber) {
if (newCard.cardNumber in idByCardNumber) {
return false
}
idByCardNumber.remove(oldCard.cardNumber)
idByCardNumber[newCard.cardNumber] = id
}
cardById[id] = newCard.copy(id = id)
return true
}

fun getPassword(id: UUID): String {
return cardById[id]?.password ?: ""
}

}
14 changes: 14 additions & 0 deletions app/src/main/java/nextstep/payments/ui/card_list/CardListScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ import nextstep.payments.ui.theme.PaymentsTheme
fun CardListScreenRoot(
viewModel: CardListViewModel,
navigateToNewCard: () -> Unit,
navigateToEditCard: (CreditCard) -> Unit,
modifier: Modifier = Modifier,
) {
val state = viewModel.state.collectAsStateWithLifecycle()

CardListScreen(
state = state.value,
navigateToNewCard = navigateToNewCard,
navigateToEditCard = navigateToEditCard,
modifier = modifier,
)
}
Expand All @@ -56,6 +58,7 @@ fun CardListScreenRoot(
internal fun CardListScreen(
state: CardListState,
navigateToNewCard: () -> Unit,
navigateToEditCard: (CreditCard) -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
Expand Down Expand Up @@ -110,6 +113,7 @@ internal fun CardListScreen(
is CreditCardUiState.Many -> {
ManyCardScreen(
state = cards,
navigateToEditCard = navigateToEditCard,
modifier = Modifier.padding(innerPadding),
)
}
Expand All @@ -118,6 +122,7 @@ internal fun CardListScreen(
OneCardScreen(
state = cards,
newCardAddContent = paymentCardAdd,
navigateToEditCard = navigateToEditCard,
modifier = Modifier.padding(innerPadding),
)
}
Expand Down Expand Up @@ -152,6 +157,7 @@ private fun EmptyCardScreen(
@Composable
private fun ManyCardScreen(
state: CreditCardUiState.Many,
navigateToEditCard: (CreditCard) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
Expand All @@ -167,6 +173,9 @@ private fun ManyCardScreen(
}
) {
PaymentCard(
modifier = Modifier.clickable {
navigateToEditCard(it)
},
cardInfo = it,
)
}
Expand All @@ -177,6 +186,7 @@ private fun ManyCardScreen(
fun OneCardScreen(
state: CreditCardUiState.One,
newCardAddContent: @Composable () -> Unit,
navigateToEditCard: (CreditCard) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Expand All @@ -185,6 +195,9 @@ fun OneCardScreen(
.fillMaxSize(),
) {
PaymentCard(
modifier = Modifier.clickable {
navigateToEditCard(state.creditCard)
},
cardInfo = state.creditCard
)
Spacer(modifier = Modifier.padding(bottom = 32.dp))
Expand Down Expand Up @@ -240,6 +253,7 @@ private fun CardListScreenPreview(
CardListScreen(
state = state,
navigateToNewCard = {},
navigateToEditCard = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package nextstep.payments.ui.common.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import nextstep.payments.ui.new_card.CardType
import java.util.UUID
import kotlin.text.filter

/**
* ์ด ํด๋ž˜์Šค๊ฐ€ common/model ํ•˜์œ„์— ์žˆ๋Š”๋ฐ, ๋” ์ ์ ˆํ•œ ์œ„์น˜๊ฐ€ ์–ด๋””์ผ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.
*/
@Parcelize
data class CreditCard(
val id: UUID = UUID.randomUUID(),
val cardNumber: String = "",
val expiredDate: String = "",
val ownerName: String = "",
val company: CardType = CardType.NOT_SELECTED,
) {
): Parcelable {
fun formatExpiredDate(): String {
val groups = expiredDate.filter { it.isDigit() }.chunked(2)
return groups.joinToString(" / ")
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/nextstep/payments/ui/mapper/CardMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import nextstep.payments.ui.new_card.CardType

fun CardEntity.toUi(): CreditCard =
CreditCard(
id = id,
cardNumber = cardNumber,
expiredDate = expiredDate,
ownerName = ownerName,
Expand Down Expand Up @@ -40,3 +41,4 @@ fun CardTypeEntity.toUi(): CardType {
CardTypeEntity.KB -> CardType.KB
}
}

5 changes: 4 additions & 1 deletion app/src/main/java/nextstep/payments/ui/new_card/CardType.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package nextstep.payments.ui.new_card

import android.os.Parcelable
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import kotlinx.parcelize.Parcelize
import nextstep.payments.R

@Parcelize
enum class CardType(
val companyName: String,
@param:DrawableRes val imageResource: Int,
@param:ColorInt val color: Int,
) {
): Parcelable {
NOT_SELECTED("", 0, 0xFF333333.toInt()),
BC("BC์นด๋“œ", R.drawable.bc, 0xFFF04651.toInt()),
SHINHAN("์‹ ํ•œ์นด๋“œ", R.drawable.shinhan, 0xFF0046FF.toInt()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ sealed interface NewCardEvent {

data object CardAddSuccess : NewCardEvent
data object CardAddFail : NewCardEvent
data object CardEditSuccess : NewCardEvent
data object CardEditFail : NewCardEvent
data object NavigateBack : NewCardEvent
}
24 changes: 20 additions & 4 deletions app/src/main/java/nextstep/payments/ui/new_card/NewCardScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fun NewCardScreenRoot(

ObserveAsEvents(viewModel.events) { event ->
when (event) {
NewCardEvent.CardAddSuccess -> {
NewCardEvent.CardAddSuccess, NewCardEvent.CardEditSuccess, NewCardEvent.NavigateBack -> {
navigateToCardList()
}

Expand All @@ -73,22 +73,35 @@ fun NewCardScreenRoot(
).show()
}

NewCardEvent.NavigateBack -> {
navigateToCardList()
NewCardEvent.CardEditFail -> {
Toast.makeText(
context,
context.getString(R.string.card_list_edit_card_fail),
Toast.LENGTH_SHORT
).show()
}
}
}

val isAddEnabled by remember {
derivedStateOf {
state.isValid(CardInputValidator)
!state.isCardValueSame(viewModel.originalState) && state.isValid(CardInputValidator)
}
}

val topBarTitle = remember {
if (viewModel.originalState == NewCardState.EMPTY) {
context.getString(R.string.new_card_top_bar_title)
} else {
context.getString(R.string.edit_card_top_bar_title)
}
}

NewCardScreen(
state = state,
isAddEnabled = isAddEnabled,
onAction = viewModel::onAction,
topBarTitle = topBarTitle,
modifier = modifier,
)
}
Expand All @@ -98,6 +111,7 @@ internal fun NewCardScreen(
state: NewCardState,
isAddEnabled: Boolean,
onAction: (NewCardAction) -> Unit,
topBarTitle: String,
modifier: Modifier = Modifier,
) {
val cardNumberTransformation = remember {
Expand All @@ -116,6 +130,7 @@ internal fun NewCardScreen(
onSaveClick = {
onAction(NewCardAction.OnAddCardClick)
},
title = topBarTitle,
isAddEnabled = isAddEnabled,
)
},
Expand Down Expand Up @@ -291,6 +306,7 @@ private fun NewCardScreenPreview() {
showBottomSheet = false,
),
isAddEnabled = true,
topBarTitle = "์นด๋“œ ์ถ”๊ฐ€",
onAction = { },
)
}
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/nextstep/payments/ui/new_card/NewCardState.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
package nextstep.payments.ui.new_card

import java.util.UUID

data class NewCardState(
val cardNumber: String = "",
val expiredDate: String = "",
val ownerName: String = "",
val password: String = "",
val showBottomSheet: Boolean = true,
val cardType: CardType = CardType.NOT_SELECTED,
val id: UUID = UUID.randomUUID(),
) {
fun isValid(cardInputValidator: CardInputValidator): Boolean {
return cardInputValidator.isCardNumberValid(cardNumber) &&
cardInputValidator.isExpiredDateValid(expiredDate) &&
cardInputValidator.isCardOwnerNameValid(ownerName) &&
cardInputValidator.isPasswordValid(password)
}

fun isCardValueSame(other: NewCardState): Boolean {
return cardNumber == other.cardNumber && expiredDate == other.expiredDate
&& ownerName == other.ownerName && password == other.password
&& cardType == other.cardType
}

companion object {
val EMPTY = NewCardState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ fun NewCardTopBar(
onBackClick: () -> Unit,
onSaveClick: () -> Unit,
isAddEnabled: Boolean,
title: String,
modifier: Modifier = Modifier,
) {
TopAppBar(
title = { Text("์นด๋“œ ์ถ”๊ฐ€") },
title = { Text(title) },
navigationIcon = {
IconButton(onClick = { onBackClick() }) {
Icon(
Expand Down
Loading