From fc0c8f995c1f37e7940225ae9f6af758d3154f8b Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Mon, 9 Sep 2024 20:28:48 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs:=20README=20-=20step3=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20step4=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 0ed792d9..b727ef93 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,17 @@ - 카드사 목록 아이템 구현 - 카드사 선택 시 해당 카드에 맞게 카드 미리보기 변경 구현 +### STEP-3 Feedback +- 카드사 선택 테스트 코드 추가 +- BottomSheet 자연스럽게 사라지기 +- PaymentCard 프리뷰 보완 +- BankTypeUiModel nullable 개선 + +## STEP-4 +- 카드 수정 기능 구현 + - 카드 목록에서 카드를 선택하면 카드 수정 화면 + - 카드 수정 화면에서 변경사항이 발생하지 않으면 수정이 불가능하게 구현 + - 카드가 수정되면 카드 목록 화면에 변경사항이 반영 + - 카드 수정 시 상단 타이틀 UI + From 3ffda72d7115f5e5c828654b61e6dbd1adb60f07 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Mon, 9 Sep 2024 20:55:11 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20NewCardRouteScreen=20'=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=EC=82=AC=20=EC=84=A0=ED=83=9D'=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payments/ui/NewCardRouteScreenTest.kt | 24 +++++++++++++++++++ .../bottomsheet/bank/BankSelectRow.kt | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt b/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt index fff9608c..caf73d34 100644 --- a/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt +++ b/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt @@ -4,9 +4,11 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import nextstep.payments.data.model.BankType import nextstep.payments.screen.model.BankTypeUiModel import nextstep.payments.screen.newcard.NewCardRouteScreen import nextstep.payments.screen.newcard.NewCardViewModel @@ -89,5 +91,27 @@ internal class NewCardRouteScreenTest { composeTestRule.onNodeWithTag("NewCardRouteScreen") .assertDoesNotExist() } + + @Test + fun 카드사를_선택하면_바텀시트가_내려가고_카드사가_선택되어있다() { + //GIVEN + composeTestRule.setContent { + NewCardRouteScreen( + modifier = Modifier.testTag("NewCardRouteScreen"), + navigateToCardList = { }, + viewModel = viewModel + ) + } + + //WHEN + composeTestRule.onNodeWithTag(BankTypeUiModel.BC.name).performClick() + + composeTestRule.waitForIdle() + + //THEN + composeTestRule.onNodeWithTag("BankSelectBottomSheet") + .assertIsNotDisplayed() + assert(viewModel.bankType.value == BankTypeUiModel.BC) + } } diff --git a/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectRow.kt b/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectRow.kt index 4023b711..a28d7966 100644 --- a/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectRow.kt +++ b/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectRow.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -45,7 +46,7 @@ fun BankSelectRow( bankType = bankType, modifier = Modifier.wrapContentHeight().weight(1f).clickable { onClick(bankType) - } + }.testTag(bankType.name) ) } } From 92e486f05ff39b2034830ae4ec80d3fdcd5e32d0 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Mon, 9 Sep 2024 21:09:05 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20PaymentCard=20Preview=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=EC=82=AC=20=ED=83=80=EC=9E=85=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BC=80=EC=9D=B4=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/payments/component/card/PaymentCard.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt index fc93f2eb..0c601886 100644 --- a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt +++ b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt @@ -232,3 +232,13 @@ private fun Preview4() { } } +@Preview(showBackground = true, name = "PaymentCardWithoutBankType", backgroundColor = 0xFF333333) +@Composable +private fun Preview5() { + PaymentsTheme { + PaymentCard( + bankType = null + ) + } +} + From 68532edf79a45f4d4028e51a5258b517f8500238 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Mon, 9 Sep 2024 23:43:56 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20NewCard=20->=20ManageCard=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + ...enTest.kt => ManageCardRouteScreenTest.kt} | 28 +++++----- app/src/main/AndroidManifest.xml | 2 +- .../java/nextstep/payments/MainActivity.kt | 8 ++- ...ardTextField.kt => ManageCardTextField.kt} | 0 .../{NewCardTopBar.kt => ManageCardTopBar.kt} | 15 ++++- .../payments/data/model/CreditCard.kt | 6 +- .../screen/cardlist/CardListScreen.kt | 16 +++--- .../ManageCardActivity.kt} | 8 +-- .../screen/cardmanage/ManageCardEvent.kt | 5 ++ .../ManageCardScreen.kt} | 56 +++++++++++++++---- .../ManageCardViewModel.kt} | 40 +++++++------ .../payments/screen/model/ManageCardType.kt | 15 +++++ .../payments/screen/model/arg/CardArgType.kt | 16 ++++++ .../payments/screen/newcard/NewCardEvent.kt | 5 -- 15 files changed, 155 insertions(+), 66 deletions(-) rename app/src/androidTest/java/nextstep/payments/ui/{NewCardRouteScreenTest.kt => ManageCardRouteScreenTest.kt} (79%) rename app/src/main/java/nextstep/payments/component/textfield/{NewCardTextField.kt => ManageCardTextField.kt} (100%) rename app/src/main/java/nextstep/payments/component/topbar/{NewCardTopBar.kt => ManageCardTopBar.kt} (78%) rename app/src/main/java/nextstep/payments/screen/{newcard/NewCardActivity.kt => cardmanage/ManageCardActivity.kt} (68%) create mode 100644 app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardEvent.kt rename app/src/main/java/nextstep/payments/screen/{newcard/NewCardScreen.kt => cardmanage/ManageCardScreen.kt} (78%) rename app/src/main/java/nextstep/payments/screen/{newcard/NewCardViewModel.kt => cardmanage/ManageCardViewModel.kt} (68%) create mode 100644 app/src/main/java/nextstep/payments/screen/model/ManageCardType.kt create mode 100644 app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt delete mode 100644 app/src/main/java/nextstep/payments/screen/newcard/NewCardEvent.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9996218f..a229b1ba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + id("kotlin-parcelize") } android { diff --git a/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt b/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt similarity index 79% rename from app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt rename to app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt index caf73d34..58c902b5 100644 --- a/app/src/androidTest/java/nextstep/payments/ui/NewCardRouteScreenTest.kt +++ b/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt @@ -8,23 +8,25 @@ import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick -import nextstep.payments.data.model.BankType +import androidx.lifecycle.SavedStateHandle import nextstep.payments.screen.model.BankTypeUiModel -import nextstep.payments.screen.newcard.NewCardRouteScreen -import nextstep.payments.screen.newcard.NewCardViewModel +import nextstep.payments.screen.cardmanage.ManageCardRouteScreen +import nextstep.payments.screen.cardmanage.ManageCardViewModel import org.junit.Before import org.junit.Rule import org.junit.Test -internal class NewCardRouteScreenTest { +internal class ManageCardRouteScreenTest { @get:Rule val composeTestRule = createAndroidComposeRule() - private lateinit var viewModel: NewCardViewModel + private lateinit var viewModel: ManageCardViewModel @Before fun setUp(){ - viewModel = NewCardViewModel() + viewModel = ManageCardViewModel( + savedStateHandle = SavedStateHandle() + ) } @Test @@ -33,7 +35,7 @@ internal class NewCardRouteScreenTest { //GIVEN composeTestRule.setContent { - NewCardRouteScreen( + ManageCardRouteScreen( navigateToCardList = { isNavigated = true }, @@ -58,7 +60,7 @@ internal class NewCardRouteScreenTest { fun 새_카드_추가_화면_진입_시_카드사_선택_바텀_시트가_나타난다(){ //GIVEN composeTestRule.setContent { - NewCardRouteScreen( + ManageCardRouteScreen( navigateToCardList = { }, viewModel = viewModel ) @@ -73,8 +75,8 @@ internal class NewCardRouteScreenTest { fun 카드사_선택을_하지_않고_뒤로_가기_시_카드_추가_화면에서_빠져나온다() { //GIVEN composeTestRule.setContent { - NewCardRouteScreen( - modifier = Modifier.testTag("NewCardRouteScreen"), + ManageCardRouteScreen( + modifier = Modifier.testTag("ManageCardRouteScreen"), navigateToCardList = { }, viewModel = viewModel ) @@ -88,7 +90,7 @@ internal class NewCardRouteScreenTest { composeTestRule.waitForIdle() //THEN - composeTestRule.onNodeWithTag("NewCardRouteScreen") + composeTestRule.onNodeWithTag("ManageCardRouteScreen") .assertDoesNotExist() } @@ -96,8 +98,8 @@ internal class NewCardRouteScreenTest { fun 카드사를_선택하면_바텀시트가_내려가고_카드사가_선택되어있다() { //GIVEN composeTestRule.setContent { - NewCardRouteScreen( - modifier = Modifier.testTag("NewCardRouteScreen"), + ManageCardRouteScreen( + modifier = Modifier.testTag("ManageCardRouteScreen"), navigateToCardList = { }, viewModel = viewModel ) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f47641aa..0cb227f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,7 +24,7 @@ diff --git a/app/src/main/java/nextstep/payments/MainActivity.kt b/app/src/main/java/nextstep/payments/MainActivity.kt index 5d30c079..c34a3d80 100644 --- a/app/src/main/java/nextstep/payments/MainActivity.kt +++ b/app/src/main/java/nextstep/payments/MainActivity.kt @@ -9,7 +9,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import nextstep.payments.screen.cardlist.CardListScreen import nextstep.payments.screen.cardlist.CardListViewModel -import nextstep.payments.screen.newcard.NewCardActivity +import nextstep.payments.screen.cardmanage.ManageCardActivity import nextstep.payments.ui.theme.PaymentsTheme class MainActivity : ComponentActivity() { @@ -26,8 +26,10 @@ class MainActivity : ComponentActivity() { PaymentsTheme { CardListScreen( viewModel = viewModel, - navigateToNewCard = { - launcher.launch(Intent(this, NewCardActivity::class.java)) + navigateToManageCard = { + launcher.launch( + Intent(this, ManageCardActivity::class.java) + ) } ) } diff --git a/app/src/main/java/nextstep/payments/component/textfield/NewCardTextField.kt b/app/src/main/java/nextstep/payments/component/textfield/ManageCardTextField.kt similarity index 100% rename from app/src/main/java/nextstep/payments/component/textfield/NewCardTextField.kt rename to app/src/main/java/nextstep/payments/component/textfield/ManageCardTextField.kt diff --git a/app/src/main/java/nextstep/payments/component/topbar/NewCardTopBar.kt b/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt similarity index 78% rename from app/src/main/java/nextstep/payments/component/topbar/NewCardTopBar.kt rename to app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt index 51049ee7..3303195c 100644 --- a/app/src/main/java/nextstep/payments/component/topbar/NewCardTopBar.kt +++ b/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt @@ -11,17 +11,26 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import nextstep.payments.screen.model.ManageCardType @OptIn(ExperimentalMaterial3Api::class) @Composable -fun NewCardTopBar( - isAddCardEnabled : Boolean, +fun ManageCardTopBar( + manageCardType: ManageCardType, + isAddCardEnabled: Boolean, onBackClick: () -> Unit, onSaveClick: () -> Unit, modifier: Modifier = Modifier, ) { TopAppBar( - title = { Text("카드 추가") }, + title = { + Text( + text = when (manageCardType) { + ManageCardType.ADD -> "카드 추가" + ManageCardType.EDIT -> "카드 수정" + } + ) + }, navigationIcon = { IconButton(onClick = { onBackClick() }) { Icon( diff --git a/app/src/main/java/nextstep/payments/data/model/CreditCard.kt b/app/src/main/java/nextstep/payments/data/model/CreditCard.kt index 037a32f1..bce53359 100644 --- a/app/src/main/java/nextstep/payments/data/model/CreditCard.kt +++ b/app/src/main/java/nextstep/payments/data/model/CreditCard.kt @@ -1,9 +1,13 @@ package nextstep.payments.data.model +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class CreditCard( val cardNumber: String, val expiredDate: String, val ownerName: String, val password: String, val bankType: BankType -) +) : Parcelable diff --git a/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt b/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt index 9df00a73..43161040 100644 --- a/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt +++ b/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt @@ -31,14 +31,14 @@ import nextstep.payments.ui.theme.PaymentsTheme @Composable fun CardListScreen( viewModel: CardListViewModel, - navigateToNewCard: () -> Unit, + navigateToManageCard: () -> Unit, modifier: Modifier = Modifier ) { val creditCardUiState by viewModel.cardListUiState.collectAsStateWithLifecycle() CardListScreen( modifier = modifier, - navigateToNewCard = navigateToNewCard, + navigateToManageCard = navigateToManageCard, creditCardUiState = creditCardUiState ) } @@ -46,7 +46,7 @@ fun CardListScreen( @Composable fun CardListScreen( creditCardUiState: CreditCardUiState, - navigateToNewCard: () -> Unit, + navigateToManageCard: () -> Unit, modifier: Modifier = Modifier ) { Scaffold( @@ -56,7 +56,7 @@ fun CardListScreen( actions = { if(creditCardUiState is CreditCardUiState.Many){ TextButton( - onClick = navigateToNewCard + onClick = navigateToManageCard ) { Text( text = stringResource(id = R.string.card_list_add), @@ -104,7 +104,7 @@ fun CardListScreen( if (creditCardUiState !is CreditCardUiState.Many) { item { AdditionCard( - onClick = navigateToNewCard + onClick = navigateToManageCard ) } } @@ -130,7 +130,7 @@ private fun Preview1() { PaymentsTheme { CardListScreen( viewModel = CardListViewModel(), - navigateToNewCard = {} + navigateToManageCard = {} ) } } @@ -149,7 +149,7 @@ private fun Preview2() { bankType = BankType.BC ).toUiModel() ), - navigateToNewCard = {} + navigateToManageCard = {} ) } } @@ -191,7 +191,7 @@ private fun Preview3() { ) ).map { it.toUiModel() } ), - navigateToNewCard = {} + navigateToManageCard = {} ) } } diff --git a/app/src/main/java/nextstep/payments/screen/newcard/NewCardActivity.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt similarity index 68% rename from app/src/main/java/nextstep/payments/screen/newcard/NewCardActivity.kt rename to app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt index 40c6a705..9d6603a2 100644 --- a/app/src/main/java/nextstep/payments/screen/newcard/NewCardActivity.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt @@ -1,18 +1,18 @@ -package nextstep.payments.screen.newcard +package nextstep.payments.screen.cardmanage import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import nextstep.payments.ui.theme.PaymentsTheme -class NewCardActivity : ComponentActivity() { +class ManageCardActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PaymentsTheme { - NewCardRouteScreen( + ManageCardRouteScreen( navigateToCardList = { isAdded -> - if(isAdded == NewCardEvent.Success) setResult(RESULT_OK) + if(isAdded == ManageCardEvent.Success) setResult(RESULT_OK) finish() } ) diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardEvent.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardEvent.kt new file mode 100644 index 00000000..65457d17 --- /dev/null +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardEvent.kt @@ -0,0 +1,5 @@ +package nextstep.payments.screen.cardmanage + +enum class ManageCardEvent { + Pending, Success, Cancel +} diff --git a/app/src/main/java/nextstep/payments/screen/newcard/NewCardScreen.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt similarity index 78% rename from app/src/main/java/nextstep/payments/screen/newcard/NewCardScreen.kt rename to app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt index 8b8c2c86..6b452686 100644 --- a/app/src/main/java/nextstep/payments/screen/newcard/NewCardScreen.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt @@ -1,4 +1,4 @@ -package nextstep.payments.screen.newcard +package nextstep.payments.screen.cardmanage import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,24 +20,29 @@ 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 kotlinx.coroutines.flow.map import nextstep.payments.component.bottomsheet.bank.BankSelectBottomSheet import nextstep.payments.component.card.PaymentCard import nextstep.payments.component.textfield.CardNumberTextFiled import nextstep.payments.component.textfield.ExpiredDateTextFiled import nextstep.payments.component.textfield.OwnerNameTextFiled import nextstep.payments.component.textfield.PasswordTextFiled -import nextstep.payments.component.topbar.NewCardTopBar +import nextstep.payments.component.topbar.ManageCardTopBar import nextstep.payments.screen.model.BankTypeUiModel +import nextstep.payments.screen.model.ManageCardType +import nextstep.payments.screen.model.toManageCardType import nextstep.payments.ui.theme.PaymentsTheme @OptIn(ExperimentalMaterial3Api::class) @Composable -internal fun NewCardRouteScreen( +internal fun ManageCardRouteScreen( modifier: Modifier = Modifier, - navigateToCardList: (NewCardEvent) -> Unit, - viewModel: NewCardViewModel = viewModel(), + navigateToCardList: (ManageCardEvent) -> Unit, + viewModel: ManageCardViewModel = viewModel(), ) { + val manageCardType by viewModel.cardArgType + .map { it.toManageCardType() }.collectAsStateWithLifecycle(ManageCardType.ADD) val cardNumber by viewModel.cardNumber.collectAsStateWithLifecycle() val expiredDate by viewModel.expiredDate.collectAsStateWithLifecycle() val ownerName by viewModel.ownerName.collectAsStateWithLifecycle() @@ -62,7 +67,7 @@ internal fun NewCardRouteScreen( } LaunchedEffect(key1 = cardAdded) { - if (cardAdded != NewCardEvent.Pending) { + if (cardAdded != ManageCardEvent.Pending) { navigateToCardList(cardAdded) } } @@ -75,8 +80,9 @@ internal fun NewCardRouteScreen( ) } - NewCardScreen( + ManageCardScreen( modifier = modifier, + manageCardType = manageCardType, cardNumber = cardNumber, expiredDate = expiredDate, ownerName = ownerName, @@ -98,7 +104,8 @@ internal fun NewCardRouteScreen( @Composable -internal fun NewCardScreen( +internal fun ManageCardScreen( + manageCardType : ManageCardType, cardNumber: String, expiredDate: String, ownerName: String, @@ -116,7 +123,8 @@ internal fun NewCardScreen( Scaffold( topBar = { - NewCardTopBar( + ManageCardTopBar( + manageCardType = manageCardType, isAddCardEnabled = isAddCardEnabled, onBackClick = onBackClick, onSaveClick = onSaveClick @@ -167,11 +175,35 @@ internal fun NewCardScreen( } -@Preview +@Preview(showBackground = true, name = "AddCardScreenPreview") @Composable -private fun NewCardScreenPreview() { +private fun Preview1() { PaymentsTheme { - NewCardScreen( + ManageCardScreen( + manageCardType = ManageCardType.ADD, + cardNumber = "0000000000000000", + expiredDate = "1123", + ownerName = "김", + password = "1234", + bankType = BankTypeUiModel.BC, + isAddCardEnabled = true, + setCardNumber = {}, + setExpiredDate = {}, + setOwnerName = {}, + setPassword = {}, + onBackClick = {}, + onSaveClick = {} + ) + } +} + + +@Preview(showBackground = true, name = "EditCardScreenPreview") +@Composable +private fun Preview2() { + PaymentsTheme { + ManageCardScreen( + manageCardType = ManageCardType.EDIT, cardNumber = "0000000000000000", expiredDate = "1123", ownerName = "김", diff --git a/app/src/main/java/nextstep/payments/screen/newcard/NewCardViewModel.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt similarity index 68% rename from app/src/main/java/nextstep/payments/screen/newcard/NewCardViewModel.kt rename to app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt index 25232575..d0c1f6af 100644 --- a/app/src/main/java/nextstep/payments/screen/newcard/NewCardViewModel.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt @@ -1,5 +1,6 @@ -package nextstep.payments.screen.newcard +package nextstep.payments.screen.cardmanage +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow @@ -7,14 +8,21 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.stateIn import nextstep.payments.data.PaymentCardsRepository import nextstep.payments.data.model.CreditCard import nextstep.payments.screen.model.BankTypeUiModel +import nextstep.payments.screen.model.arg.CardArgType import nextstep.payments.screen.model.toEntity -class NewCardViewModel : ViewModel() { +class ManageCardViewModel( + savedStateHandle: SavedStateHandle +) : ViewModel() { + + val cardArgType = savedStateHandle.getStateFlow( + CardArgType.MANAGE_CARD_TYPE_ARG, + CardArgType.AddCardArg + ) private val _cardNumber = MutableStateFlow("") val cardNumber: StateFlow = _cardNumber.asStateFlow() @@ -29,15 +37,15 @@ class NewCardViewModel : ViewModel() { val password: StateFlow = _password.asStateFlow() private val _bankType = MutableStateFlow(null) - val bankType : StateFlow = _bankType.asStateFlow() + val bankType: StateFlow = _bankType.asStateFlow() - val isAddCardEnabled : StateFlow = + val isAddCardEnabled: StateFlow = combine( cardNumber, expiredDate, password, bankType - ){ cardNumber, expiredDate, password, bankType -> + ) { cardNumber, expiredDate, password, bankType -> cardNumber.length == 16 && expiredDate.length == 4 && password.length == 4 && bankType != null }.stateIn( scope = viewModelScope, @@ -45,16 +53,16 @@ class NewCardViewModel : ViewModel() { started = SharingStarted.WhileSubscribed(500) ) - private val _cardAdded = MutableStateFlow(NewCardEvent.Pending) - val cardAdded : StateFlow = _cardAdded.asStateFlow() + private val _cardAdded = MutableStateFlow(ManageCardEvent.Pending) + val cardAdded: StateFlow = _cardAdded.asStateFlow() fun setCardNumber(cardNumber: String) { - if(cardNumber.length > 16) return + if (cardNumber.length > 16) return _cardNumber.value = cardNumber } fun setExpiredDate(expiredDate: String) { - if(expiredDate.length > 4) return + if (expiredDate.length > 4) return _expiredDate.value = expiredDate } @@ -63,15 +71,15 @@ class NewCardViewModel : ViewModel() { } fun setPassword(password: String) { - if(password.length > 4) return + if (password.length > 4) return _password.value = password } - fun setBankType(bankTypeUiModel: BankTypeUiModel){ + fun setBankType(bankTypeUiModel: BankTypeUiModel) { _bankType.value = bankTypeUiModel } - fun addCard(){ + fun addCard() { val selectedBankType = bankType.value?.toEntity() ?: return PaymentCardsRepository.addCard( @@ -83,10 +91,10 @@ class NewCardViewModel : ViewModel() { bankType = selectedBankType ) ) - _cardAdded.value = NewCardEvent.Success + _cardAdded.value = ManageCardEvent.Success } - fun cancelToAddCard(){ - _cardAdded.value = NewCardEvent.Cancel + fun cancelToAddCard() { + _cardAdded.value = ManageCardEvent.Cancel } } diff --git a/app/src/main/java/nextstep/payments/screen/model/ManageCardType.kt b/app/src/main/java/nextstep/payments/screen/model/ManageCardType.kt new file mode 100644 index 00000000..72afee5a --- /dev/null +++ b/app/src/main/java/nextstep/payments/screen/model/ManageCardType.kt @@ -0,0 +1,15 @@ +package nextstep.payments.screen.model + +import nextstep.payments.screen.model.arg.CardArgType + +enum class ManageCardType { + ADD, + EDIT +} + +fun CardArgType.toManageCardType() : ManageCardType + = when(this){ + is CardArgType.AddCardArg -> ManageCardType.ADD + is CardArgType.EditCardArg -> ManageCardType.EDIT + } + diff --git a/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt b/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt new file mode 100644 index 00000000..43e304d8 --- /dev/null +++ b/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt @@ -0,0 +1,16 @@ +package nextstep.payments.screen.model.arg + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import nextstep.payments.data.model.CreditCard + +sealed class CardArgType : Parcelable { + @Parcelize + data object AddCardArg : CardArgType() + @Parcelize + data class EditCardArg(val creditCard: CreditCard) : CardArgType() + + companion object { + const val MANAGE_CARD_TYPE_ARG = "manageCardTypeArg" + } +} diff --git a/app/src/main/java/nextstep/payments/screen/newcard/NewCardEvent.kt b/app/src/main/java/nextstep/payments/screen/newcard/NewCardEvent.kt deleted file mode 100644 index 1a627242..00000000 --- a/app/src/main/java/nextstep/payments/screen/newcard/NewCardEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.payments.screen.newcard - -enum class NewCardEvent { - Pending, Success, Cancel -} \ No newline at end of file From 3d45fa1d9e4b160015fa2cdb69b9f03e558c4e3d Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Tue, 10 Sep 2024 00:01:09 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=A7=84=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/payments/MainActivity.kt | 14 ++++++++- .../payments/component/card/PaymentCard.kt | 11 +++++-- .../nextstep/payments/data/model/BankType.kt | 3 +- .../screen/cardlist/CardListScreen.kt | 29 ++++++++++++------- .../payments/screen/model/BankTypeUiModel.kt | 4 +-- .../screen/model/CreditCardUiModel.kt | 8 +++++ 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/nextstep/payments/MainActivity.kt b/app/src/main/java/nextstep/payments/MainActivity.kt index c34a3d80..943e63b1 100644 --- a/app/src/main/java/nextstep/payments/MainActivity.kt +++ b/app/src/main/java/nextstep/payments/MainActivity.kt @@ -10,6 +10,8 @@ import androidx.activity.viewModels import nextstep.payments.screen.cardlist.CardListScreen import nextstep.payments.screen.cardlist.CardListViewModel import nextstep.payments.screen.cardmanage.ManageCardActivity +import nextstep.payments.screen.model.arg.CardArgType +import nextstep.payments.screen.model.toModel import nextstep.payments.ui.theme.PaymentsTheme class MainActivity : ComponentActivity() { @@ -26,10 +28,20 @@ class MainActivity : ComponentActivity() { PaymentsTheme { CardListScreen( viewModel = viewModel, - navigateToManageCard = { + navigateToAddCard = { launcher.launch( Intent(this, ManageCardActivity::class.java) ) + }, + navigateToEditCard = { card -> + launcher.launch( + Intent(this, ManageCardActivity::class.java).apply { + putExtra( + CardArgType.MANAGE_CARD_TYPE_ARG, + CardArgType.EditCardArg(card.toModel()) + ) + } + ) } ) } diff --git a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt index 0c601886..f022523d 100644 --- a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt +++ b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt @@ -37,11 +37,13 @@ import nextstep.payments.ui.theme.PaymentsTheme fun PaymentCardFrame( bankType: BankTypeUiModel?, modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, content: @Composable BoxScope.() -> Unit = {} ) { CardFrame( modifier = modifier, - backgroundColor = bankType?.color ?: Color(0xFF333333) + backgroundColor = bankType?.color ?: Color(0xFF333333), + onClick = onClick ) { bankType?.let { bankType -> BankTypeRow( @@ -79,11 +81,15 @@ fun PaymentCard( @Composable fun PaymentCard( card: CreditCardUiModel, + onClick: (CreditCardUiModel) -> Unit, modifier: Modifier = Modifier ) { PaymentCardFrame( modifier = modifier, bankType = card.bankTypeUiModel, + onClick = { + onClick(card) + }, content = { Column( modifier = modifier @@ -227,7 +233,8 @@ private fun Preview4() { month = "12", year = "12", bankTypeUiModel = BankTypeUiModel.BC - ) + ), + onClick = {} ) } } diff --git a/app/src/main/java/nextstep/payments/data/model/BankType.kt b/app/src/main/java/nextstep/payments/data/model/BankType.kt index 0f3b8ad1..94fde409 100644 --- a/app/src/main/java/nextstep/payments/data/model/BankType.kt +++ b/app/src/main/java/nextstep/payments/data/model/BankType.kt @@ -8,5 +8,6 @@ enum class BankType { WOORI, LOTTE, HANA, - KB + KB, + DEFAULT } diff --git a/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt b/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt index 43161040..d25f148e 100644 --- a/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt +++ b/app/src/main/java/nextstep/payments/screen/cardlist/CardListScreen.kt @@ -25,20 +25,23 @@ import nextstep.payments.component.card.PaymentCard import nextstep.payments.component.topbar.CardListTopBar import nextstep.payments.data.model.BankType import nextstep.payments.data.model.CreditCard +import nextstep.payments.screen.model.CreditCardUiModel import nextstep.payments.screen.model.toUiModel import nextstep.payments.ui.theme.PaymentsTheme @Composable fun CardListScreen( viewModel: CardListViewModel, - navigateToManageCard: () -> Unit, + navigateToAddCard: () -> Unit, + navigateToEditCard: (CreditCardUiModel) -> Unit, modifier: Modifier = Modifier ) { val creditCardUiState by viewModel.cardListUiState.collectAsStateWithLifecycle() CardListScreen( modifier = modifier, - navigateToManageCard = navigateToManageCard, + navigateToAddCard = navigateToAddCard, + navigateToEditCard = navigateToEditCard, creditCardUiState = creditCardUiState ) } @@ -46,7 +49,8 @@ fun CardListScreen( @Composable fun CardListScreen( creditCardUiState: CreditCardUiState, - navigateToManageCard: () -> Unit, + navigateToAddCard: () -> Unit, + navigateToEditCard: (CreditCardUiModel) -> Unit, modifier: Modifier = Modifier ) { Scaffold( @@ -56,7 +60,7 @@ fun CardListScreen( actions = { if(creditCardUiState is CreditCardUiState.Many){ TextButton( - onClick = navigateToManageCard + onClick = navigateToAddCard ) { Text( text = stringResource(id = R.string.card_list_add), @@ -88,7 +92,8 @@ fun CardListScreen( is CreditCardUiState.One -> { item { PaymentCard( - card = creditCardUiState.card + card = creditCardUiState.card, + onClick = navigateToEditCard ) } } @@ -96,7 +101,8 @@ fun CardListScreen( is CreditCardUiState.Many -> { items(creditCardUiState.cards) { card -> PaymentCard( - card = card + card = card, + onClick = navigateToEditCard ) } } @@ -104,7 +110,7 @@ fun CardListScreen( if (creditCardUiState !is CreditCardUiState.Many) { item { AdditionCard( - onClick = navigateToManageCard + onClick = navigateToAddCard ) } } @@ -130,7 +136,8 @@ private fun Preview1() { PaymentsTheme { CardListScreen( viewModel = CardListViewModel(), - navigateToManageCard = {} + navigateToEditCard = {}, + navigateToAddCard = {} ) } } @@ -149,7 +156,8 @@ private fun Preview2() { bankType = BankType.BC ).toUiModel() ), - navigateToManageCard = {} + navigateToEditCard = {}, + navigateToAddCard = {} ) } } @@ -191,7 +199,8 @@ private fun Preview3() { ) ).map { it.toUiModel() } ), - navigateToManageCard = {} + navigateToEditCard = {}, + navigateToAddCard = {} ) } } diff --git a/app/src/main/java/nextstep/payments/screen/model/BankTypeUiModel.kt b/app/src/main/java/nextstep/payments/screen/model/BankTypeUiModel.kt index 8b31a79f..1260bf43 100644 --- a/app/src/main/java/nextstep/payments/screen/model/BankTypeUiModel.kt +++ b/app/src/main/java/nextstep/payments/screen/model/BankTypeUiModel.kt @@ -57,8 +57,8 @@ fun BankType.toUiModel() : BankTypeUiModel? { return BankTypeUiModel.entries.find { it.name == this.name } } -fun BankTypeUiModel.toEntity() : BankType? { - return BankType.entries.find { it.name == this.name } +fun BankTypeUiModel?.toEntity() : BankType { + return BankType.entries.find { it.name == this?.name } ?: BankType.DEFAULT } diff --git a/app/src/main/java/nextstep/payments/screen/model/CreditCardUiModel.kt b/app/src/main/java/nextstep/payments/screen/model/CreditCardUiModel.kt index 2fbecb04..3c06fa53 100644 --- a/app/src/main/java/nextstep/payments/screen/model/CreditCardUiModel.kt +++ b/app/src/main/java/nextstep/payments/screen/model/CreditCardUiModel.kt @@ -41,3 +41,11 @@ fun CreditCard.toUiModel() = bankTypeUiModel = bankType.toUiModel() ) +fun CreditCardUiModel.toModel() + = CreditCard( + cardNumber = cardNumber, + ownerName = ownerName, + password = password, + bankType = bankTypeUiModel.toEntity(), + expiredDate = month + year + ) From c44c606ee0197254517cab2347c7083ee564ba76 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Tue, 10 Sep 2024 07:06:33 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=9D=B4=20=EB=B0=9C=EC=83=9D=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9C=BC=EB=A9=B4=20=EC=88=98=EC=A0=95=EC=9D=B4=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/cardmanage/ManageCardViewModel.kt | 42 +++++++++++++++---- .../payments/screen/model/arg/CardArgType.kt | 7 ++++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt index d0c1f6af..3a55e28f 100644 --- a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt @@ -14,6 +14,7 @@ import nextstep.payments.data.model.CreditCard import nextstep.payments.screen.model.BankTypeUiModel import nextstep.payments.screen.model.arg.CardArgType import nextstep.payments.screen.model.toEntity +import nextstep.payments.screen.model.toUiModel class ManageCardViewModel( savedStateHandle: SavedStateHandle @@ -24,35 +25,62 @@ class ManageCardViewModel( CardArgType.AddCardArg ) - private val _cardNumber = MutableStateFlow("") + private val _cardNumber = MutableStateFlow(cardArgType.value.creditCardToEdit?.cardNumber ?: "") val cardNumber: StateFlow = _cardNumber.asStateFlow() - private val _expiredDate = MutableStateFlow("") + private val _expiredDate = + MutableStateFlow(cardArgType.value.creditCardToEdit?.expiredDate ?: "") val expiredDate: StateFlow = _expiredDate.asStateFlow() - private val _ownerName = MutableStateFlow("") + private val _ownerName = MutableStateFlow(cardArgType.value.creditCardToEdit?.ownerName ?: "") val ownerName: StateFlow = _ownerName.asStateFlow() - private val _password = MutableStateFlow("") + private val _password = MutableStateFlow(cardArgType.value.creditCardToEdit?.password ?: "") val password: StateFlow = _password.asStateFlow() - private val _bankType = MutableStateFlow(null) + private val _bankType = MutableStateFlow(cardArgType.value.creditCardToEdit?.bankType?.toUiModel()) val bankType: StateFlow = _bankType.asStateFlow() val isAddCardEnabled: StateFlow = combine( cardNumber, expiredDate, + ownerName, password, bankType - ) { cardNumber, expiredDate, password, bankType -> - cardNumber.length == 16 && expiredDate.length == 4 && password.length == 4 && bankType != null + ) { cardNumber, expiredDate, ownerName, password, bankType -> + cardArgType.value.creditCardToEdit?.let { creditCard -> + if(isNotCardChanged(creditCard, cardNumber, expiredDate, ownerName, password, bankType)) + return@combine false + } + isValidInputs(cardNumber, expiredDate, password, bankType) }.stateIn( scope = viewModelScope, initialValue = false, started = SharingStarted.WhileSubscribed(500) ) + private fun isNotCardChanged( + creditCard: CreditCard, + cardNumber: String, + expiredDate: String, + ownerName: String, + password: String, + bankType: BankTypeUiModel? + ) = creditCard.cardNumber == cardNumber && + creditCard.expiredDate == expiredDate && + creditCard.ownerName == ownerName && + creditCard.password == password && + creditCard.bankType.toUiModel() == bankType + + private fun isValidInputs( + cardNumber: String, + expiredDate: String, + password: String, + bankType: BankTypeUiModel? + ) = cardNumber.length == 16 && expiredDate.length == 4 && password.length == 4 && bankType != null + + private val _cardAdded = MutableStateFlow(ManageCardEvent.Pending) val cardAdded: StateFlow = _cardAdded.asStateFlow() diff --git a/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt b/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt index 43e304d8..e5404f36 100644 --- a/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt +++ b/app/src/main/java/nextstep/payments/screen/model/arg/CardArgType.kt @@ -10,6 +10,13 @@ sealed class CardArgType : Parcelable { @Parcelize data class EditCardArg(val creditCard: CreditCard) : CardArgType() + val creditCardToEdit : CreditCard? by lazy { + when(this){ + is EditCardArg -> this.creditCard + else -> null + } + } + companion object { const val MANAGE_CARD_TYPE_ARG = "manageCardTypeArg" } From 2e960e5b271e59fae7f42a46fc3cfe20112f3854 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Tue, 10 Sep 2024 07:31:15 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/topbar/ManageCardTopBar.kt | 4 +- .../payments/data/PaymentCardsRepository.kt | 12 +++++- .../screen/cardmanage/ManageCardScreen.kt | 26 ++++++------ .../screen/cardmanage/ManageCardViewModel.kt | 42 ++++++++++++------- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt b/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt index 3303195c..fdffeb30 100644 --- a/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt +++ b/app/src/main/java/nextstep/payments/component/topbar/ManageCardTopBar.kt @@ -17,7 +17,7 @@ import nextstep.payments.screen.model.ManageCardType @Composable fun ManageCardTopBar( manageCardType: ManageCardType, - isAddCardEnabled: Boolean, + isSaveCardEnabled: Boolean, onBackClick: () -> Unit, onSaveClick: () -> Unit, modifier: Modifier = Modifier, @@ -42,7 +42,7 @@ fun ManageCardTopBar( actions = { IconButton( modifier = Modifier.testTag("saveButton"), - enabled = isAddCardEnabled, + enabled = isSaveCardEnabled, onClick = { onSaveClick() } ) { Icon( diff --git a/app/src/main/java/nextstep/payments/data/PaymentCardsRepository.kt b/app/src/main/java/nextstep/payments/data/PaymentCardsRepository.kt index 724eaac3..ba91d388 100644 --- a/app/src/main/java/nextstep/payments/data/PaymentCardsRepository.kt +++ b/app/src/main/java/nextstep/payments/data/PaymentCardsRepository.kt @@ -10,4 +10,14 @@ object PaymentCardsRepository { fun addCard(creditCard: CreditCard) { _creditCards.add(creditCard) } -} \ No newline at end of file + + fun editCard( + currentCard: CreditCard, + updatedCard: CreditCard + ) { + val currentCardIndex = _creditCards.indexOf(currentCard) + if(currentCardIndex == -1) return + + _creditCards[currentCardIndex] = updatedCard + } +} diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt index 6b452686..05c359c0 100644 --- a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt @@ -48,8 +48,8 @@ internal fun ManageCardRouteScreen( val ownerName by viewModel.ownerName.collectAsStateWithLifecycle() val password by viewModel.password.collectAsStateWithLifecycle() val bankType by viewModel.bankType.collectAsStateWithLifecycle() - val cardAdded by viewModel.cardAdded.collectAsStateWithLifecycle() - val isAddCardEnabled by viewModel.isAddCardEnabled.collectAsStateWithLifecycle() + val cardChanged by viewModel.cardChanged.collectAsStateWithLifecycle() + val isSaveCardEnabled by viewModel.isSaveCardEnabled.collectAsStateWithLifecycle() val modalBottomSheetState = rememberModalBottomSheetState( confirmValueChange = { false } ) @@ -62,13 +62,13 @@ internal fun ManageCardRouteScreen( LaunchedEffect(modalBottomSheetState.targetValue) { if (modalBottomSheetState.hasExpandedState && modalBottomSheetState.targetValue == SheetValue.Hidden && bankType == null) { - viewModel.cancelToAddCard() + viewModel.cancelToChangeCard() } } - LaunchedEffect(key1 = cardAdded) { - if (cardAdded != ManageCardEvent.Pending) { - navigateToCardList(cardAdded) + LaunchedEffect(key1 = cardChanged) { + if (cardChanged != ManageCardEvent.Pending) { + navigateToCardList(cardChanged) } } @@ -88,16 +88,16 @@ internal fun ManageCardRouteScreen( ownerName = ownerName, password = password, bankType = bankType, - isAddCardEnabled = isAddCardEnabled, + isSaveCardEnabled = isSaveCardEnabled, setCardNumber = viewModel::setCardNumber, setExpiredDate = viewModel::setExpiredDate, setOwnerName = viewModel::setOwnerName, setPassword = viewModel::setPassword, onBackClick = { - viewModel.cancelToAddCard() + viewModel.cancelToChangeCard() }, onSaveClick = { - viewModel.addCard() + viewModel.saveCard() } ) } @@ -111,7 +111,7 @@ internal fun ManageCardScreen( ownerName: String, password: String, bankType: BankTypeUiModel?, - isAddCardEnabled : Boolean, + isSaveCardEnabled : Boolean, setCardNumber: (String) -> Unit, setExpiredDate: (String) -> Unit, setOwnerName: (String) -> Unit, @@ -125,7 +125,7 @@ internal fun ManageCardScreen( topBar = { ManageCardTopBar( manageCardType = manageCardType, - isAddCardEnabled = isAddCardEnabled, + isSaveCardEnabled = isSaveCardEnabled, onBackClick = onBackClick, onSaveClick = onSaveClick ) @@ -186,7 +186,7 @@ private fun Preview1() { ownerName = "김", password = "1234", bankType = BankTypeUiModel.BC, - isAddCardEnabled = true, + isSaveCardEnabled = true, setCardNumber = {}, setExpiredDate = {}, setOwnerName = {}, @@ -209,7 +209,7 @@ private fun Preview2() { ownerName = "김", password = "1234", bankType = BankTypeUiModel.BC, - isAddCardEnabled = true, + isSaveCardEnabled = true, setCardNumber = {}, setExpiredDate = {}, setOwnerName = {}, diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt index 3a55e28f..022af180 100644 --- a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardViewModel.kt @@ -41,7 +41,7 @@ class ManageCardViewModel( private val _bankType = MutableStateFlow(cardArgType.value.creditCardToEdit?.bankType?.toUiModel()) val bankType: StateFlow = _bankType.asStateFlow() - val isAddCardEnabled: StateFlow = + val isSaveCardEnabled: StateFlow = combine( cardNumber, expiredDate, @@ -81,8 +81,8 @@ class ManageCardViewModel( ) = cardNumber.length == 16 && expiredDate.length == 4 && password.length == 4 && bankType != null - private val _cardAdded = MutableStateFlow(ManageCardEvent.Pending) - val cardAdded: StateFlow = _cardAdded.asStateFlow() + private val _cardChanged = MutableStateFlow(ManageCardEvent.Pending) + val cardChanged: StateFlow = _cardChanged.asStateFlow() fun setCardNumber(cardNumber: String) { if (cardNumber.length > 16) return @@ -107,22 +107,34 @@ class ManageCardViewModel( _bankType.value = bankTypeUiModel } - fun addCard() { + fun saveCard() { val selectedBankType = bankType.value?.toEntity() ?: return - PaymentCardsRepository.addCard( - CreditCard( - cardNumber = cardNumber.value, - expiredDate = expiredDate.value, - ownerName = ownerName.value, - password = password.value, - bankType = selectedBankType - ) + val creditCard = CreditCard( + cardNumber = cardNumber.value, + expiredDate = expiredDate.value, + ownerName = ownerName.value, + password = password.value, + bankType = selectedBankType ) - _cardAdded.value = ManageCardEvent.Success + + when(cardArgType.value){ + is CardArgType.AddCardArg -> { + PaymentCardsRepository.addCard(creditCard) + } + is CardArgType.EditCardArg -> { + cardArgType.value.creditCardToEdit?.let { currentCard -> + PaymentCardsRepository.editCard(currentCard, creditCard) + } + + } + } + + _cardChanged.value = ManageCardEvent.Success } - fun cancelToAddCard() { - _cardAdded.value = ManageCardEvent.Cancel + + fun cancelToChangeCard() { + _cardChanged.value = ManageCardEvent.Cancel } } From 50c2837a4cf6ffb7d0747c6ecb6fd6d8e2fb15f7 Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Thu, 12 Sep 2024 19:20:49 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=EC=82=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/payments/ui/PaymentCardTest.kt | 10 ++-- .../bottomsheet/bank/BankSelectBottomSheet.kt | 7 ++- .../payments/component/card/PaymentCard.kt | 11 ++-- .../screen/cardmanage/ManageCardActivity.kt | 4 +- .../screen/cardmanage/ManageCardScreen.kt | 56 ++++++++++++++++--- 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/app/src/androidTest/java/nextstep/payments/ui/PaymentCardTest.kt b/app/src/androidTest/java/nextstep/payments/ui/PaymentCardTest.kt index f7b8f30d..2c7457a8 100644 --- a/app/src/androidTest/java/nextstep/payments/ui/PaymentCardTest.kt +++ b/app/src/androidTest/java/nextstep/payments/ui/PaymentCardTest.kt @@ -28,13 +28,14 @@ internal class PaymentCardTest { month = "", year = "", bankTypeUiModel = BankTypeUiModel.BC - ) + ), + onClick = {} ) } //THEN composeTestRule - .onNodeWithTag("cardNumberText") + .onNodeWithTag("cardNumberText",useUnmergedTree = true) .assertTextContains("1234 - 1234 - **** - ****") } @@ -52,13 +53,14 @@ internal class PaymentCardTest { month = "04", year = "13", bankTypeUiModel = BankTypeUiModel.BC - ) + ), + onClick = {} ) } //THEN composeTestRule - .onNodeWithTag("expiredDateText") + .onNodeWithTag(testTag = "expiredDateText", useUnmergedTree = true) .assertTextContains("04 / 13") } diff --git a/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectBottomSheet.kt b/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectBottomSheet.kt index 431ad114..a75d8335 100644 --- a/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectBottomSheet.kt +++ b/app/src/main/java/nextstep/payments/component/bottomsheet/bank/BankSelectBottomSheet.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState -import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -23,6 +22,7 @@ import nextstep.payments.ui.theme.PaymentsTheme @Composable fun BankSelectBottomSheet( onBankTypeClick : (BankTypeUiModel) -> Unit, + onDismissRequest : () -> Unit, modalBottomSheetState : SheetState, modifier: Modifier = Modifier, containerColor : Color = Color.White @@ -31,7 +31,7 @@ fun BankSelectBottomSheet( modifier = modifier, sheetState = modalBottomSheetState, containerColor = containerColor, - onDismissRequest = { }, + onDismissRequest = onDismissRequest, ) { BankSelectRow( modifier = Modifier.navigationBarsPadding(), @@ -61,7 +61,8 @@ private fun Preview1() { onBankTypeClick = { bankType -> selectedBank = bankType }, - modalBottomSheetState = modalBottomSheetState + modalBottomSheetState = modalBottomSheetState, + onDismissRequest = {} ) } } diff --git a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt index f022523d..a7353486 100644 --- a/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt +++ b/app/src/main/java/nextstep/payments/component/card/PaymentCard.kt @@ -2,7 +2,6 @@ package nextstep.payments.component.card import androidx.compose.foundation.Image 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.BoxScope @@ -20,7 +19,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource @@ -70,11 +68,13 @@ fun PaymentCardFrame( @Composable fun PaymentCard( bankType: BankTypeUiModel?, - modifier: Modifier = Modifier + onClick : () -> Unit, + modifier: Modifier = Modifier, ) { PaymentCardFrame( bankType = bankType, - modifier = modifier + modifier = modifier, + onClick = onClick ) } @@ -244,7 +244,8 @@ private fun Preview4() { private fun Preview5() { PaymentsTheme { PaymentCard( - bankType = null + bankType = null, + onClick = {}, ) } } diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt index 9d6603a2..4117696a 100644 --- a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardActivity.kt @@ -11,8 +11,8 @@ class ManageCardActivity : ComponentActivity() { setContent { PaymentsTheme { ManageCardRouteScreen( - navigateToCardList = { isAdded -> - if(isAdded == ManageCardEvent.Success) setResult(RESULT_OK) + navigateToCardList = { isChanged -> + if(isChanged == ManageCardEvent.Success) setResult(RESULT_OK) finish() } ) diff --git a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt index 05c359c0..e40a19a6 100644 --- a/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt +++ b/app/src/main/java/nextstep/payments/screen/cardmanage/ManageCardScreen.kt @@ -13,6 +13,9 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -21,6 +24,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import nextstep.payments.component.bottomsheet.bank.BankSelectBottomSheet import nextstep.payments.component.card.PaymentCard import nextstep.payments.component.textfield.CardNumberTextFiled @@ -51,18 +55,27 @@ internal fun ManageCardRouteScreen( val cardChanged by viewModel.cardChanged.collectAsStateWithLifecycle() val isSaveCardEnabled by viewModel.isSaveCardEnabled.collectAsStateWithLifecycle() val modalBottomSheetState = rememberModalBottomSheetState( - confirmValueChange = { false } + confirmValueChange = { + bankType != null + } ) + var shouldHideBottomSheet by remember { mutableStateOf(false) } + var isShownBottomSheet by remember { mutableStateOf(true) } - LaunchedEffect(key1 = bankType) { - if (bankType != null) { - modalBottomSheetState.hide() + LaunchedEffect(key1 = cardChanged) { + if (cardChanged != ManageCardEvent.Pending) { + navigateToCardList(cardChanged) } } LaunchedEffect(modalBottomSheetState.targetValue) { - if (modalBottomSheetState.hasExpandedState && modalBottomSheetState.targetValue == SheetValue.Hidden && bankType == null) { - viewModel.cancelToChangeCard() + if (modalBottomSheetState.hasExpandedState && modalBottomSheetState.targetValue == SheetValue.Hidden) { + if( bankType == null) { + viewModel.cancelToChangeCard() + } + else { + shouldHideBottomSheet = true + } } } @@ -72,10 +85,30 @@ internal fun ManageCardRouteScreen( } } - if (bankType == null) { + LaunchedEffect(key1 = bankType) { + if (bankType != null) { + shouldHideBottomSheet = true + } + } + + LaunchedEffect(shouldHideBottomSheet) { + if(shouldHideBottomSheet){ + launch { + modalBottomSheetState.hide() + }.invokeOnCompletion { + isShownBottomSheet = false + shouldHideBottomSheet = false + } + } + } + + if (isShownBottomSheet) { BankSelectBottomSheet( onBankTypeClick = viewModel::setBankType, modalBottomSheetState = modalBottomSheetState, + onDismissRequest = { + shouldHideBottomSheet = true + }, modifier = Modifier.testTag("BankSelectBottomSheet") ) } @@ -93,6 +126,9 @@ internal fun ManageCardRouteScreen( setExpiredDate = viewModel::setExpiredDate, setOwnerName = viewModel::setOwnerName, setPassword = viewModel::setPassword, + onCardClick = { + isShownBottomSheet = true + }, onBackClick = { viewModel.cancelToChangeCard() }, @@ -116,6 +152,7 @@ internal fun ManageCardScreen( setExpiredDate: (String) -> Unit, setOwnerName: (String) -> Unit, setPassword: (String) -> Unit, + onCardClick : () -> Unit, onBackClick: () -> Unit, onSaveClick: () -> Unit, modifier: Modifier = Modifier @@ -142,7 +179,8 @@ internal fun ManageCardScreen( Spacer(modifier = Modifier.height(14.dp)) PaymentCard( - bankType = bankType + bankType = bankType, + onClick = onCardClick ) Spacer(modifier = Modifier.height(10.dp)) @@ -191,6 +229,7 @@ private fun Preview1() { setExpiredDate = {}, setOwnerName = {}, setPassword = {}, + onCardClick = {}, onBackClick = {}, onSaveClick = {} ) @@ -214,6 +253,7 @@ private fun Preview2() { setExpiredDate = {}, setOwnerName = {}, setPassword = {}, + onCardClick = {}, onBackClick = {}, onSaveClick = {} ) From b411353b7efb798bc7be12a810a62c7dad9014da Mon Sep 17 00:00:00 2001 From: eshc123 <> Date: Thu, 12 Sep 2024 22:24:02 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payments/ui/ManageCardRouteScreenTest.kt | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt b/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt index 58c902b5..6aa89942 100644 --- a/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt +++ b/app/src/androidTest/java/nextstep/payments/ui/ManageCardRouteScreenTest.kt @@ -4,14 +4,19 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.lifecycle.SavedStateHandle +import nextstep.payments.data.model.BankType +import nextstep.payments.data.model.CreditCard import nextstep.payments.screen.model.BankTypeUiModel import nextstep.payments.screen.cardmanage.ManageCardRouteScreen import nextstep.payments.screen.cardmanage.ManageCardViewModel +import nextstep.payments.screen.model.arg.CardArgType import org.junit.Before import org.junit.Rule import org.junit.Test @@ -23,7 +28,7 @@ internal class ManageCardRouteScreenTest { private lateinit var viewModel: ManageCardViewModel @Before - fun setUp(){ + fun setUp() { viewModel = ManageCardViewModel( savedStateHandle = SavedStateHandle() ) @@ -37,7 +42,7 @@ internal class ManageCardRouteScreenTest { composeTestRule.setContent { ManageCardRouteScreen( navigateToCardList = { - isNavigated = true + isNavigated = true }, viewModel = viewModel ) @@ -57,7 +62,7 @@ internal class ManageCardRouteScreenTest { } @Test - fun 새_카드_추가_화면_진입_시_카드사_선택_바텀_시트가_나타난다(){ + fun 새_카드_추가_화면_진입_시_카드사_선택_바텀_시트가_나타난다() { //GIVEN composeTestRule.setContent { ManageCardRouteScreen( @@ -115,5 +120,70 @@ internal class ManageCardRouteScreenTest { .assertIsNotDisplayed() assert(viewModel.bankType.value == BankTypeUiModel.BC) } + + @Test + fun 카드_수정_화면에서_변경사항이_발생하지_않으면_저장_버튼을_클릭할_수_없다() { + //GIVEN + viewModel = ManageCardViewModel( + savedStateHandle = SavedStateHandle().apply { + set(CardArgType.MANAGE_CARD_TYPE_ARG, + CardArgType.EditCardArg( + CreditCard( + cardNumber = "1234123412341234", + expiredDate = "1122", + ownerName = "김컴포즈", + password = "1234", + bankType = BankType.BC + ) + ) + ) + } + ) + composeTestRule.setContent { + ManageCardRouteScreen( + modifier = Modifier.testTag("ManageCardRouteScreen"), + navigateToCardList = { }, + viewModel = viewModel + ) + } + + //THEN + composeTestRule.onNodeWithTag("saveButton").assertIsNotEnabled() + } + + @Test + fun 카드_수정_화면에서_변경사항이_발생하면_저장_버튼을_클릭할_수_있다() { + //GIVEN + viewModel = ManageCardViewModel( + savedStateHandle = SavedStateHandle().apply { + set(CardArgType.MANAGE_CARD_TYPE_ARG, + CardArgType.EditCardArg( + CreditCard( + cardNumber = "1234123412341234", + expiredDate = "1122", + ownerName = "김컴포즈", + password = "1234", + bankType = BankType.BC + ) + ) + ) + } + ) + composeTestRule.setContent { + ManageCardRouteScreen( + modifier = Modifier.testTag("ManageCardRouteScreen"), + navigateToCardList = { }, + viewModel = viewModel + ) + } + + //WHEN + viewModel.setBankType(BankTypeUiModel.HYUNDAI) + + composeTestRule.waitForIdle() + + //THEN + composeTestRule.onNodeWithTag("saveButton").assertIsEnabled() + } }