From f576d74413612d1ad157f399250fe3b286f10c55 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 22:56:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?docs=20-=20=EA=B5=AC=ED=98=84=ED=95=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 52c45084..af3c89b6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,9 @@ # android-payments -## **๐Ÿš€ 4๋‹จ๊ณ„ - ํŽ˜์ด๋จผ์ธ (์นด๋“œ ์ˆ˜์ •)** +## **๐Ÿš€ 5๋‹จ๊ณ„ - ํŽ˜์ด๋จผ์ธ (๋ฆฌํŒฉํ† ๋ง)** --- ### ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๋ชฉ๋ก -- ์นด๋“œ ์ˆ˜์ • ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค - - ์นด๋“œ ๋ชฉ๋ก์—์„œ ์นด๋“œ๋ฅผ ์„ ํƒํ•˜๋ฉด ์นด๋“œ ์ˆ˜์ • ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•œ๋‹ค. - - ์นด๋“œ ์ˆ˜์ • ํ™”๋ฉด์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฉด ์ˆ˜์ •์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค - - ์นด๋“œ๊ฐ€ ์ˆ˜์ •๋˜๋ฉด ์นด๋“œ ๋ชฉ๋ก ํ™”๋ฉด์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋œ๋‹ค. -- ๊ตฌํ˜„์— ๋งž๊ฒŒ ๋ฆฌํŒฉํ† ๋ง ํ•œ๋‹ค. -- ๋ฆฌ๋ทฐ์–ด๋‹˜์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•œ๋‹ค. \ No newline at end of file +- navigation ์„ ์ด์šฉํ•˜์—ฌ ๋ฆฌํŒฉํ† ๋ง ํ•œ๋‹ค \ No newline at end of file From b9d64faeb3f99d7004d745ec7867bccddefb36d3 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 22:58:06 +0900 Subject: [PATCH 02/11] =?UTF-8?q?build=20-=20navigation-compose,=20navigat?= =?UTF-8?q?ion-test=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09a13ab7..b3aa6cae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,4 +71,7 @@ dependencies { androidTestImplementation(libs.androidx.espresso.intents) implementation(libs.kotlinx.serialization.json) + + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.navigation.testing) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eeaffe66..7ca402c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,8 @@ activityCompose = "1.10.0" composeBom = "2025.02.00" kotlinSerializationJson = "1.8.0" +navigationCompose = "2.8.8" +navigationTesting = "2.8.8" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -33,6 +35,9 @@ androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents" kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerializationJson" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navigationTesting" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From 218fd04284698f178a3c979276dbd9bf39e691bb Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:00:35 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat=20-=20InjectUtil=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/payments/util/InjectUtil.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 app/src/main/java/nextstep/payments/util/InjectUtil.kt diff --git a/app/src/main/java/nextstep/payments/util/InjectUtil.kt b/app/src/main/java/nextstep/payments/util/InjectUtil.kt new file mode 100644 index 00000000..9018866e --- /dev/null +++ b/app/src/main/java/nextstep/payments/util/InjectUtil.kt @@ -0,0 +1,51 @@ +package nextstep.payments.util + +import androidx.compose.runtime.Composable +import androidx.lifecycle.AbstractSavedStateViewModelFactory +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import nextstep.payments.data.PaymentCardsRepository +import nextstep.payments.ui.cardlist.CardListViewModel +import nextstep.payments.ui.updatecard.UpdateCardViewModel + +object InjectUtil { + + @Composable + fun createCardListViewModel() : CardListViewModel { + return viewModel(factory = createCardListViewModelFactory()) + } + + @Composable + fun createUpdateCardViewModel() : UpdateCardViewModel { + return viewModel(factory = createUpdateCardViewModelFactory()) + } + + + private fun createCardListViewModelFactory(repository: PaymentCardsRepository = PaymentCardsRepository) = + object : AbstractSavedStateViewModelFactory() { + override fun create( + key: String, + modelClass: Class, + handle: SavedStateHandle + ): T { + if (modelClass.isAssignableFrom(CardListViewModel::class.java)) { + return CardListViewModel(handle, repository) as T + } else throw IllegalArgumentException() + } + } + + private fun createUpdateCardViewModelFactory(repository: PaymentCardsRepository = PaymentCardsRepository) = + object : AbstractSavedStateViewModelFactory() { + override fun create( + key: String, + modelClass: Class, + handle: SavedStateHandle + ): T { + if (modelClass.isAssignableFrom(UpdateCardViewModel::class.java)) { + return UpdateCardViewModel(handle, repository) as T + } else throw IllegalArgumentException() + } + } + +} \ No newline at end of file From 7509722915b738558e54bf8e6dedf8df0a70e937 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:01:32 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat=20-=20Screen=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/nextstep/payments/navigation/Screen.kt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/src/main/java/nextstep/payments/navigation/Screen.kt diff --git a/app/src/main/java/nextstep/payments/navigation/Screen.kt b/app/src/main/java/nextstep/payments/navigation/Screen.kt new file mode 100644 index 00000000..39cd8c4e --- /dev/null +++ b/app/src/main/java/nextstep/payments/navigation/Screen.kt @@ -0,0 +1,3 @@ +package nextstep.payments.navigation + +interface Screen \ No newline at end of file From a9f6eb55186fffe7c17ab203b633a216d72d51e2 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:04:21 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat=20-=20CardList=20Navigation=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payments/ui/cardlist/CardListScreen.kt | 30 +++++++++++++ .../payments/ui/cardlist/CardListViewModel.kt | 14 ++++++- .../cardlist/navigation/CardListNavigation.kt | 42 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/nextstep/payments/ui/cardlist/navigation/CardListNavigation.kt diff --git a/app/src/main/java/nextstep/payments/ui/cardlist/CardListScreen.kt b/app/src/main/java/nextstep/payments/ui/cardlist/CardListScreen.kt index 526e88ac..0e148af2 100644 --- a/app/src/main/java/nextstep/payments/ui/cardlist/CardListScreen.kt +++ b/app/src/main/java/nextstep/payments/ui/cardlist/CardListScreen.kt @@ -4,8 +4,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.compose.collectAsStateWithLifecycle import nextstep.payments.designsystem.theme.PaymentsTheme import nextstep.payments.model.BankType import nextstep.payments.model.Card @@ -13,8 +16,35 @@ import nextstep.payments.ui.cardlist.component.CardListTopBar import nextstep.payments.ui.cardlist.component.EmptyCardContainer import nextstep.payments.ui.cardlist.component.ManyCardContainer import nextstep.payments.ui.cardlist.component.OneCardContainer +import nextstep.payments.util.InjectUtil import java.time.YearMonth + +@Composable +fun CardListScreen( + onRouteToNewCard: () -> Unit, + onRouteToUpdateCard: (Card) -> Unit, + cardListViewModel: CardListViewModel = InjectUtil.createCardListViewModel() +) { + + val uiState by cardListViewModel.cardListUiState.collectAsStateWithLifecycle() + + val isFetchCards by cardListViewModel.isFetchCards.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + if (isFetchCards) { + cardListViewModel.fetchCards() + } + } + + CardListScreen( + uiState = uiState, + onRouteToNewCard = onRouteToNewCard, + onRouteToUpdateCard = onRouteToUpdateCard + ) +} + + @Composable fun CardListScreen( uiState: CardListUiState, diff --git a/app/src/main/java/nextstep/payments/ui/cardlist/CardListViewModel.kt b/app/src/main/java/nextstep/payments/ui/cardlist/CardListViewModel.kt index 3ce41ee8..f8434e47 100644 --- a/app/src/main/java/nextstep/payments/ui/cardlist/CardListViewModel.kt +++ b/app/src/main/java/nextstep/payments/ui/cardlist/CardListViewModel.kt @@ -1,19 +1,29 @@ package nextstep.payments.ui.cardlist +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import nextstep.payments.data.PaymentCardsRepository +import nextstep.payments.ui.cardlist.navigation.clearIsFetchCards +import nextstep.payments.ui.cardlist.navigation.isFetchCardStateFlow + +class CardListViewModel( + private val savedStateHandle: SavedStateHandle, + private val repository: PaymentCardsRepository = PaymentCardsRepository +) : ViewModel() { + + val isFetchCards = savedStateHandle.isFetchCardStateFlow() -class CardListViewModel(private val repository: PaymentCardsRepository = PaymentCardsRepository) : - ViewModel() { private val _cardListUiState: MutableStateFlow = MutableStateFlow(CardListUiState.Empty) val cardListUiState: StateFlow = _cardListUiState.asStateFlow() fun fetchCards() { + savedStateHandle.clearIsFetchCards() + val cards = repository.cards val uiState = when (cards.size) { diff --git a/app/src/main/java/nextstep/payments/ui/cardlist/navigation/CardListNavigation.kt b/app/src/main/java/nextstep/payments/ui/cardlist/navigation/CardListNavigation.kt new file mode 100644 index 00000000..b98362cb --- /dev/null +++ b/app/src/main/java/nextstep/payments/ui/cardlist/navigation/CardListNavigation.kt @@ -0,0 +1,42 @@ +package nextstep.payments.ui.cardlist.navigation + +import androidx.lifecycle.SavedStateHandle +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable +import nextstep.payments.model.Card +import nextstep.payments.navigation.Screen +import nextstep.payments.ui.cardlist.CardListScreen + +fun NavController.routeToCardList(isFetchCards: Boolean) { + navigate(CardList(isFetchCards)) { + popUpTo(CardList(isFetchCards)) { + inclusive = true + } + } +} + +fun NavGraphBuilder.cardListNavGraph( + onRouteToNewCard: () -> Unit, + onRouteToUpdateCard: (Card) -> Unit +) { + composable { + CardListScreen( + onRouteToNewCard = onRouteToNewCard, + onRouteToUpdateCard = onRouteToUpdateCard + ) + } +} + +@Serializable +data class CardList(val isFetchCards: Boolean) : Screen + +private const val KEY_IS_FETCH_CARDS = "isFetchCards" + +fun SavedStateHandle.isFetchCardStateFlow() = + getStateFlow(KEY_IS_FETCH_CARDS, false) + +fun SavedStateHandle.clearIsFetchCards() { + this[KEY_IS_FETCH_CARDS] = false +} From 9b0c58cc933c534874b1e583711b2dd33ac683e3 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:04:49 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat=20-=20NewCard=20Navigation=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payments/ui/newcard/NewCardActivity.kt | 24 ----------------- .../newcard/navigation/NewCardNavigation.kt | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 24 deletions(-) delete mode 100644 app/src/main/java/nextstep/payments/ui/newcard/NewCardActivity.kt create mode 100644 app/src/main/java/nextstep/payments/ui/newcard/navigation/NewCardNavigation.kt diff --git a/app/src/main/java/nextstep/payments/ui/newcard/NewCardActivity.kt b/app/src/main/java/nextstep/payments/ui/newcard/NewCardActivity.kt deleted file mode 100644 index 31cc8bf7..00000000 --- a/app/src/main/java/nextstep/payments/ui/newcard/NewCardActivity.kt +++ /dev/null @@ -1,24 +0,0 @@ -package nextstep.payments.ui.newcard - -import android.content.Context -import android.content.Intent -import androidx.compose.runtime.Composable -import nextstep.payments.base.BaseComponentActivity - -class NewCardActivity : BaseComponentActivity() { - @Composable - override fun SetContent() { - NewCardScreen( - onRouteToCardList = { - setResult(RESULT_OK) - finish() - }, - onBackClick = { finish() } - ) - } - companion object { - fun newInstance(context: Context): Intent { - return Intent(context, NewCardActivity::class.java) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/nextstep/payments/ui/newcard/navigation/NewCardNavigation.kt b/app/src/main/java/nextstep/payments/ui/newcard/navigation/NewCardNavigation.kt new file mode 100644 index 00000000..fd7e6e4a --- /dev/null +++ b/app/src/main/java/nextstep/payments/ui/newcard/navigation/NewCardNavigation.kt @@ -0,0 +1,27 @@ +package nextstep.payments.ui.newcard.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable +import nextstep.payments.navigation.Screen +import nextstep.payments.ui.newcard.NewCardScreen + +fun NavController.routeToNewCard() { + navigate(NewCard) +} + +fun NavGraphBuilder.newCardNavGraph( + onBackClick: () -> Unit, + onRouteToCardList: () -> Unit, +) { + composable { + NewCardScreen( + onBackClick = onBackClick, + onRouteToCardList = onRouteToCardList + ) + } +} + +@Serializable +data object NewCard : Screen From 2ee9f443369f7366eedf07028caf3977e125b1da Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:05:30 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat=20-=20UpdateCard=20Navigation=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/updatecard/UpdateCardActivity.kt | 41 ------------------- .../ui/updatecard/UpdateCardScreen.kt | 5 ++- .../ui/updatecard/UpdateCardViewModel.kt | 29 +++---------- .../navigation/UpdateCardNavigation.kt | 28 +++++++++++++ 4 files changed, 37 insertions(+), 66 deletions(-) delete mode 100644 app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardActivity.kt create mode 100644 app/src/main/java/nextstep/payments/ui/updatecard/navigation/UpdateCardNavigation.kt diff --git a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardActivity.kt b/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardActivity.kt deleted file mode 100644 index e258b29f..00000000 --- a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardActivity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package nextstep.payments.ui.updatecard - -import android.content.Context -import android.content.Intent -import androidx.compose.runtime.Composable -import androidx.lifecycle.ViewModelProvider -import nextstep.payments.base.BaseComponentActivity -import nextstep.payments.model.Card -import nextstep.payments.util.JsonConfig - -class UpdateCardActivity : BaseComponentActivity() { - - private val updateCardViewModel by lazy { - ViewModelProvider( - owner = this, - factory = UpdateCardViewModel.createViewModelFactory() - )[UpdateCardViewModel::class] - } - - @Composable - override fun SetContent() { - UpdateCardScreen( - onBackClick = { finish() }, - onUpdate = { - setResult(RESULT_OK) - finish() - }, - viewModel = updateCardViewModel - ) - } - - companion object { - const val KEY_CARD_ITEM = "key_card_item" - - fun newInstance(context: Context, item: Card): Intent { - return Intent(context, UpdateCardActivity::class.java).apply { - putExtra(KEY_CARD_ITEM, JsonConfig.json.encodeToString(item)) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardScreen.kt b/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardScreen.kt index 05a0a096..11e94ac1 100644 --- a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardScreen.kt +++ b/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardScreen.kt @@ -7,13 +7,14 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import nextstep.payments.designsystem.component.CardInfoScreen import nextstep.payments.ui.updatecard.component.UpdateCardTopBar +import nextstep.payments.util.InjectUtil @Composable fun UpdateCardScreen( onBackClick: () -> Unit, onUpdate: () -> Unit, - viewModel: UpdateCardViewModel, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + viewModel: UpdateCardViewModel = InjectUtil.createUpdateCardViewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardViewModel.kt b/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardViewModel.kt index b424ea8d..208d94fc 100644 --- a/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardViewModel.kt +++ b/app/src/main/java/nextstep/payments/ui/updatecard/UpdateCardViewModel.kt @@ -1,25 +1,24 @@ package nextstep.payments.ui.updatecard -import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.navigation.toRoute import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import nextstep.payments.data.PaymentCardsRepository -import nextstep.payments.ext.getSerializable -import nextstep.payments.model.Card -import nextstep.payments.ui.updatecard.UpdateCardActivity.Companion.KEY_CARD_ITEM +import nextstep.payments.ui.updatecard.navigation.UpdateCard class UpdateCardViewModel( savedStateHandle: SavedStateHandle, private val paymentCardsRepository: PaymentCardsRepository = PaymentCardsRepository ) : ViewModel() { - private val getCard = requireNotNull( - savedStateHandle.getSerializable(KEY_CARD_ITEM) - ) { "Card is Null" } + private val getCard = + requireNotNull( + savedStateHandle.toRoute().decodeCard() + ) { "Card is Null" } private val _uiState = MutableStateFlow(UpdateCardState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -68,20 +67,4 @@ class UpdateCardViewModel( } } } - - companion object { - - fun createViewModelFactory(repository: PaymentCardsRepository = PaymentCardsRepository) = - object : AbstractSavedStateViewModelFactory() { - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - if (modelClass.isAssignableFrom(UpdateCardViewModel::class.java)) { - return UpdateCardViewModel(handle, repository) as T - } else throw IllegalArgumentException() - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/nextstep/payments/ui/updatecard/navigation/UpdateCardNavigation.kt b/app/src/main/java/nextstep/payments/ui/updatecard/navigation/UpdateCardNavigation.kt new file mode 100644 index 00000000..ed1c52a0 --- /dev/null +++ b/app/src/main/java/nextstep/payments/ui/updatecard/navigation/UpdateCardNavigation.kt @@ -0,0 +1,28 @@ +package nextstep.payments.ui.updatecard.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable +import nextstep.payments.model.Card +import nextstep.payments.navigation.Screen +import nextstep.payments.ui.updatecard.UpdateCardScreen +import nextstep.payments.util.JsonConfig + +fun NavController.routeToUpdateCard(card: Card) { + navigate(UpdateCard(encodeCardString = JsonConfig.json.encodeToString(card))) +} + +fun NavGraphBuilder.updateCardNavGraph( + onBackClick: () -> Unit, + onUpdate: () -> Unit, +) { + composable { + UpdateCardScreen(onBackClick = onBackClick, onUpdate = onUpdate) + } +} + +@Serializable +data class UpdateCard(val encodeCardString: String) : Screen { + fun decodeCard(): Card = JsonConfig.json.decodeFromString(encodeCardString) +} From 31e82ac23e719d336ae7f83d9dbaaa447a39fd4b Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:06:15 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat=20-=20Navigator=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/payments/navigation/Navigator.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/src/main/java/nextstep/payments/navigation/Navigator.kt diff --git a/app/src/main/java/nextstep/payments/navigation/Navigator.kt b/app/src/main/java/nextstep/payments/navigation/Navigator.kt new file mode 100644 index 00000000..44bd0ff3 --- /dev/null +++ b/app/src/main/java/nextstep/payments/navigation/Navigator.kt @@ -0,0 +1,55 @@ +package nextstep.payments.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import nextstep.payments.model.Card +import nextstep.payments.ui.cardlist.navigation.CardList +import nextstep.payments.ui.cardlist.navigation.routeToCardList +import nextstep.payments.ui.newcard.navigation.routeToNewCard +import nextstep.payments.ui.updatecard.navigation.routeToUpdateCard + + +@Composable +fun rememberNavigator( + navController: NavHostController = rememberNavController() +): Navigator = remember(navController) { + NavigatorImpl(navController) +} + +@Stable +class NavigatorImpl( + override val navController: NavHostController +) : Navigator { + override val startDestination: Screen + get() = CardList(isFetchCards = false) + + override val routeToNewCard: () -> Unit + get() = { + navController.routeToNewCard() + } + + override val routeToUpdateCard: (Card) -> Unit + get() = { card -> + navController.routeToUpdateCard(card = card) + } + override val routeToCardList: (isFetchCards: Boolean) -> Unit + get() = navController::routeToCardList +} + + +interface Navigator { + + val navController: NavHostController + + val startDestination: Screen + + val routeToNewCard: () -> Unit + + val routeToUpdateCard: (Card) -> Unit + + val routeToCardList: (isFetchCards: Boolean) -> Unit + +} \ No newline at end of file From be786474dff875a2405cad118d41a09631ce5647 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:07:24 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat=20-=20MainScreen=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/payments/ui/main/MainScreen.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/src/main/java/nextstep/payments/ui/main/MainScreen.kt diff --git a/app/src/main/java/nextstep/payments/ui/main/MainScreen.kt b/app/src/main/java/nextstep/payments/ui/main/MainScreen.kt new file mode 100644 index 00000000..281dd087 --- /dev/null +++ b/app/src/main/java/nextstep/payments/ui/main/MainScreen.kt @@ -0,0 +1,38 @@ +package nextstep.payments.ui.main + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import nextstep.payments.navigation.Navigator +import nextstep.payments.navigation.rememberNavigator +import nextstep.payments.ui.cardlist.navigation.cardListNavGraph +import nextstep.payments.ui.newcard.navigation.newCardNavGraph +import nextstep.payments.ui.updatecard.navigation.updateCardNavGraph + + +@Composable +fun MainScreen( + navigator: Navigator = rememberNavigator(), +) { + Box(modifier = Modifier.fillMaxSize()) { + NavHost( + navController = navigator.navController, + startDestination = navigator.startDestination + ) { + cardListNavGraph( + onRouteToNewCard = navigator.routeToNewCard, + onRouteToUpdateCard = navigator.routeToUpdateCard + ) + newCardNavGraph( + onBackClick = navigator.navController::popBackStack, + onRouteToCardList = { navigator.routeToCardList(true) } + ) + updateCardNavGraph( + onBackClick = navigator.navController::popBackStack, + onUpdate = { navigator.routeToCardList(true) } + ) + } + } +} \ No newline at end of file From c2ffe361c10e7ee36731c15c8e6d741efdf40025 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:08:23 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor=20-=20MainActivity=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=A0=9C=EA=B1=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 8 ----- .../java/nextstep/payments/MainActivity.kt | 33 ++----------------- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5d9436ec..40ffa6e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,14 +23,6 @@ - - - - diff --git a/app/src/main/java/nextstep/payments/MainActivity.kt b/app/src/main/java/nextstep/payments/MainActivity.kt index 383be2b2..d1c0c1cd 100644 --- a/app/src/main/java/nextstep/payments/MainActivity.kt +++ b/app/src/main/java/nextstep/payments/MainActivity.kt @@ -1,41 +1,12 @@ package nextstep.payments -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle import nextstep.payments.base.BaseComponentActivity -import nextstep.payments.ui.cardlist.CardListScreen -import nextstep.payments.ui.cardlist.CardListViewModel -import nextstep.payments.ui.newcard.NewCardActivity -import nextstep.payments.ui.updatecard.UpdateCardActivity +import nextstep.payments.ui.main.MainScreen class MainActivity : BaseComponentActivity() { - private val viewModel: CardListViewModel by viewModels() - @Composable override fun SetContent() { - val launcher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - viewModel.fetchCards() - } - } - val uiState by viewModel.cardListUiState.collectAsStateWithLifecycle() - CardListScreen( - uiState = uiState, - onRouteToNewCard = { - launcher.launch( - NewCardActivity.newInstance(context = this) - ) - }, - onRouteToUpdateCard = { card -> - launcher.launch( - UpdateCardActivity.newInstance(context = this, item = card) - ) - } - ) + MainScreen() } } From 8fe628e2030707802f7e406baa513a8c2c694073 Mon Sep 17 00:00:00 2001 From: parkduksung Date: Mon, 3 Mar 2025 23:18:07 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor=20-=20ActivityTest=20->=20Screen?= =?UTF-8?q?Test=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...outeActivityTest.kt => RouteScreenTest.kt} | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) rename app/src/androidTest/java/nextstep/payments/{RouteActivityTest.kt => RouteScreenTest.kt} (60%) diff --git a/app/src/androidTest/java/nextstep/payments/RouteActivityTest.kt b/app/src/androidTest/java/nextstep/payments/RouteScreenTest.kt similarity index 60% rename from app/src/androidTest/java/nextstep/payments/RouteActivityTest.kt rename to app/src/androidTest/java/nextstep/payments/RouteScreenTest.kt index 77eea45e..4b1b78d6 100644 --- a/app/src/androidTest/java/nextstep/payments/RouteActivityTest.kt +++ b/app/src/androidTest/java/nextstep/payments/RouteScreenTest.kt @@ -1,45 +1,62 @@ package nextstep.payments -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.lifecycle.ViewModelProvider +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.compose.ComposeNavigator +import androidx.navigation.testing.TestNavHostController import nextstep.payments.data.PaymentCardsRepository import nextstep.payments.model.BankType import nextstep.payments.model.Card +import nextstep.payments.navigation.rememberNavigator import nextstep.payments.ui.cardlist.CardListViewModel +import nextstep.payments.ui.cardlist.navigation.CardList +import nextstep.payments.ui.main.MainScreen +import nextstep.payments.ui.newcard.navigation.NewCard +import nextstep.payments.ui.updatecard.navigation.UpdateCard +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test import java.time.YearMonth -class RouteActivityTest { - +class RouteScreenTest { @get:Rule - val composeTestRule = createAndroidComposeRule() + val composeTestRule = createComposeRule() + private lateinit var navController: TestNavHostController @Before fun setUp() { PaymentCardsRepository.clear() + composeTestRule.setContent { + navController = TestNavHostController(LocalContext.current) + navController.navigatorProvider.addNavigator(ComposeNavigator()) + MainScreen(navigator = rememberNavigator(navController)) + } + } + + @Test + fun `์‹œ์ž‘๊ฒฝ๋กœ๊ฐ€_์˜ฌ๋ฐ”๋ฅด๊ฒŒ_๋‚˜์™€์•ผ_ํ•œ๋‹ค`() { + assertEquals(navController.currentDestination?.hasRoute(), true) } @Test fun `์นด๋“œ๊ฐ€_๋น„์–ด์žˆ๋Š”_๊ฒฝ์šฐ_์นด๋“œ์ถ”๊ฐ€_๋ฒ„ํŠผ์„_ํด๋ฆญํ•˜๋ฉด_์ƒˆ๋กœ์šด_์นด๋“œ๋ฅผ_์ถ”๊ฐ€ํ•˜๋Š”_ํ™”๋ฉด์œผ๋กœ_์ด๋™ํ•ด์•ผ_ํ•œ๋‹ค`() { + //when composeTestRule.onNodeWithContentDescription("add_card_icon").performClick() - composeTestRule.waitForIdle() - //then - composeTestRule.onNodeWithContentDescription("BankSelectBottomSheet").isDisplayed() + assertEquals(navController.currentDestination?.hasRoute(), true) } @Test fun `์นด๋“œ๊ฐ€_ํ•œ๊ฐœ๋งŒ_์žˆ๋Š”_๊ฒฝ์šฐ_์นด๋“œ์ถ”๊ฐ€_๋ฒ„ํŠผ์„_ํด๋ฆญํ•˜๋ฉด_์ƒˆ๋กœ์šด_์นด๋“œ๋ฅผ_์ถ”๊ฐ€ํ•˜๋Š”_ํ™”๋ฉด์œผ๋กœ_์ด๋™ํ•ด์•ผ_ํ•œ๋‹ค`() { - //given + // given val card = Card( type = BankType.BC, number = "2345234523452345", @@ -49,22 +66,19 @@ class RouteActivityTest { ) PaymentCardsRepository.upsertCard(card) - val cardListViewModel = - ViewModelProvider(composeTestRule.activity)[CardListViewModel::class.java] - - cardListViewModel.fetchCards() + navController.currentBackStackEntry?.let { + ViewModelProvider(it)[CardListViewModel::class.java].fetchCards() + } composeTestRule.waitForIdle() - //when composeTestRule.onNodeWithContentDescription("add_card_icon").performClick() - composeTestRule.waitForIdle() - //then - composeTestRule.onNodeWithContentDescription("BankSelectBottomSheet").isDisplayed() + assertEquals(navController.currentDestination?.hasRoute(), true) } + @Test fun `์นด๋“œ๊ฐ€_์—ฌ๋Ÿฌ๊ฐœ์ธ_๊ฒฝ์šฐ_์ถ”๊ฐ€_ํ…์ŠคํŠธ๋ฅผ_ํด๋ฆญํ•˜๋ฉด_์ƒˆ๋กœ์šด_์นด๋“œ๋ฅผ_์ถ”๊ฐ€ํ•˜๋Š”_ํ™”๋ฉด์œผ๋กœ_์ด๋™ํ•ด์•ผ_ํ•œ๋‹ค`() { //given @@ -86,19 +100,17 @@ class RouteActivityTest { ) card.forEach(PaymentCardsRepository::upsertCard) - val cardListViewModel = - ViewModelProvider(composeTestRule.activity)[CardListViewModel::class.java] - - cardListViewModel.fetchCards() + navController.currentBackStackEntry?.let { + ViewModelProvider(it)[CardListViewModel::class.java].fetchCards() + } composeTestRule.waitForIdle() //when composeTestRule.onNodeWithText("์ถ”๊ฐ€").performClick() - composeTestRule.waitForIdle() //then - composeTestRule.onNodeWithContentDescription("BankSelectBottomSheet").isDisplayed() + assertEquals(navController.currentDestination?.hasRoute(), true) } @Test @@ -112,24 +124,17 @@ class RouteActivityTest { password = "" ) PaymentCardsRepository.upsertCard(card) - - val cardListViewModel = - ViewModelProvider(composeTestRule.activity)[CardListViewModel::class.java] - - cardListViewModel.fetchCards() + navController.currentBackStackEntry?.let { + ViewModelProvider(it)[CardListViewModel::class.java].fetchCards() + } composeTestRule.waitForIdle() - //when composeTestRule .onNodeWithText("๋น„์”จ์นด๋“œ") .performClick() - composeTestRule.waitForIdle() - //then - composeTestRule - .onNodeWithText("์นด๋“œ ์ˆ˜์ •") - .assertIsDisplayed() + assertEquals(navController.currentDestination?.hasRoute(), true) } } \ No newline at end of file