From 90b90e076e290ee7743baab7464263a153a30f79 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sat, 4 Jan 2025 17:26:41 +0900 Subject: [PATCH 01/26] =?UTF-8?q?[PC-256]=20RegistrationScreen=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../graph/registration/RegistrationScreen.kt | 65 ++++++++++++++++++- .../registration/RegistrationViewModel.kt | 37 +++++++++++ .../contract/RegistrationIntent.kt | 6 +- .../java/com/puzzle/presentation/ui/App.kt | 3 +- 4 files changed, 108 insertions(+), 3 deletions(-) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index 6226dfc6..c218e496 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -1,11 +1,27 @@ package com.puzzle.auth.graph.registration import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel +import com.puzzle.auth.graph.registration.contract.RegistrationIntent import com.puzzle.auth.graph.registration.contract.RegistrationState +import com.puzzle.designsystem.component.PieceSolidButton +import com.puzzle.designsystem.component.PieceSubBackTopBar +import com.puzzle.designsystem.foundation.PieceTheme +import com.puzzle.navigation.NavigationEvent @Composable internal fun RegistrationRoute( @@ -15,14 +31,61 @@ internal fun RegistrationRoute( RegistrationScreen( state = state, + navigate = { event -> viewModel.onIntent(RegistrationIntent.Navigate(event)) } ) } @Composable private fun RegistrationScreen( state: RegistrationState, + navigate: (NavigationEvent) -> Unit, ) { - Column() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + ) { + PieceSubBackTopBar( + title = "", + onBackClick = { navigate(NavigationEvent.NavigateUp) }, + modifier = Modifier.height(60.dp), + ) + Text( + text = buildAnnotatedString { + append("Piece의\n") + + withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) { + append("이용약관") + } + + append("을 확인해 주세요") + }, + style = PieceTheme.typography.headingLSB, + color = PieceTheme.colors.black, + modifier = Modifier.padding(top = 20.dp), + ) + + Spacer( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) + + + + Spacer( + modifier = Modifier + .fillMaxWidth() + .weight(2f) + ) + + PieceSolidButton( + label = "다음", + onClick = {}, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp), + ) } } \ No newline at end of file diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index 2bae953a..e6d28fba 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -4,17 +4,54 @@ import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.hilt.AssistedViewModelFactory import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory +import com.puzzle.auth.graph.login.contract.LoginIntent +import com.puzzle.auth.graph.login.contract.LoginSideEffect +import com.puzzle.auth.graph.registration.contract.RegistrationIntent +import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationState import com.puzzle.navigation.NavigationHelper import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.BUFFERED +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch class RegistrationViewModel @AssistedInject constructor( @Assisted initialState: RegistrationState, private val navigationHelper: NavigationHelper, ) : MavericksViewModel(initialState) { + private val intents = Channel(BUFFERED) + + private val _sideEffect = Channel(BUFFERED) + val sideEffect = _sideEffect.receiveAsFlow() + + init { + intents.receiveAsFlow() + .onEach(::processIntent) + .launchIn(viewModelScope) + } + + internal fun onIntent(intent: RegistrationIntent) = viewModelScope.launch { + intents.send(intent) + } + + private fun processIntent(intent: RegistrationIntent) { + when (intent) { + is RegistrationIntent.Navigate -> navigationHelper.navigate(intent.navigationEvent) + } + } + + private fun handleSideEffect(sideEffect: RegistrationSideEffect) { + when (sideEffect) { + else -> Unit + } + } + @AssistedFactory interface Factory : AssistedViewModelFactory { override fun create(state: RegistrationState): RegistrationViewModel diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt index 72a182d3..5645530e 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt @@ -1,3 +1,7 @@ package com.puzzle.auth.graph.registration.contract -sealed class RegistrationIntent \ No newline at end of file +import com.puzzle.navigation.NavigationEvent + +sealed class RegistrationIntent { + data class Navigate(val navigationEvent: NavigationEvent) : RegistrationIntent() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt index 001275e2..caccf04c 100644 --- a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt +++ b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt @@ -34,6 +34,7 @@ import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import com.puzzle.common.ui.NoRippleInteractionSource +import com.puzzle.designsystem.R import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.navigation.AuthGraph import com.puzzle.navigation.MatchingGraph @@ -41,7 +42,6 @@ import com.puzzle.navigation.MatchingGraphDest.MatchingDetailRoute import com.puzzle.navigation.MyPageRoute import com.puzzle.navigation.Route import com.puzzle.navigation.SettingRoute -import com.puzzle.designsystem.R import com.puzzle.presentation.navigation.AppNavHost import com.puzzle.presentation.navigation.TopLevelDestination import kotlin.reflect.KClass @@ -59,6 +59,7 @@ fun App( Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, + containerColor = PieceTheme.colors.white, bottomBar = { if (currentDestination?.shouldHideBottomNavigation() == false) { AppBottomBar( From 60bb46cd504f38db9a9713f92f129bb2e70272e8 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sat, 4 Jan 2025 18:28:41 +0900 Subject: [PATCH 02/26] =?UTF-8?q?[PC-256]=20Check=20Component=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 --- .../puzzle/designsystem/component/Check.kt | 138 ++++++++++++++++++ .../designsystem/foundation/Typography.kt | 1 + .../src/main/res/drawable/ic_arrow_right.xml | 13 ++ .../src/main/res/drawable/ic_check.xml | 10 ++ 4 files changed, 162 insertions(+) create mode 100644 core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt create mode 100644 core/designsystem/src/main/res/drawable/ic_arrow_right.xml create mode 100644 core/designsystem/src/main/res/drawable/ic_check.xml diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt new file mode 100644 index 00000000..7bdc035b --- /dev/null +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt @@ -0,0 +1,138 @@ +package com.puzzle.designsystem.component + +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.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.puzzle.designsystem.R +import com.puzzle.designsystem.foundation.PieceTheme + +@Composable +fun PieceCheck( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + Image( + painter = painterResource(R.drawable.ic_check), + contentDescription = "", + colorFilter = ColorFilter.tint( + color = if (checked) PieceTheme.colors.primaryDefault + else PieceTheme.colors.light1, + ), + modifier = modifier.clickable { + onCheckedChange(!checked) + }, + ) +} + +@Composable +fun PieceCheckList( + checked: Boolean, + label: String, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + showArrow: Boolean = false, + containerColor: Color = PieceTheme.colors.white, + onArrowClick: () -> Unit = {}, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .height(52.dp) + .clip(RoundedCornerShape(8.dp)) + .background(containerColor), + ) { + PieceCheck( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = Modifier.padding(start = 14.dp, end = 12.dp), + ) + + Text( + text = label, + style = PieceTheme.typography.bodyMR, + color = PieceTheme.colors.black, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.weight(1f), + ) + + if (showArrow) { + Image( + painter = painterResource(R.drawable.ic_arrow_right), + contentDescription = "", + modifier = Modifier + .padding(end = 14.dp) + .size(24.dp) + .clickable { onArrowClick() }, + ) + } + } +} + +@Preview +@Composable +fun PreviewPieceCheck() { + PieceTheme { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + PieceCheck( + checked = true, + onCheckedChange = {}, + ) + + PieceCheck( + checked = false, + onCheckedChange = {}, + ) + } + } +} + +@Preview +@Composable +fun PreviewPieceCheckList() { + PieceTheme { + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier + .background(PieceTheme.colors.black) + .padding(20.dp), + ) { + PieceCheckList( + checked = true, + label = "약관 전체 동의", + onCheckedChange = {}, + containerColor = PieceTheme.colors.light3, + modifier = Modifier.fillMaxWidth(), + ) + + PieceCheckList( + checked = false, + showArrow = true, + label = "약관 전체 동의", + onCheckedChange = {}, + onArrowClick = {}, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt index 2fd62025..0c1b3724 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt @@ -68,6 +68,7 @@ data class PieceTypography( ), val bodyMR: TextStyle = TextStyle( fontFamily = PretendardMedium, + fontWeight = FontWeight.W400, fontSize = 16.sp, lineHeight = 24.sp, ), diff --git a/core/designsystem/src/main/res/drawable/ic_arrow_right.xml b/core/designsystem/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 00000000..c82291c8 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,13 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_check.xml b/core/designsystem/src/main/res/drawable/ic_check.xml new file mode 100644 index 00000000..29c39a1d --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_check.xml @@ -0,0 +1,10 @@ + + + From f9064765ce3418e39a1b0366b7491dc802f6dcff Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sat, 4 Jan 2025 18:40:23 +0900 Subject: [PATCH 03/26] =?UTF-8?q?[PC-256]=20RegistrationScreen=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../puzzle/designsystem/component/Check.kt | 8 +++-- .../designsystem/foundation/Typography.kt | 10 ++++-- .../graph/registration/RegistrationScreen.kt | 31 +++++++++++++++++-- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt index 7bdc035b..094a36ea 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt @@ -38,9 +38,11 @@ fun PieceCheck( color = if (checked) PieceTheme.colors.primaryDefault else PieceTheme.colors.light1, ), - modifier = modifier.clickable { - onCheckedChange(!checked) - }, + modifier = modifier + .size(20.dp) + .clickable { + onCheckedChange(!checked) + }, ) } diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt index 0c1b3724..ff976358 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt @@ -29,6 +29,13 @@ private val PretendardMedium = FontFamily( ), ) +private val PretendardRegular = FontFamily( + Font( + resId = R.font.pretendard_medium, + weight = FontWeight.Normal, + ), +) + @Immutable data class PieceTypography( val headingXLSB: TextStyle = TextStyle( @@ -67,8 +74,7 @@ data class PieceTypography( lineHeight = 24.sp, ), val bodyMR: TextStyle = TextStyle( - fontFamily = PretendardMedium, - fontWeight = FontWeight.W400, + fontFamily = PretendardRegular, fontSize = 16.sp, lineHeight = 24.sp, ), diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index c218e496..eee3c00f 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -18,6 +18,7 @@ import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import com.puzzle.auth.graph.registration.contract.RegistrationIntent import com.puzzle.auth.graph.registration.contract.RegistrationState +import com.puzzle.designsystem.component.PieceCheckList import com.puzzle.designsystem.component.PieceSolidButton import com.puzzle.designsystem.component.PieceSubBackTopBar import com.puzzle.designsystem.foundation.PieceTheme @@ -69,15 +70,41 @@ private fun RegistrationScreen( Spacer( modifier = Modifier .fillMaxWidth() - .weight(1f) + .weight(1f), ) + PieceCheckList( + checked = false, + label = "약관 전체 동의", + containerColor = PieceTheme.colors.light3, + onCheckedChange = {}, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + ) + PieceCheckList( + checked = true, + showArrow = true, + label = "[필수] 서비스 이용약관 동의", + onCheckedChange = {}, + onArrowClick = {}, + modifier = Modifier.fillMaxWidth(), + ) + + PieceCheckList( + checked = true, + showArrow = true, + label = "[필수] 개인정보처리 방침 동의", + onCheckedChange = {}, + onArrowClick = {}, + modifier = Modifier.fillMaxWidth(), + ) Spacer( modifier = Modifier .fillMaxWidth() - .weight(2f) + .weight(2f), ) PieceSolidButton( From a0fa57a2ac4cf9e042cf26c575c0759aae645280 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sat, 4 Jan 2025 19:50:30 +0900 Subject: [PATCH 04/26] =?UTF-8?q?[PC-256]=20=EC=9D=B4=EC=9A=A9=EC=95=BD?= =?UTF-8?q?=EA=B4=80=20=EB=8F=99=EC=9D=98=20=EB=A1=9C=EC=A7=81=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 --- .../puzzle/designsystem/component/Check.kt | 8 +++---- .../graph/registration/RegistrationScreen.kt | 18 +++++++++----- .../registration/RegistrationViewModel.kt | 24 +++++++++++++++++-- .../contract/RegistrationIntent.kt | 5 +++- .../contract/RegistrationState.kt | 7 ++++-- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt index 094a36ea..905f9dda 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt @@ -28,7 +28,7 @@ import com.puzzle.designsystem.foundation.PieceTheme @Composable fun PieceCheck( checked: Boolean, - onCheckedChange: (Boolean) -> Unit, + onCheckedChange: () -> Unit, modifier: Modifier = Modifier, ) { Image( @@ -40,9 +40,7 @@ fun PieceCheck( ), modifier = modifier .size(20.dp) - .clickable { - onCheckedChange(!checked) - }, + .clickable { onCheckedChange() }, ) } @@ -50,7 +48,7 @@ fun PieceCheck( fun PieceCheckList( checked: Boolean, label: String, - onCheckedChange: (Boolean) -> Unit, + onCheckedChange: () -> Unit, modifier: Modifier = Modifier, showArrow: Boolean = false, containerColor: Color = PieceTheme.colors.white, diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index eee3c00f..528c176e 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -32,6 +32,9 @@ internal fun RegistrationRoute( RegistrationScreen( state = state, + checkAllTerms = { viewModel.onIntent(RegistrationIntent.CheckAllTerms) }, + checkPrivacyAndPolicy = { viewModel.onIntent(RegistrationIntent.CheckPrivacyPolicy) }, + checkTermsOfUse = { viewModel.onIntent(RegistrationIntent.CheckTermsOfUse) }, navigate = { event -> viewModel.onIntent(RegistrationIntent.Navigate(event)) } ) } @@ -39,6 +42,9 @@ internal fun RegistrationRoute( @Composable private fun RegistrationScreen( state: RegistrationState, + checkAllTerms: () -> Unit, + checkPrivacyAndPolicy: () -> Unit, + checkTermsOfUse: () -> Unit, navigate: (NavigationEvent) -> Unit, ) { Column( @@ -74,29 +80,29 @@ private fun RegistrationScreen( ) PieceCheckList( - checked = false, + checked = state.agreeAllTerms, label = "약관 전체 동의", containerColor = PieceTheme.colors.light3, - onCheckedChange = {}, + onCheckedChange = checkAllTerms, modifier = Modifier .fillMaxWidth() .padding(bottom = 12.dp), ) PieceCheckList( - checked = true, + checked = state.isTermsOfUseChecked, showArrow = true, label = "[필수] 서비스 이용약관 동의", - onCheckedChange = {}, + onCheckedChange = checkTermsOfUse, onArrowClick = {}, modifier = Modifier.fillMaxWidth(), ) PieceCheckList( - checked = true, + checked = state.isPrivacyPolicyChecked, showArrow = true, label = "[필수] 개인정보처리 방침 동의", - onCheckedChange = {}, + onCheckedChange = checkPrivacyAndPolicy, onArrowClick = {}, modifier = Modifier.fillMaxWidth(), ) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index e6d28fba..eb156050 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -4,8 +4,6 @@ import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.hilt.AssistedViewModelFactory import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory -import com.puzzle.auth.graph.login.contract.LoginIntent -import com.puzzle.auth.graph.login.contract.LoginSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationIntent import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationState @@ -43,6 +41,28 @@ class RegistrationViewModel @AssistedInject constructor( private fun processIntent(intent: RegistrationIntent) { when (intent) { is RegistrationIntent.Navigate -> navigationHelper.navigate(intent.navigationEvent) + is RegistrationIntent.CheckPrivacyPolicy -> checkPrivacyPolicy() + is RegistrationIntent.CheckTermsOfUse -> checkTermsOfUse() + is RegistrationIntent.CheckAllTerms -> checkAllTerms() + } + } + + private fun checkPrivacyPolicy() = + setState { copy(isPrivacyPolicyChecked = !isPrivacyPolicyChecked) } + + private fun checkTermsOfUse() = setState { copy(isTermsOfUseChecked = !isTermsOfUseChecked) } + + private fun checkAllTerms() = setState { + if (isTermsOfUseChecked && isPrivacyPolicyChecked) { + copy( + isTermsOfUseChecked = false, + isPrivacyPolicyChecked = false, + ) + } else { + copy( + isTermsOfUseChecked = true, + isPrivacyPolicyChecked = true, + ) } } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt index 5645530e..8a974e89 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt @@ -3,5 +3,8 @@ package com.puzzle.auth.graph.registration.contract import com.puzzle.navigation.NavigationEvent sealed class RegistrationIntent { + data object CheckAllTerms : RegistrationIntent() + data object CheckPrivacyPolicy : RegistrationIntent() + data object CheckTermsOfUse : RegistrationIntent() data class Navigate(val navigationEvent: NavigationEvent) : RegistrationIntent() -} \ No newline at end of file +} diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt index ec85d52d..83687e93 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt @@ -3,5 +3,8 @@ package com.puzzle.auth.graph.registration.contract import com.airbnb.mvrx.MavericksState data class RegistrationState( - val a: Boolean = false, -) : MavericksState \ No newline at end of file + val isTermsOfUseChecked: Boolean = false, + val isPrivacyPolicyChecked: Boolean = false, +) : MavericksState { + val agreeAllTerms = isTermsOfUseChecked && isPrivacyPolicyChecked +} From 2a1fb55bc95d3668a07cd180276887bc263c5c90 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 12:57:54 +0900 Subject: [PATCH 05/26] =?UTF-8?q?[PC-256]=20=EC=95=BD=EA=B4=80=EC=9D=84=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=EB=8F=99=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/puzzle/domain/model/terms/TermInfo.kt | 11 +++++++ .../registration/RegistrationDetailScreen.kt | 8 +++++ .../graph/registration/RegistrationScreen.kt | 33 +++++++------------ .../registration/RegistrationViewModel.kt | 30 +++++++++-------- .../contract/RegistrationIntent.kt | 3 +- .../contract/RegistrationState.kt | 30 +++++++++++++++-- 6 files changed, 75 insertions(+), 40 deletions(-) create mode 100644 core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt create mode 100644 feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationDetailScreen.kt diff --git a/core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt b/core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt new file mode 100644 index 00000000..81780d1e --- /dev/null +++ b/core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt @@ -0,0 +1,11 @@ +package com.puzzle.domain.model.terms + +import java.time.LocalDateTime + +data class TermInfo( + val termId: Int, + val title: String, + val content: String, + val required: Boolean, + val startDate: LocalDateTime, +) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationDetailScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationDetailScreen.kt new file mode 100644 index 00000000..408dd8ce --- /dev/null +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationDetailScreen.kt @@ -0,0 +1,8 @@ +package com.puzzle.auth.graph.registration + +import androidx.compose.runtime.Composable + +@Composable +internal fun RegistrationDetailScreen() { + +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index 528c176e..a0441078 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -33,8 +33,7 @@ internal fun RegistrationRoute( RegistrationScreen( state = state, checkAllTerms = { viewModel.onIntent(RegistrationIntent.CheckAllTerms) }, - checkPrivacyAndPolicy = { viewModel.onIntent(RegistrationIntent.CheckPrivacyPolicy) }, - checkTermsOfUse = { viewModel.onIntent(RegistrationIntent.CheckTermsOfUse) }, + checkTerm = { viewModel.onIntent(RegistrationIntent.CheckTerm(it)) }, navigate = { event -> viewModel.onIntent(RegistrationIntent.Navigate(event)) } ) } @@ -43,8 +42,7 @@ internal fun RegistrationRoute( private fun RegistrationScreen( state: RegistrationState, checkAllTerms: () -> Unit, - checkPrivacyAndPolicy: () -> Unit, - checkTermsOfUse: () -> Unit, + checkTerm: (Int) -> Unit, navigate: (NavigationEvent) -> Unit, ) { Column( @@ -89,23 +87,16 @@ private fun RegistrationScreen( .padding(bottom = 12.dp), ) - PieceCheckList( - checked = state.isTermsOfUseChecked, - showArrow = true, - label = "[필수] 서비스 이용약관 동의", - onCheckedChange = checkTermsOfUse, - onArrowClick = {}, - modifier = Modifier.fillMaxWidth(), - ) - - PieceCheckList( - checked = state.isPrivacyPolicyChecked, - showArrow = true, - label = "[필수] 개인정보처리 방침 동의", - onCheckedChange = checkPrivacyAndPolicy, - onArrowClick = {}, - modifier = Modifier.fillMaxWidth(), - ) + state.termInfos.forEach { termInfo -> + PieceCheckList( + checked = state.termsCheckedInfo.getOrDefault(termInfo.termId, false), + showArrow = true, + label = termInfo.title, + onCheckedChange = { checkTerm(termInfo.termId) }, + onArrowClick = {}, + modifier = Modifier.fillMaxWidth(), + ) + } Spacer( modifier = Modifier diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index eb156050..d99050cb 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -41,28 +41,30 @@ class RegistrationViewModel @AssistedInject constructor( private fun processIntent(intent: RegistrationIntent) { when (intent) { is RegistrationIntent.Navigate -> navigationHelper.navigate(intent.navigationEvent) - is RegistrationIntent.CheckPrivacyPolicy -> checkPrivacyPolicy() - is RegistrationIntent.CheckTermsOfUse -> checkTermsOfUse() + is RegistrationIntent.CheckTerm -> checkTerm(intent.termId) is RegistrationIntent.CheckAllTerms -> checkAllTerms() } } - private fun checkPrivacyPolicy() = - setState { copy(isPrivacyPolicyChecked = !isPrivacyPolicyChecked) } + private fun checkTerm(termId: Int) = setState { + val updatedTermsCheckedInfo = termsCheckedInfo.toMutableMap().apply { + this[termId] = !(this[termId] ?: false) + } - private fun checkTermsOfUse() = setState { copy(isTermsOfUseChecked = !isTermsOfUseChecked) } + copy(termsCheckedInfo = updatedTermsCheckedInfo) + } private fun checkAllTerms() = setState { - if (isTermsOfUseChecked && isPrivacyPolicyChecked) { - copy( - isTermsOfUseChecked = false, - isPrivacyPolicyChecked = false, - ) + if (agreeAllTerms) { + copy(termsCheckedInfo = mutableMapOf()) } else { - copy( - isTermsOfUseChecked = true, - isPrivacyPolicyChecked = true, - ) + val updatedTermsCheckedInfo = termsCheckedInfo.toMutableMap() + + termInfos.forEach { termInfo -> + updatedTermsCheckedInfo[termInfo.termId] = true + } + + copy(termsCheckedInfo = updatedTermsCheckedInfo) } } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt index 8a974e89..fa57cbc0 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt @@ -4,7 +4,6 @@ import com.puzzle.navigation.NavigationEvent sealed class RegistrationIntent { data object CheckAllTerms : RegistrationIntent() - data object CheckPrivacyPolicy : RegistrationIntent() - data object CheckTermsOfUse : RegistrationIntent() + data class CheckTerm(val termId: Int) : RegistrationIntent() data class Navigate(val navigationEvent: NavigationEvent) : RegistrationIntent() } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt index 83687e93..09ceb62f 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt @@ -1,10 +1,34 @@ package com.puzzle.auth.graph.registration.contract import com.airbnb.mvrx.MavericksState +import com.puzzle.domain.model.terms.TermInfo +import java.time.LocalDateTime data class RegistrationState( - val isTermsOfUseChecked: Boolean = false, - val isPrivacyPolicyChecked: Boolean = false, + val termInfos: List = listOf( + TermInfo( + termId = 1, + title = "서비스 이용약관", + content = "서비스 이용에 대한 약관 내용입니다.", + required = true, + startDate = LocalDateTime.parse("2024-01-01T00:00:00") + ), + TermInfo( + termId = 2, + title = "개인정보 처리방침", + content = "개인정보 보호에 대한 약관 내용입니다.", + required = true, + startDate = LocalDateTime.parse("2024-01-01T00:00:00") + ), + TermInfo( + termId = 3, + title = "위치 정보 이용약관", + content = "위치 정보 활용에 대한 약관 내용입니다.", + required = false, + startDate = LocalDateTime.parse("2024-01-01T00:00:00") + ), + ), + val termsCheckedInfo: MutableMap = mutableMapOf(), ) : MavericksState { - val agreeAllTerms = isTermsOfUseChecked && isPrivacyPolicyChecked + val agreeAllTerms = termInfos.all { termsCheckedInfo.getOrDefault(it.termId, false) } } From bda08b0150574f1942bda543a323f5911bf71d21 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 13:13:41 +0900 Subject: [PATCH 06/26] =?UTF-8?q?[PC-256]=20=EC=95=BD=EA=B4=80=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=AC=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/terms/{TermInfo.kt => Term.kt} | 2 +- .../domain/repository/TermsRepository.kt | 5 +++ .../java/com/puzzle/network/api/PieceApi.kt | 6 ++- .../network/model/terms/LoadTermsResponse.kt | 42 +++++++++++++++++++ .../puzzle/network/source/TermsDataSource.kt | 12 ++++++ .../graph/registration/RegistrationScreen.kt | 2 +- .../registration/RegistrationViewModel.kt | 2 +- .../contract/RegistrationState.kt | 12 +++--- .../com/puzzle/presentation/MainViewModel.kt | 4 ++ 9 files changed, 77 insertions(+), 10 deletions(-) rename core/domain/src/main/java/com/puzzle/domain/model/terms/{TermInfo.kt => Term.kt} (90%) create mode 100644 core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt create mode 100644 core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt create mode 100644 core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt diff --git a/core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt b/core/domain/src/main/java/com/puzzle/domain/model/terms/Term.kt similarity index 90% rename from core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt rename to core/domain/src/main/java/com/puzzle/domain/model/terms/Term.kt index 81780d1e..e0896524 100644 --- a/core/domain/src/main/java/com/puzzle/domain/model/terms/TermInfo.kt +++ b/core/domain/src/main/java/com/puzzle/domain/model/terms/Term.kt @@ -2,7 +2,7 @@ package com.puzzle.domain.model.terms import java.time.LocalDateTime -data class TermInfo( +data class Term( val termId: Int, val title: String, val content: String, diff --git a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt new file mode 100644 index 00000000..a19425b1 --- /dev/null +++ b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt @@ -0,0 +1,5 @@ +package com.puzzle.domain.repository + +interface TermsRepository { + suspend fun loadTerms() +} \ No newline at end of file diff --git a/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt b/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt index 691c4f21..3d9447d0 100644 --- a/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt +++ b/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt @@ -5,8 +5,9 @@ import com.puzzle.network.model.auth.LoginOauthResponse import com.puzzle.network.model.auth.RequestAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeResponse -import retrofit2.Response +import com.puzzle.network.model.terms.LoadTermsResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST interface PieceApi { @@ -18,4 +19,7 @@ interface PieceApi { @POST("/api/register/sms/auth/code/verify") suspend fun verifyAuthCode(@Body verifyAuthCodeRequest: VerifyAuthCodeRequest): Result + + @GET("/api/terms") + suspend fun loadTerms(): Result } diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt new file mode 100644 index 00000000..1d97553c --- /dev/null +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -0,0 +1,42 @@ +package com.puzzle.network.model.terms + +import com.puzzle.domain.model.terms.Term +import java.time.LocalDateTime +import java.time.format.DateTimeParseException + +data class LoadTermsResponse( + val status: String?, + val message: String?, + val data: Data?, +) { + data class Data( + val response: List?, + ) { + fun toDomain() = response?.map { it.toDomain() } ?: emptyList() + } +} + +data class TermResponse( + val termId: Int?, + val title: String?, + val content: String?, + val required: Boolean?, + val startDate: String?, +) { + fun toDomain() = Term( + termId = termId ?: -1, + title = title ?: "UNKNOWN", + content = content ?: "UNKNOWN", + required = required ?: false, + startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN + ) +} + +// 현재는 여기에서만 사용되지만 3군데 이상에서 사용시 core:common 모듈을 만들고 그곳으로 이동 +private fun String?.parseDateTime(): LocalDateTime { + return try { + this?.let { LocalDateTime.parse(it) } ?: LocalDateTime.MIN + } catch (e: DateTimeParseException) { + LocalDateTime.MIN + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt new file mode 100644 index 00000000..d0d98ca2 --- /dev/null +++ b/core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt @@ -0,0 +1,12 @@ +package com.puzzle.network.source + +import com.puzzle.network.api.PieceApi +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TermsDataSource @Inject constructor( + private val pieceApi: PieceApi, +) { + suspend fun loadTerms() = pieceApi.loadTerms() +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index a0441078..7c6f7bff 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -87,7 +87,7 @@ private fun RegistrationScreen( .padding(bottom = 12.dp), ) - state.termInfos.forEach { termInfo -> + state.terms.forEach { termInfo -> PieceCheckList( checked = state.termsCheckedInfo.getOrDefault(termInfo.termId, false), showArrow = true, diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index d99050cb..d291912e 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -60,7 +60,7 @@ class RegistrationViewModel @AssistedInject constructor( } else { val updatedTermsCheckedInfo = termsCheckedInfo.toMutableMap() - termInfos.forEach { termInfo -> + terms.forEach { termInfo -> updatedTermsCheckedInfo[termInfo.termId] = true } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt index 09ceb62f..5b77b3a5 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt @@ -1,26 +1,26 @@ package com.puzzle.auth.graph.registration.contract import com.airbnb.mvrx.MavericksState -import com.puzzle.domain.model.terms.TermInfo +import com.puzzle.domain.model.terms.Term import java.time.LocalDateTime data class RegistrationState( - val termInfos: List = listOf( - TermInfo( + val terms: List = listOf( + Term( termId = 1, title = "서비스 이용약관", content = "서비스 이용에 대한 약관 내용입니다.", required = true, startDate = LocalDateTime.parse("2024-01-01T00:00:00") ), - TermInfo( + Term( termId = 2, title = "개인정보 처리방침", content = "개인정보 보호에 대한 약관 내용입니다.", required = true, startDate = LocalDateTime.parse("2024-01-01T00:00:00") ), - TermInfo( + Term( termId = 3, title = "위치 정보 이용약관", content = "위치 정보 활용에 대한 약관 내용입니다.", @@ -30,5 +30,5 @@ data class RegistrationState( ), val termsCheckedInfo: MutableMap = mutableMapOf(), ) : MavericksState { - val agreeAllTerms = termInfos.all { termsCheckedInfo.getOrDefault(it.termId, false) } + val agreeAllTerms = terms.all { termsCheckedInfo.getOrDefault(it.termId, false) } } diff --git a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt index 63e18a98..30e9b1ce 100644 --- a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt +++ b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt @@ -33,4 +33,8 @@ class MainViewModel @Inject constructor( } } } + + private fun loadTerms() = viewModelScope.launch { + + } } From 6c028dd7518847a4377653a3e0405e539a6446fb Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 13:20:51 +0900 Subject: [PATCH 07/26] =?UTF-8?q?[PC-256]=20MainViewModel=EC=9D=98=20init?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=95=BD=EA=B4=80=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/puzzle/data/di/DataModule.kt | 10 +++++++++- .../puzzle/data/repository/TermsRepositoryImpl.kt | 14 ++++++++++++++ .../puzzle/domain/repository/TermsRepository.kt | 2 +- .../domain/usecase/terms/LoadTermsUseCase.kt | 10 ++++++++++ .../java/com/puzzle/presentation/MainViewModel.kt | 9 ++++++++- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt create mode 100644 core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt diff --git a/core/data/src/main/java/com/puzzle/data/di/DataModule.kt b/core/data/src/main/java/com/puzzle/data/di/DataModule.kt index c52cbaff..bba9932e 100644 --- a/core/data/src/main/java/com/puzzle/data/di/DataModule.kt +++ b/core/data/src/main/java/com/puzzle/data/di/DataModule.kt @@ -1,7 +1,9 @@ package com.puzzle.data.di import com.puzzle.data.repository.AuthRepositoryImpl +import com.puzzle.data.repository.TermsRepositoryImpl import com.puzzle.domain.repository.AuthRepository +import com.puzzle.domain.repository.TermsRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -18,4 +20,10 @@ abstract class DataModule { abstract fun bindsAuthRepository( authRepositoryImpl: AuthRepositoryImpl, ): AuthRepository -} \ No newline at end of file + + @Binds + @Singleton + abstract fun bindsTermsRepository( + termsRepositoryImpl: TermsRepositoryImpl, + ): TermsRepository +} diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt new file mode 100644 index 00000000..3545b095 --- /dev/null +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.puzzle.data.repository + +import com.puzzle.domain.repository.TermsRepository +import com.puzzle.network.source.TermsDataSource +import javax.inject.Inject + +class TermsRepositoryImpl @Inject constructor( + private val termsDataSource: TermsDataSource, +) : TermsRepository { + override suspend fun loadTerms(): Result { + termsDataSource.loadTerms() + return Result.success(Unit) + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt index a19425b1..87836112 100644 --- a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt +++ b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt @@ -1,5 +1,5 @@ package com.puzzle.domain.repository interface TermsRepository { - suspend fun loadTerms() + suspend fun loadTerms(): Result } \ No newline at end of file diff --git a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt new file mode 100644 index 00000000..84145138 --- /dev/null +++ b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt @@ -0,0 +1,10 @@ +package com.puzzle.domain.usecase.terms + +import com.puzzle.domain.repository.TermsRepository +import javax.inject.Inject + +class LoadTermsUseCase @Inject constructor( + private val termsRepository: TermsRepository, +) { + suspend operator fun invoke(): Result = termsRepository.loadTerms() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt index 30e9b1ce..e80e90ad 100644 --- a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt +++ b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.puzzle.common.event.EventHelper import com.puzzle.domain.model.error.ErrorHelper import com.puzzle.domain.model.error.HttpResponseException +import com.puzzle.domain.usecase.terms.LoadTermsUseCase import com.puzzle.navigation.NavigationHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -12,6 +13,7 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( + private val loadTermsUseCase: LoadTermsUseCase, internal val navigationHelper: NavigationHelper, internal val eventHelper: EventHelper, private val errorHelper: ErrorHelper, @@ -19,6 +21,7 @@ class MainViewModel @Inject constructor( init { handleError() + loadTerms() } private fun handleError() = viewModelScope.launch { @@ -35,6 +38,10 @@ class MainViewModel @Inject constructor( } private fun loadTerms() = viewModelScope.launch { - + loadTermsUseCase().onSuccess { + // Todo + }.onFailure { + errorHelper.sendError(it) + } } } From 082bc81abf292c9ca21671e1e631839c67d7fdd8 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 13:38:15 +0900 Subject: [PATCH 08/26] =?UTF-8?q?[PC-256]=20ProxyMan=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=EC=95=BD?= =?UTF-8?q?=EA=B4=80=20=EC=A0=95=EB=B3=B4=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EA=B2=83=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 5 ++++- .../main/res/xml/network_security_config.xml | 17 +++++++++++++++++ core/network/build.gradle.kts | 9 +++++++-- .../network/model/terms/LoadTermsResponse.kt | 4 ++++ gradle.properties | 2 +- 6 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4138d5ba..e30e1265 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -40,4 +40,5 @@ dependencies { implementation(libs.kakao.user) implementation(projects.presentation) + implementation(projects.core.data) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e76a7103..3c408875 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Piece" @@ -32,10 +33,12 @@ android:exported="true"> + - diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..84da531b --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 5955643c..49532be3 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("piece.android.library") id("piece.android.hilt") @@ -6,19 +8,22 @@ plugins { android { namespace = "com.puzzle.network" + val localProperties = Properties() + localProperties.load(project.rootProject.file("local.properties").bufferedReader()) + buildTypes { debug { buildConfigField( "String", "PIECE_BASE_URL", - "\"${properties["PIECE_DEV_BASE_URL"]}\"", + "\"${localProperties["PIECE_DEV_BASE_URL"]}\"", ) } release { buildConfigField( "String", "PIECE_BASE_URL", - "\"${properties["PIECE_PROD_BASE_URL"]}\"", + "\"${localProperties["PIECE_PROD_BASE_URL"]}\"", ) } } diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt index 1d97553c..b66c0322 100644 --- a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -1,14 +1,17 @@ package com.puzzle.network.model.terms import com.puzzle.domain.model.terms.Term +import kotlinx.serialization.Serializable import java.time.LocalDateTime import java.time.format.DateTimeParseException +@Serializable data class LoadTermsResponse( val status: String?, val message: String?, val data: Data?, ) { + @Serializable data class Data( val response: List?, ) { @@ -16,6 +19,7 @@ data class LoadTermsResponse( } } +@Serializable data class TermResponse( val termId: Int?, val title: String?, diff --git a/gradle.properties b/gradle.properties index aec575af..0c81f9fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects From 77a7e84f29cd1692843372d8be7eba9aca82a71e Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 14:06:58 +0900 Subject: [PATCH 09/26] =?UTF-8?q?[PC-256]=20LocalDataBase=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Room=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/common/.gitignore | 1 + core/common/build.gradle.kts | 15 +++++++ .../main/java/com/puzzle/common/MyClass.kt | 4 ++ core/database/.gitignore | 1 + core/database/build.gradle.kts | 16 ++++++++ core/database/consumer-rules.pro | 0 core/database/proguard-rules.pro | 21 ++++++++++ .../database/ExampleInstrumentedTest.kt | 24 ++++++++++++ core/database/src/main/AndroidManifest.xml | 4 ++ .../java/com/puzzle/database/PieceDatabase.kt | 10 +++++ .../java/com/puzzle/database/di/DaoModule.kt | 5 +++ .../com/puzzle/database/di/DatabaseModule.kt | 25 ++++++++++++ .../database/model/terms/TermsEntity.kt | 27 +++++++++++++ .../com/puzzle/database/ExampleUnitTest.kt | 17 ++++++++ gradle/libs.versions.toml | 39 ++++++++++--------- settings.gradle.kts | 2 + 16 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 core/common/.gitignore create mode 100644 core/common/build.gradle.kts create mode 100644 core/common/src/main/java/com/puzzle/common/MyClass.kt create mode 100644 core/database/.gitignore create mode 100644 core/database/build.gradle.kts create mode 100644 core/database/consumer-rules.pro create mode 100644 core/database/proguard-rules.pro create mode 100644 core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt create mode 100644 core/database/src/main/AndroidManifest.xml create mode 100644 core/database/src/main/java/com/puzzle/database/PieceDatabase.kt create mode 100644 core/database/src/main/java/com/puzzle/database/di/DaoModule.kt create mode 100644 core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt create mode 100644 core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt create mode 100644 core/database/src/test/java/com/puzzle/database/ExampleUnitTest.kt diff --git a/core/common/.gitignore b/core/common/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts new file mode 100644 index 00000000..66262989 --- /dev/null +++ b/core/common/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("java-library") + alias(libs.plugins.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} \ No newline at end of file diff --git a/core/common/src/main/java/com/puzzle/common/MyClass.kt b/core/common/src/main/java/com/puzzle/common/MyClass.kt new file mode 100644 index 00000000..279d4444 --- /dev/null +++ b/core/common/src/main/java/com/puzzle/common/MyClass.kt @@ -0,0 +1,4 @@ +package com.puzzle.common + +class MyClass { +} \ No newline at end of file diff --git a/core/database/.gitignore b/core/database/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/database/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts new file mode 100644 index 00000000..d92a7e0b --- /dev/null +++ b/core/database/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("piece.android.library") + id("piece.android.hilt") +} + +android { + namespace = "com.puzzle.database" +} + +dependencies { + implementation(projects.core.domain) + + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) +} diff --git a/core/database/consumer-rules.pro b/core/database/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/database/proguard-rules.pro b/core/database/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/database/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt b/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..7c24a6d6 --- /dev/null +++ b/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.puzzle.database + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.puzzle.database.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/database/src/main/AndroidManifest.xml b/core/database/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/core/database/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt new file mode 100644 index 00000000..2fdff22f --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt @@ -0,0 +1,10 @@ +package com.puzzle.database + +import androidx.room.RoomDatabase + +internal abstract class PieceDatabase : RoomDatabase() { + + companion object { + internal const val NAME = "piece-database" + } +} \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt b/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt new file mode 100644 index 00000000..9f3518f9 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt @@ -0,0 +1,5 @@ +package com.puzzle.database.di + + +object DaoModule { +} \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt new file mode 100644 index 00000000..2dd6d0a2 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt @@ -0,0 +1,25 @@ +package com.puzzle.database.di + +import android.content.Context +import androidx.room.Room +import com.puzzle.database.PieceDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object DatabaseModule { + @Provides + @Singleton + fun providesNiaDatabase( + @ApplicationContext context: Context, + ): PieceDatabase = Room.databaseBuilder( + context, + PieceDatabase::class.java, + PieceDatabase.NAME, + ).build() +} \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt b/core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt new file mode 100644 index 00000000..009051b0 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt @@ -0,0 +1,27 @@ +package com.puzzle.database.model.terms + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.puzzle.domain.model.terms.Term +import java.time.LocalDateTime + +@Entity( + tableName = TERMS_TABLE_NAME, +) +data class TermsEntity( + @PrimaryKey val termId: Int?, + val title: String?, + val content: String?, + val required: Boolean?, + val startDate: String?, +) { + fun toDomain() = Term( + termId = termId ?: -1, + title = title ?: "UNKNOWN", + content = content ?: "UNKNOWN", + required = required ?: false, + startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN + ) +} + +private const val TERMS_TABLE_NAME = "terms" \ No newline at end of file diff --git a/core/database/src/test/java/com/puzzle/database/ExampleUnitTest.kt b/core/database/src/test/java/com/puzzle/database/ExampleUnitTest.kt new file mode 100644 index 00000000..b15a51f3 --- /dev/null +++ b/core/database/src/test/java/com/puzzle/database/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.puzzle.database + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5514cf41..56997315 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,19 +19,19 @@ androidxNavigation = "2.8.3" androidxFragment = "1.8.4" # https://developer.android.com/jetpack/androidx/releases/constraintlayout androidxConstraintlayout = "2.1.4" -# https://developer.android.com/jetpack/androidx/releases/core\ +# https://developer.android.com/jetpack/androidx/releases/core androidxSplashscreen = "1.0.1" - -## Compose +# https://developer.android.com/training/data-storage/room +androidxRoom = "2.6.1" +# https://developer.android.com/jetpack/androidx/releases/test +androidxTestRunner = "1.6.2" +androidxTestExt = "1.2.1" +androidxEspresso = "3.6.1" # https://developer.android.com/develop/ui/compose/bom/bom-mapping androidxComposeBom = "2024.12.01" # https://developer.android.com/jetpack/androidx/releases/navigation androidxComposeNavigation = "2.8.4" -## Amplitude -# https://amplitude.com/docs/sdks/analytics/android -amplitudeAnalytics = "1.16.8" - ## Material material = "1.12.0" @@ -58,21 +58,14 @@ retrofit = "2.11.0" kotlin = "2.0.0" # https://github.com/Kotlin/kotlinx.serialization kotlinxSerializationJson = "1.7.0" - -## Coroutine # https://github.com/Kotlin/kotlinx.coroutines -coroutine = "1.9.0-RC" +kotlinxCoroutine = "1.9.0-RC" ## Test # https://github.com/junit-team/junit4 junit4 = "4.13.2" junitJupiter = "5.8.2" -# https://developer.android.com/jetpack/androidx/releases/test -androidxTestRunner = "1.6.2" -androidxTestExt = "1.2.1" -androidxEspresso = "3.6.1" - # https://mockk.io/ mockk = "1.13.11" @@ -100,6 +93,10 @@ mavericks = "3.0.9" # https://github.com/skydoves/Cloudy cloudy = "0.2.4" +## Amplitude +# https://amplitude.com/docs/sdks/analytics/android +amplitudeAnalytics = "1.16.8" + [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -127,6 +124,14 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" } compose-compiler-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } +androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" } +androidx-room-runtime = { group = "androidx.room", name = " room-runtime", version.ref = "androidxRoom" } +androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidxRoom" } + +coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutine" } +coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutine" } +coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutine" } + material = { group = "com.google.android.material", name = "material", version.ref = "material" } hilt-core = { group = "com.google.dagger", name = "hilt-core", version.ref = "hilt" } @@ -153,10 +158,6 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" } mockk-agent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockk" } -coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutine" } -coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutine" } -coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine" } - firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 41c4c480..b6eefdbd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,3 +35,5 @@ include(":core:network") include(":core:navigation") include(":core:common-ui") include(":presentation") +include(":core:database") +include(":core:common") From c8b7b223278a687877bba4a4d6acec347f39f56b Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 14:51:09 +0900 Subject: [PATCH 10/26] =?UTF-8?q?[PC-256]=20TermDao=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20Query=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logic/piece.android.feature.gradle.kts | 1 + core/common/build.gradle.kts | 15 ++++-------- .../main/java/com/puzzle/common/MyClass.kt | 4 ---- .../main/java/com/puzzle/common/TimeUtil.kt | 18 ++++++++++++++ .../data/repository/TermsRepositoryImpl.kt | 6 ++--- core/database/build.gradle.kts | 3 ++- .../java/com/puzzle/database/PieceDatabase.kt | 10 ++++++++ .../java/com/puzzle/database/dao/TermDao.kt | 24 +++++++++++++++++++ .../java/com/puzzle/database/di/DaoModule.kt | 14 ++++++++++- .../terms/{TermsEntity.kt => TermEntity.kt} | 15 ++++++------ .../source/term/LocalTermDataSource.kt | 15 ++++++++++++ core/network/build.gradle.kts | 1 + .../network/model/terms/LoadTermsResponse.kt | 11 +-------- .../{TermsDataSource.kt => TermDataSource.kt} | 2 +- 14 files changed, 100 insertions(+), 39 deletions(-) delete mode 100644 core/common/src/main/java/com/puzzle/common/MyClass.kt create mode 100644 core/common/src/main/java/com/puzzle/common/TimeUtil.kt create mode 100644 core/database/src/main/java/com/puzzle/database/dao/TermDao.kt rename core/database/src/main/java/com/puzzle/database/model/terms/{TermsEntity.kt => TermEntity.kt} (66%) create mode 100644 core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt rename core/network/src/main/java/com/puzzle/network/source/{TermsDataSource.kt => TermDataSource.kt} (84%) diff --git a/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts b/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts index e72a7035..c2bb49c4 100644 --- a/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts +++ b/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(project(":core:domain")) implementation(project(":core:navigation")) implementation(project(":core:common-ui")) + implementation(project(":core:common")) // implementation(project(":core:analytics")) val libs = project.extensions.libs diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 66262989..492e1cf0 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,15 +1,8 @@ plugins { - id("java-library") - alias(libs.plugins.kotlin.jvm) + id("piece.kotlin.library") + id("piece.kotlin.hilt") } -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 +dependencies { + implementation(libs.coroutines.core) } - -kotlin { - compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 - } -} \ No newline at end of file diff --git a/core/common/src/main/java/com/puzzle/common/MyClass.kt b/core/common/src/main/java/com/puzzle/common/MyClass.kt deleted file mode 100644 index 279d4444..00000000 --- a/core/common/src/main/java/com/puzzle/common/MyClass.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.puzzle.common - -class MyClass { -} \ No newline at end of file diff --git a/core/common/src/main/java/com/puzzle/common/TimeUtil.kt b/core/common/src/main/java/com/puzzle/common/TimeUtil.kt new file mode 100644 index 00000000..2df910d2 --- /dev/null +++ b/core/common/src/main/java/com/puzzle/common/TimeUtil.kt @@ -0,0 +1,18 @@ +package com.puzzle.common + +import java.time.LocalDateTime +import java.time.format.DateTimeParseException + +/** + * String?을 LocalDateTime으로 변환합니다. + * + * - 문자열이 null인 경우, [LocalDateTime.MIN]을 반환합니다. + * - 잘못된 형식으로 인해 파싱에 실패할 경우, [LocalDateTime.MIN]을 반환합니다. + */ +fun String?.parseDateTime(): LocalDateTime { + return try { + this?.let { LocalDateTime.parse(it) } ?: LocalDateTime.MIN + } catch (e: DateTimeParseException) { + LocalDateTime.MIN + } +} diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 3545b095..02dd62d2 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -1,14 +1,14 @@ package com.puzzle.data.repository import com.puzzle.domain.repository.TermsRepository -import com.puzzle.network.source.TermsDataSource +import com.puzzle.network.source.TermDataSource import javax.inject.Inject class TermsRepositoryImpl @Inject constructor( - private val termsDataSource: TermsDataSource, + private val termDataSource: TermDataSource, ) : TermsRepository { override suspend fun loadTerms(): Result { - termsDataSource.loadTerms() + termDataSource.loadTerms() return Result.success(Unit) } } \ No newline at end of file diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index d92a7e0b..038def2f 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -9,7 +9,8 @@ android { dependencies { implementation(projects.core.domain) - + implementation(projects.core.common) + implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) ksp(libs.androidx.room.compiler) diff --git a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt index 2fdff22f..08da566b 100644 --- a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt +++ b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt @@ -1,8 +1,18 @@ package com.puzzle.database +import androidx.room.Database import androidx.room.RoomDatabase +import com.puzzle.database.dao.TermDao +import com.puzzle.database.model.terms.TermEntity +@Database( + entities = [ + TermEntity::class, + ], + version = 1, +) internal abstract class PieceDatabase : RoomDatabase() { + abstract fun termDao(): TermDao companion object { internal const val NAME = "piece-database" diff --git a/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt b/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt new file mode 100644 index 00000000..56da9c39 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt @@ -0,0 +1,24 @@ +package com.puzzle.database.dao + +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.puzzle.database.model.terms.TermEntity + +interface TermDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertTerms(vararg terms: TermEntity) + + @Query(value = "SELECT * FROM term") + suspend fun getTerms(): List + + @Query(value = "DELETE FROM term") + suspend fun clearTerms() + + @Transaction + suspend fun clearAndInsertTerms(vararg terms: TermEntity) { + clearTerms() + insertTerms(*terms) + } +} diff --git a/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt b/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt index 9f3518f9..9cece83c 100644 --- a/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt +++ b/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt @@ -1,5 +1,17 @@ package com.puzzle.database.di +import com.puzzle.database.PieceDatabase +import com.puzzle.database.dao.TermDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent -object DaoModule { +@Module +@InstallIn(SingletonComponent::class) +internal object DaoModule { + @Provides + fun providesTopicsDao( + database: PieceDatabase, + ): TermDao = database.termDao() } \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt similarity index 66% rename from core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt rename to core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt index 009051b0..14756fc8 100644 --- a/core/database/src/main/java/com/puzzle/database/model/terms/TermsEntity.kt +++ b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt @@ -1,19 +1,20 @@ package com.puzzle.database.model.terms +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import com.puzzle.common.parseDateTime import com.puzzle.domain.model.terms.Term import java.time.LocalDateTime -@Entity( - tableName = TERMS_TABLE_NAME, -) -data class TermsEntity( - @PrimaryKey val termId: Int?, +@Entity(tableName = "term") +data class TermEntity( + @PrimaryKey + @ColumnInfo(name = "term_id") val termId: Int?, val title: String?, val content: String?, val required: Boolean?, - val startDate: String?, + @ColumnInfo(name = "start_date") val startDate: String?, ) { fun toDomain() = Term( termId = termId ?: -1, @@ -23,5 +24,3 @@ data class TermsEntity( startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN ) } - -private const val TERMS_TABLE_NAME = "terms" \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt b/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt new file mode 100644 index 00000000..a8d57b87 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt @@ -0,0 +1,15 @@ +package com.puzzle.database.source.term + +import com.puzzle.database.dao.TermDao +import com.puzzle.database.model.terms.TermEntity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalTermDataSource @Inject constructor( + private val termDao: TermDao, +) { + suspend fun getTerms() = termDao.getTerms() + suspend fun clearAndInsertTerms(terms: List) = + termDao.clearAndInsertTerms(*terms.toTypedArray()) +} diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 49532be3..01868a59 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -35,6 +35,7 @@ android { dependencies { implementation(projects.core.domain) + implementation(projects.core.common) implementation(libs.retrofit.core) implementation(libs.retrofit.kotlin.serialization) diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt index b66c0322..4f656224 100644 --- a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -1,9 +1,9 @@ package com.puzzle.network.model.terms +import com.puzzle.common.parseDateTime import com.puzzle.domain.model.terms.Term import kotlinx.serialization.Serializable import java.time.LocalDateTime -import java.time.format.DateTimeParseException @Serializable data class LoadTermsResponse( @@ -35,12 +35,3 @@ data class TermResponse( startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN ) } - -// 현재는 여기에서만 사용되지만 3군데 이상에서 사용시 core:common 모듈을 만들고 그곳으로 이동 -private fun String?.parseDateTime(): LocalDateTime { - return try { - this?.let { LocalDateTime.parse(it) } ?: LocalDateTime.MIN - } catch (e: DateTimeParseException) { - LocalDateTime.MIN - } -} \ No newline at end of file diff --git a/core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt similarity index 84% rename from core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt rename to core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt index d0d98ca2..56be6061 100644 --- a/core/network/src/main/java/com/puzzle/network/source/TermsDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt @@ -5,7 +5,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class TermsDataSource @Inject constructor( +class TermDataSource @Inject constructor( private val pieceApi: PieceApi, ) { suspend fun loadTerms() = pieceApi.loadTerms() From 3098f3145fb52965b62d750b5f9355f88bc280bc Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 15:14:20 +0900 Subject: [PATCH 11/26] =?UTF-8?q?[PC-256]=20ApiResponse=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EB=84=A4=EB=A6=AD=EC=9C=BC=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20CallAdapter=EC=97=90=EC=84=9C=20status=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9D=84=20=EA=B2=B0=EC=A0=95=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/TermsRepositoryImpl.kt | 7 +-- .../network/adapter/PieceCallAdapter.kt | 54 ++++++++++++++----- .../com/puzzle/network/di/RetrofitModule.kt | 5 +- ...uestAuthCodeResponse.kt => ApiResponse.kt} | 6 +-- .../network/model/auth/LoginOauthResponse.kt | 17 ++---- .../model/auth/VerifyAuthCodeResponse.kt | 17 ++---- .../network/model/terms/LoadTermsResponse.kt | 11 +--- 7 files changed, 64 insertions(+), 53 deletions(-) rename core/network/src/main/java/com/puzzle/network/model/{auth/RequestAuthCodeResponse.kt => ApiResponse.kt} (53%) diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 02dd62d2..c2f32b4a 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -6,9 +6,10 @@ import javax.inject.Inject class TermsRepositoryImpl @Inject constructor( private val termDataSource: TermDataSource, + private val localTermDataSource: TermDataSource, ) : TermsRepository { - override suspend fun loadTerms(): Result { - termDataSource.loadTerms() - return Result.success(Unit) + override suspend fun loadTerms(): Result = runCatching { + val terms = termDataSource.loadTerms().getOrThrow() + localTermDataSource } } \ No newline at end of file diff --git a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt index 65b8c290..0baa54e8 100644 --- a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt +++ b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt @@ -2,6 +2,9 @@ package com.puzzle.network.adapter import com.puzzle.domain.model.error.HttpResponseException import com.puzzle.domain.model.error.HttpResponseStatus +import com.puzzle.network.model.ApiResponse +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer import okhttp3.Request import okio.Timeout import retrofit2.Call @@ -15,7 +18,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class PieceCallAdapterFactory @Inject constructor() : CallAdapter.Factory() { +class PieceCallAdapterFactory @Inject constructor( + private val json: Json, +) : CallAdapter.Factory() { override fun get( type: Type, annotations: Array, @@ -27,22 +32,25 @@ class PieceCallAdapterFactory @Inject constructor() : CallAdapter.Factory() { // 해당 타입의 제네릭 타입을 가져옴 Result의 T를 뜻함 val responseType = getParameterUpperBound(0, wrapperType as ParameterizedType) - return PieceCallAdapter(responseType) + return PieceCallAdapter(responseType, json) } } private class PieceCallAdapter( private val resultType: Type, + private val json: Json, ) : CallAdapter>> { // 반환 타입은 Result의 T임 override fun responseType(): Type = resultType // Call 반환의 응답을 CallAdapter로 매핑 시킴. - override fun adapt(call: Call): Call> = PieceCall(call) + override fun adapt(call: Call): Call> = PieceCall(call, resultType, json) } private class PieceCall( private val delegate: Call, + private val responseType: Type, + private val json: Json, ) : Call> { override fun enqueue(callback: Callback>) { delegate.enqueue(object : Callback { @@ -61,20 +69,40 @@ private class PieceCall( } private fun Response.toResult(): Result { - if (isSuccessful) { - return Result.success(body() ?: Unit as T) - } else { - errorBody()?.let { - return Result.failure( + return try { + val rawBody = body() ?: return Result.failure( + HttpResponseException( + status = HttpResponseStatus.create(-1), + msg = "Response body is null" + ) + ) + + val serializer = json.serializersModule.serializer(responseType) + val apiResponse: ApiResponse = + json.decodeFromString(serializer, rawBody.toString()) as ApiResponse + + if (apiResponse.status == "success") { + apiResponse.data?.let { + Result.success(it) + } ?: Result.failure( + HttpResponseException( + status = HttpResponseStatus.create(-1), + msg = "API response data is null" + ) + ) + } else { + Result.failure( HttpResponseException( status = HttpResponseStatus.create(code()), - msg = "", // Todo + msg = apiResponse.message ?: "Unknown error" ) ) - } ?: return Result.failure( + } + } catch (e: Exception) { + Result.failure( HttpResponseException( status = HttpResponseStatus.create(-1), - msg = "알 수 없는 에러입니다." + msg = "Response parsing error: ${e.message}" ) ) } @@ -85,10 +113,10 @@ private class PieceCall( override fun execute(): Response> = throw UnsupportedOperationException("PieceCall doesn't support execute") - override fun clone(): Call> = PieceCall(delegate.clone()) + override fun clone(): Call> = PieceCall(delegate.clone(), responseType, json) override fun isExecuted(): Boolean = delegate.isExecuted override fun isCanceled(): Boolean = delegate.isCanceled override fun request(): Request = delegate.request() override fun timeout(): Timeout = delegate.timeout() override fun cancel() = delegate.cancel() -} \ No newline at end of file +} diff --git a/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt b/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt index 8833b9f8..c5deeee3 100644 --- a/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt +++ b/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt @@ -19,7 +19,9 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object RetrofitModule { - private val json = Json { + @Singleton + @Provides + fun provideJson(): Json = Json { ignoreUnknownKeys = true } @@ -40,6 +42,7 @@ object RetrofitModule { @Singleton @Provides fun providesAuthApi( + json: Json, okHttpClient: OkHttpClient, callAdapterFactory: PieceCallAdapterFactory, ): PieceApi = Retrofit.Builder() diff --git a/core/network/src/main/java/com/puzzle/network/model/auth/RequestAuthCodeResponse.kt b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt similarity index 53% rename from core/network/src/main/java/com/puzzle/network/model/auth/RequestAuthCodeResponse.kt rename to core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt index c608ef15..d29abf6a 100644 --- a/core/network/src/main/java/com/puzzle/network/model/auth/RequestAuthCodeResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt @@ -1,10 +1,10 @@ -package com.puzzle.network.model.auth +package com.puzzle.network.model import kotlinx.serialization.Serializable @Serializable -data class RequestAuthCodeResponse( +data class ApiResponse( val status: String?, val message: String?, - val data: String?, + val data: T? ) diff --git a/core/network/src/main/java/com/puzzle/network/model/auth/LoginOauthResponse.kt b/core/network/src/main/java/com/puzzle/network/model/auth/LoginOauthResponse.kt index 90161c89..843d8fb4 100644 --- a/core/network/src/main/java/com/puzzle/network/model/auth/LoginOauthResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/auth/LoginOauthResponse.kt @@ -4,15 +4,8 @@ import kotlinx.serialization.Serializable @Serializable data class LoginOauthResponse( - val status: String?, - val message: String?, - val data: Data?, -) { - @Serializable - data class Data( - val smsVerified: Boolean?, - val registerCompleted: Boolean?, - val accessToken: String?, - val refreshToken: String?, - ) -} + val smsVerified: Boolean?, + val registerCompleted: Boolean?, + val accessToken: String?, + val refreshToken: String?, +) diff --git a/core/network/src/main/java/com/puzzle/network/model/auth/VerifyAuthCodeResponse.kt b/core/network/src/main/java/com/puzzle/network/model/auth/VerifyAuthCodeResponse.kt index 49b3d287..2acf64d6 100644 --- a/core/network/src/main/java/com/puzzle/network/model/auth/VerifyAuthCodeResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/auth/VerifyAuthCodeResponse.kt @@ -4,15 +4,8 @@ import kotlinx.serialization.Serializable @Serializable data class VerifyAuthCodeResponse( - val status: String?, - val message: String?, - val data: Data?, -) { - @Serializable - data class Data( - val smsVerified: Boolean?, - val registerCompleted: Boolean?, - val accessToken: String?, - val refreshToken: String?, - ) -} + val smsVerified: Boolean?, + val registerCompleted: Boolean?, + val accessToken: String?, + val refreshToken: String?, +) diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt index 4f656224..8ffc0b22 100644 --- a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -7,16 +7,9 @@ import java.time.LocalDateTime @Serializable data class LoadTermsResponse( - val status: String?, - val message: String?, - val data: Data?, + val response: List?, ) { - @Serializable - data class Data( - val response: List?, - ) { - fun toDomain() = response?.map { it.toDomain() } ?: emptyList() - } + fun toDomain() = response?.map { it.toDomain() } ?: emptyList() } @Serializable From 8ee6aa2d61cd732b65a72859cbf850abb2736247 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 15:20:42 +0900 Subject: [PATCH 12/26] =?UTF-8?q?[PC-256]=20=EC=95=BD=EA=B4=80=EC=9D=84=20?= =?UTF-8?q?=EB=B6=88=EB=9F=AC=EC=99=94=EC=9D=84=EA=B2=BD=EC=9A=B0,=20?= =?UTF-8?q?=EC=9D=B4=EB=A5=BC=20Room=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/data/build.gradle.kts | 3 ++- .../data/repository/TermsRepositoryImpl.kt | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index f299343f..e903b3cc 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -10,4 +10,5 @@ android { dependencies { implementation(projects.core.domain) implementation(projects.core.network) -} \ No newline at end of file + implementation(projects.core.database) +} diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index c2f32b4a..64c45a7f 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -1,15 +1,30 @@ package com.puzzle.data.repository +import com.puzzle.database.model.terms.TermEntity +import com.puzzle.database.source.term.LocalTermDataSource import com.puzzle.domain.repository.TermsRepository import com.puzzle.network.source.TermDataSource import javax.inject.Inject class TermsRepositoryImpl @Inject constructor( private val termDataSource: TermDataSource, - private val localTermDataSource: TermDataSource, + private val localTermDataSource: LocalTermDataSource, ) : TermsRepository { override suspend fun loadTerms(): Result = runCatching { - val terms = termDataSource.loadTerms().getOrThrow() - localTermDataSource + val terms = termDataSource.loadTerms() + .getOrThrow() + .toDomain() + + val termsEntity = terms.map { + TermEntity( + termId = it.termId, + title = it.title, + content = it.content, + required = it.required, + startDate = it.startDate.toString(), + ) + } + + localTermDataSource.clearAndInsertTerms(termsEntity) } } \ No newline at end of file From 3f6b1abfc3f774de3b82da955aa9d9a01929fae8 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 5 Jan 2025 15:24:12 +0900 Subject: [PATCH 13/26] =?UTF-8?q?[PC-256]=20=EC=95=BD=EA=B4=80=EC=9D=84=20?= =?UTF-8?q?=EB=A1=9C=EC=BB=AC=EC=97=90=EC=84=9C=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/puzzle/data/repository/TermsRepositoryImpl.kt | 10 +++++++++- .../com/puzzle/domain/repository/TermsRepository.kt | 3 +++ .../main/java/com/puzzle/network/model/ApiResponse.kt | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 64c45a7f..70682601 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -2,7 +2,9 @@ package com.puzzle.data.repository import com.puzzle.database.model.terms.TermEntity import com.puzzle.database.source.term.LocalTermDataSource +import com.puzzle.domain.model.terms.Term import com.puzzle.domain.repository.TermsRepository +import com.puzzle.network.model.UNKNOWN_INT import com.puzzle.network.source.TermDataSource import javax.inject.Inject @@ -14,6 +16,7 @@ class TermsRepositoryImpl @Inject constructor( val terms = termDataSource.loadTerms() .getOrThrow() .toDomain() + .filter { it.termId != UNKNOWN_INT } val termsEntity = terms.map { TermEntity( @@ -27,4 +30,9 @@ class TermsRepositoryImpl @Inject constructor( localTermDataSource.clearAndInsertTerms(termsEntity) } -} \ No newline at end of file + + override suspend fun getTerms(): Result> = runCatching { + localTermDataSource.getTerms() + .map { it.toDomain() } + } +} diff --git a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt index 87836112..291dde57 100644 --- a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt +++ b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt @@ -1,5 +1,8 @@ package com.puzzle.domain.repository +import com.puzzle.domain.model.terms.Term + interface TermsRepository { suspend fun loadTerms(): Result + suspend fun getTerms(): Result> } \ No newline at end of file diff --git a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt index d29abf6a..e3e439c2 100644 --- a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt @@ -8,3 +8,6 @@ data class ApiResponse( val message: String?, val data: T? ) + +const val UNKNOWN_INT = -1 +const val UNKNOWN_STRING = "UNKNOWN" From a4845e46fec39a2a07b84f34d0fce55eb4f20ace Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 00:32:03 +0900 Subject: [PATCH 14/26] =?UTF-8?q?[PC-256]=20ResponseDTO=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EB=84=A4=EB=A6=AD=EC=9D=84=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20BaseResponse=EB=A1=9C=20=EB=B0=9B=EC=9D=84=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logic/piece.android.feature.gradle.kts | 1 - .../data/repository/TermsRepositoryImpl.kt | 7 +++ core/database/build.gradle.kts | 1 - .../java/com/puzzle/database/dao/TermDao.kt | 2 + .../domain/usecase/terms/GetTermsUseCase.kt | 11 ++++ .../domain/usecase/terms/LoadTermsUseCase.kt | 2 +- .../network/adapter/PieceCallAdapter.kt | 62 +++++-------------- .../network/model/terms/LoadTermsResponse.kt | 23 ++++--- .../puzzle/network/source/TermDataSource.kt | 5 +- .../registration/RegistrationViewModel.kt | 12 ++++ .../contract/RegistrationState.kt | 25 +------- gradle/libs.versions.toml | 23 +++---- .../com/puzzle/presentation/MainViewModel.kt | 4 +- 13 files changed, 74 insertions(+), 104 deletions(-) create mode 100644 core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt diff --git a/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts b/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts index c2bb49c4..e72a7035 100644 --- a/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts +++ b/build-logic/src/main/java/com/puzzle/build/logic/piece.android.feature.gradle.kts @@ -19,7 +19,6 @@ dependencies { implementation(project(":core:domain")) implementation(project(":core:navigation")) implementation(project(":core:common-ui")) - implementation(project(":core:common")) // implementation(project(":core:analytics")) val libs = project.extensions.libs diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 70682601..d52d5678 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.puzzle.data.repository +import android.util.Log import com.puzzle.database.model.terms.TermEntity import com.puzzle.database.source.term.LocalTermDataSource import com.puzzle.domain.model.terms.Term @@ -13,11 +14,15 @@ class TermsRepositoryImpl @Inject constructor( private val localTermDataSource: LocalTermDataSource, ) : TermsRepository { override suspend fun loadTerms(): Result = runCatching { + Log.d("test", "loadTerms 호출") + val terms = termDataSource.loadTerms() .getOrThrow() .toDomain() .filter { it.termId != UNKNOWN_INT } + Log.d("test", terms.toString()) + val termsEntity = terms.map { TermEntity( termId = it.termId, @@ -28,6 +33,8 @@ class TermsRepositoryImpl @Inject constructor( ) } + Log.d("test", termsEntity.toString()) + localTermDataSource.clearAndInsertTerms(termsEntity) } diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index 038def2f..6a86ca5f 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { implementation(projects.core.domain) implementation(projects.core.common) - implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) ksp(libs.androidx.room.compiler) } diff --git a/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt b/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt index 56da9c39..171604d8 100644 --- a/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt +++ b/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt @@ -1,11 +1,13 @@ package com.puzzle.database.dao +import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import com.puzzle.database.model.terms.TermEntity +@Dao interface TermDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTerms(vararg terms: TermEntity) diff --git a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt new file mode 100644 index 00000000..19ac9e85 --- /dev/null +++ b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt @@ -0,0 +1,11 @@ +package com.puzzle.domain.usecase.terms + +import com.puzzle.domain.model.terms.Term +import com.puzzle.domain.repository.TermsRepository +import javax.inject.Inject + +class GetTermsUseCase @Inject constructor( + private val termsRepository: TermsRepository, +) { + suspend operator fun invoke(): Result> = termsRepository.getTerms() +} diff --git a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt index 84145138..a9f7a8eb 100644 --- a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt +++ b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt @@ -7,4 +7,4 @@ class LoadTermsUseCase @Inject constructor( private val termsRepository: TermsRepository, ) { suspend operator fun invoke(): Result = termsRepository.loadTerms() -} \ No newline at end of file +} diff --git a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt index 0baa54e8..718a8e3e 100644 --- a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt +++ b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt @@ -2,9 +2,6 @@ package com.puzzle.network.adapter import com.puzzle.domain.model.error.HttpResponseException import com.puzzle.domain.model.error.HttpResponseStatus -import com.puzzle.network.model.ApiResponse -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer import okhttp3.Request import okio.Timeout import retrofit2.Call @@ -14,43 +11,32 @@ import retrofit2.Response import retrofit2.Retrofit import java.lang.reflect.ParameterizedType import java.lang.reflect.Type -import javax.inject.Inject import javax.inject.Singleton @Singleton -class PieceCallAdapterFactory @Inject constructor( - private val json: Json, -) : CallAdapter.Factory() { +class PieceCallAdapterFactory : CallAdapter.Factory() { override fun get( type: Type, - annotations: Array, + annotations: Array, retrofit: Retrofit - ): CallAdapter<*, *>? { - // 반환 타입의 최상위 객체가 Result 객체인지 확인, 아닐 경우 null 반환 + ): CallAdapter<*, *> { val wrapperType = getParameterUpperBound(0, type as ParameterizedType) - if (getRawType(wrapperType) != Result::class.java) return null - - // 해당 타입의 제네릭 타입을 가져옴 Result의 T를 뜻함 - val responseType = getParameterUpperBound(0, wrapperType as ParameterizedType) - return PieceCallAdapter(responseType, json) + return PieceCallAdapter(wrapperType) } } private class PieceCallAdapter( private val resultType: Type, - private val json: Json, ) : CallAdapter>> { // 반환 타입은 Result의 T임 override fun responseType(): Type = resultType // Call 반환의 응답을 CallAdapter로 매핑 시킴. - override fun adapt(call: Call): Call> = PieceCall(call, resultType, json) + override fun adapt(call: Call): Call> = PieceCall(call) } private class PieceCall( private val delegate: Call, - private val responseType: Type, - private val json: Json, ) : Call> { override fun enqueue(callback: Callback>) { delegate.enqueue(object : Callback { @@ -69,40 +55,20 @@ private class PieceCall( } private fun Response.toResult(): Result { - return try { - val rawBody = body() ?: return Result.failure( - HttpResponseException( - status = HttpResponseStatus.create(-1), - msg = "Response body is null" - ) - ) - - val serializer = json.serializersModule.serializer(responseType) - val apiResponse: ApiResponse = - json.decodeFromString(serializer, rawBody.toString()) as ApiResponse - - if (apiResponse.status == "success") { - apiResponse.data?.let { - Result.success(it) - } ?: Result.failure( - HttpResponseException( - status = HttpResponseStatus.create(-1), - msg = "API response data is null" - ) - ) - } else { - Result.failure( + if (isSuccessful) { + return Result.success(body() ?: Unit as T) + } else { + errorBody()?.let { + return Result.failure( HttpResponseException( status = HttpResponseStatus.create(code()), - msg = apiResponse.message ?: "Unknown error" + msg = "", // Todo ) ) - } - } catch (e: Exception) { - Result.failure( + } ?: return Result.failure( HttpResponseException( status = HttpResponseStatus.create(-1), - msg = "Response parsing error: ${e.message}" + msg = "알 수 없는 에러입니다." ) ) } @@ -113,7 +79,7 @@ private class PieceCall( override fun execute(): Response> = throw UnsupportedOperationException("PieceCall doesn't support execute") - override fun clone(): Call> = PieceCall(delegate.clone(), responseType, json) + override fun clone(): Call> = PieceCall(delegate.clone()) override fun isExecuted(): Boolean = delegate.isExecuted override fun isCanceled(): Boolean = delegate.isCanceled override fun request(): Request = delegate.request() diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt index 8ffc0b22..564e5a96 100644 --- a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -1,5 +1,6 @@ package com.puzzle.network.model.terms +import android.util.Log import com.puzzle.common.parseDateTime import com.puzzle.domain.model.terms.Term import kotlinx.serialization.Serializable @@ -7,9 +8,9 @@ import java.time.LocalDateTime @Serializable data class LoadTermsResponse( - val response: List?, + val responses: List?, ) { - fun toDomain() = response?.map { it.toDomain() } ?: emptyList() + fun toDomain() = responses?.map { it.toDomain() } ?: emptyList() } @Serializable @@ -20,11 +21,15 @@ data class TermResponse( val required: Boolean?, val startDate: String?, ) { - fun toDomain() = Term( - termId = termId ?: -1, - title = title ?: "UNKNOWN", - content = content ?: "UNKNOWN", - required = required ?: false, - startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN - ) + fun toDomain(): Term { + Log.d("test", this.toString()) + + return Term( + termId = termId ?: -1, + title = title ?: "UNKNOWN", + content = content ?: "UNKNOWN", + required = required ?: false, + startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN + ) + } } diff --git a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt index 56be6061..0036b5ef 100644 --- a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt @@ -1,6 +1,7 @@ package com.puzzle.network.source import com.puzzle.network.api.PieceApi +import com.puzzle.network.model.terms.LoadTermsResponse import javax.inject.Inject import javax.inject.Singleton @@ -8,5 +9,5 @@ import javax.inject.Singleton class TermDataSource @Inject constructor( private val pieceApi: PieceApi, ) { - suspend fun loadTerms() = pieceApi.loadTerms() -} \ No newline at end of file + suspend fun loadTerms(): Result = pieceApi.loadTerms() +} diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index d291912e..7a835571 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -7,6 +7,8 @@ import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory import com.puzzle.auth.graph.registration.contract.RegistrationIntent import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationState +import com.puzzle.domain.model.error.ErrorHelper +import com.puzzle.domain.usecase.terms.GetTermsUseCase import com.puzzle.navigation.NavigationHelper import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -20,7 +22,9 @@ import kotlinx.coroutines.launch class RegistrationViewModel @AssistedInject constructor( @Assisted initialState: RegistrationState, + private val getTermsUseCase: GetTermsUseCase, private val navigationHelper: NavigationHelper, + private val errorHelper: ErrorHelper, ) : MavericksViewModel(initialState) { private val intents = Channel(BUFFERED) @@ -29,11 +33,19 @@ class RegistrationViewModel @AssistedInject constructor( val sideEffect = _sideEffect.receiveAsFlow() init { + fetchTerms() + intents.receiveAsFlow() .onEach(::processIntent) .launchIn(viewModelScope) } + private fun fetchTerms() = viewModelScope.launch { + getTermsUseCase().onSuccess { + setState { copy(terms = it) } + }.onFailure { errorHelper.sendError(it) } + } + internal fun onIntent(intent: RegistrationIntent) = viewModelScope.launch { intents.send(intent) } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt index 5b77b3a5..0838963d 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt @@ -2,32 +2,9 @@ package com.puzzle.auth.graph.registration.contract import com.airbnb.mvrx.MavericksState import com.puzzle.domain.model.terms.Term -import java.time.LocalDateTime data class RegistrationState( - val terms: List = listOf( - Term( - termId = 1, - title = "서비스 이용약관", - content = "서비스 이용에 대한 약관 내용입니다.", - required = true, - startDate = LocalDateTime.parse("2024-01-01T00:00:00") - ), - Term( - termId = 2, - title = "개인정보 처리방침", - content = "개인정보 보호에 대한 약관 내용입니다.", - required = true, - startDate = LocalDateTime.parse("2024-01-01T00:00:00") - ), - Term( - termId = 3, - title = "위치 정보 이용약관", - content = "위치 정보 활용에 대한 약관 내용입니다.", - required = false, - startDate = LocalDateTime.parse("2024-01-01T00:00:00") - ), - ), + val terms: List = emptyList(), val termsCheckedInfo: MutableMap = mutableMapOf(), ) : MavericksState { val agreeAllTerms = terms.all { termsCheckedInfo.getOrDefault(it.termId, false) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56997315..e4cd6575 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,10 +23,6 @@ androidxConstraintlayout = "2.1.4" androidxSplashscreen = "1.0.1" # https://developer.android.com/training/data-storage/room androidxRoom = "2.6.1" -# https://developer.android.com/jetpack/androidx/releases/test -androidxTestRunner = "1.6.2" -androidxTestExt = "1.2.1" -androidxEspresso = "3.6.1" # https://developer.android.com/develop/ui/compose/bom/bom-mapping androidxComposeBom = "2024.12.01" # https://developer.android.com/jetpack/androidx/releases/navigation @@ -37,7 +33,7 @@ material = "1.12.0" ## Kotlin Symbol Processing # https://github.com/google/ksp/ -ksp = "2.0.0-1.0.22" +ksp = "2.1.0-1.0.29" ## Hilt # https://github.com/google/dagger/releases @@ -55,7 +51,7 @@ retrofit = "2.11.0" ## Kotlin # https://github.com/JetBrains/kotlin -kotlin = "2.0.0" +kotlin = "2.1.0" # https://github.com/Kotlin/kotlinx.serialization kotlinxSerializationJson = "1.7.0" # https://github.com/Kotlin/kotlinx.coroutines @@ -65,12 +61,14 @@ kotlinxCoroutine = "1.9.0-RC" # https://github.com/junit-team/junit4 junit4 = "4.13.2" junitJupiter = "5.8.2" - # https://mockk.io/ mockk = "1.13.11" - # https://github.com/pinterest/ktlint ktlint = "12.1.1" +# https://developer.android.com/jetpack/androidx/releases/test +androidxTestRunner = "1.6.2" +androidxTestExt = "1.2.1" +androidxEspresso = "3.6.1" ## firebase googleServices = "4.4.2" @@ -80,19 +78,15 @@ messaging = "24.1.0" # https://coil-kt.github.io/coil/#jetpack-compose coil = "2.7.0" - # https://airbnb.io/lottie/#/android-compose lottie-compose = "6.5.2" - +# https://github.com/skydoves/Cloudy +cloudy = "0.2.4" # https://developers.kakao.com/docs/latest/ko/android/getting-started#apply-sdk kakao = "2.20.6" - # https://airbnb.io/mavericks/#/setup mavericks = "3.0.9" -# https://github.com/skydoves/Cloudy -cloudy = "0.2.4" - ## Amplitude # https://amplitude.com/docs/sdks/analytics/android amplitudeAnalytics = "1.16.8" @@ -125,7 +119,6 @@ androidx-compose-navigation = { group = "androidx.navigation", name = "navigatio compose-compiler-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" } -androidx-room-runtime = { group = "androidx.room", name = " room-runtime", version.ref = "androidxRoom" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidxRoom" } coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutine" } diff --git a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt index e80e90ad..4295a87d 100644 --- a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt +++ b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt @@ -38,9 +38,7 @@ class MainViewModel @Inject constructor( } private fun loadTerms() = viewModelScope.launch { - loadTermsUseCase().onSuccess { - // Todo - }.onFailure { + loadTermsUseCase().onFailure { errorHelper.sendError(it) } } From 81430322da666cbe794c6bf82a477f621fb2d804 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 02:20:18 +0900 Subject: [PATCH 15/26] =?UTF-8?q?[PC-256]=20ApiResponse=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=20=ED=95=A8=EC=88=98=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/adapter/PieceCallAdapter.kt | 16 +++++++++----- .../java/com/puzzle/network/api/PieceApi.kt | 9 ++++---- .../com/puzzle/network/di/RetrofitModule.kt | 2 +- .../com/puzzle/network/model/ApiResponse.kt | 15 ++++++++++++- .../network/model/terms/LoadTermsResponse.kt | 21 ++++++++----------- .../puzzle/network/source/AuthDataSource.kt | 8 ++++--- .../puzzle/network/source/TermDataSource.kt | 3 ++- 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt index 718a8e3e..2d42936b 100644 --- a/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt +++ b/core/network/src/main/java/com/puzzle/network/adapter/PieceCallAdapter.kt @@ -11,17 +11,23 @@ import retrofit2.Response import retrofit2.Retrofit import java.lang.reflect.ParameterizedType import java.lang.reflect.Type +import javax.inject.Inject import javax.inject.Singleton @Singleton -class PieceCallAdapterFactory : CallAdapter.Factory() { +class PieceCallAdapterFactory @Inject constructor() : CallAdapter.Factory() { override fun get( type: Type, annotations: Array, retrofit: Retrofit - ): CallAdapter<*, *> { + ): CallAdapter<*, *>? { + // 반환 타입의 최상위 객체가 Result 객체인지 확인, 아닐 경우 null 반환 val wrapperType = getParameterUpperBound(0, type as ParameterizedType) - return PieceCallAdapter(wrapperType) + if (getRawType(wrapperType) != Result::class.java) return null + + // 해당 타입의 제네릭 타입을 가져옴 Result의 T를 뜻함 + val responseType = getParameterUpperBound(0, wrapperType as ParameterizedType) + return PieceCallAdapter(responseType) } } @@ -62,13 +68,13 @@ private class PieceCall( return Result.failure( HttpResponseException( status = HttpResponseStatus.create(code()), - msg = "", // Todo + msg = "일시적인 서버 에러입니다. 계속해서 반복될 경우 문의 하기를 이용해주세요.", ) ) } ?: return Result.failure( HttpResponseException( status = HttpResponseStatus.create(-1), - msg = "알 수 없는 에러입니다." + msg = "일시적인 서버 에러입니다. 계속해서 반복될 경우 문의 하기를 이용해주세요." ) ) } diff --git a/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt b/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt index 3d9447d0..58ff540b 100644 --- a/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt +++ b/core/network/src/main/java/com/puzzle/network/api/PieceApi.kt @@ -1,5 +1,6 @@ package com.puzzle.network.api +import com.puzzle.network.model.ApiResponse import com.puzzle.network.model.auth.LoginOauthRequest import com.puzzle.network.model.auth.LoginOauthResponse import com.puzzle.network.model.auth.RequestAuthCodeRequest @@ -12,14 +13,14 @@ import retrofit2.http.POST interface PieceApi { @POST("/api/login/oauth") - suspend fun loginOauth(@Body loginOauthRequest: LoginOauthRequest): Result + suspend fun loginOauth(@Body loginOauthRequest: LoginOauthRequest): Result> @POST("/api/register/sms/auth/code") - suspend fun requestAuthCode(@Body requestAuthCodeRequest: RequestAuthCodeRequest): Result + suspend fun requestAuthCode(@Body requestAuthCodeRequest: RequestAuthCodeRequest): Result> @POST("/api/register/sms/auth/code/verify") - suspend fun verifyAuthCode(@Body verifyAuthCodeRequest: VerifyAuthCodeRequest): Result + suspend fun verifyAuthCode(@Body verifyAuthCodeRequest: VerifyAuthCodeRequest): Result> @GET("/api/terms") - suspend fun loadTerms(): Result + suspend fun loadTerms(): Result> } diff --git a/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt b/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt index c5deeee3..e32c9e95 100644 --- a/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt +++ b/core/network/src/main/java/com/puzzle/network/di/RetrofitModule.kt @@ -41,7 +41,7 @@ object RetrofitModule { @Singleton @Provides - fun providesAuthApi( + fun providesPieceApi( json: Json, okHttpClient: OkHttpClient, callAdapterFactory: PieceCallAdapterFactory, diff --git a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt index e3e439c2..90e9c72c 100644 --- a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt @@ -1,13 +1,26 @@ package com.puzzle.network.model +import com.puzzle.domain.model.error.HttpResponseException +import com.puzzle.domain.model.error.HttpResponseStatus import kotlinx.serialization.Serializable @Serializable data class ApiResponse( val status: String?, val message: String?, - val data: T? + val data: T?, ) +internal fun Result>.getResult(): Result { + return this.map { response -> + response.data ?: return Result.failure( + HttpResponseException( + status = HttpResponseStatus.InternalError, + msg = "일시적인 서버 에러입니다. 계속해서 반복될 경우 문의 하기를 이용해주세요.", + ) + ) + } +} + const val UNKNOWN_INT = -1 const val UNKNOWN_STRING = "UNKNOWN" diff --git a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt index 564e5a96..e2b6cfa4 100644 --- a/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/terms/LoadTermsResponse.kt @@ -1,8 +1,9 @@ package com.puzzle.network.model.terms -import android.util.Log import com.puzzle.common.parseDateTime import com.puzzle.domain.model.terms.Term +import com.puzzle.network.model.UNKNOWN_INT +import com.puzzle.network.model.UNKNOWN_STRING import kotlinx.serialization.Serializable import java.time.LocalDateTime @@ -21,15 +22,11 @@ data class TermResponse( val required: Boolean?, val startDate: String?, ) { - fun toDomain(): Term { - Log.d("test", this.toString()) - - return Term( - termId = termId ?: -1, - title = title ?: "UNKNOWN", - content = content ?: "UNKNOWN", - required = required ?: false, - startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN - ) - } + fun toDomain(): Term = Term( + termId = termId ?: UNKNOWN_INT, + title = title ?: UNKNOWN_STRING, + content = content ?: UNKNOWN_STRING, + required = required ?: false, + startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN + ) } diff --git a/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt index e79faa6b..620cc90e 100644 --- a/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt @@ -7,6 +7,7 @@ import com.puzzle.network.model.auth.LoginOauthResponse import com.puzzle.network.model.auth.RequestAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeResponse +import com.puzzle.network.model.getResult import javax.inject.Inject import javax.inject.Singleton @@ -20,10 +21,11 @@ class AuthDataSource @Inject constructor( providerName = provider.apiValue, token = token, ) - ) + ).getResult() - suspend fun requestAuthCode(phoneNumber: String): Result = + suspend fun requestAuthCode(phoneNumber: String): Result = pieceApi.requestAuthCode(RequestAuthCodeRequest(phoneNumber)) + .getResult() suspend fun verifyAuthCode(phoneNumber: String, code: String): Result = pieceApi.verifyAuthCode( @@ -31,5 +33,5 @@ class AuthDataSource @Inject constructor( phoneNumber = phoneNumber, code = code, ) - ) + ).getResult() } diff --git a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt index 0036b5ef..7be7fb7b 100644 --- a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt @@ -1,6 +1,7 @@ package com.puzzle.network.source import com.puzzle.network.api.PieceApi +import com.puzzle.network.model.getResult import com.puzzle.network.model.terms.LoadTermsResponse import javax.inject.Inject import javax.inject.Singleton @@ -9,5 +10,5 @@ import javax.inject.Singleton class TermDataSource @Inject constructor( private val pieceApi: PieceApi, ) { - suspend fun loadTerms(): Result = pieceApi.loadTerms() + suspend fun loadTerms(): Result = pieceApi.loadTerms().getResult() } From 62759f9eff38144325058b6cc77dc4efbd1a93c0 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 03:24:15 +0900 Subject: [PATCH 16/26] =?UTF-8?q?[PC-256]=20AndroidTest=EB=A5=BC=20?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20TestRunne?= =?UTF-8?q?r=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/puzzle/build/logic/TestAndroid.kt | 4 + .../data/repository/TermsRepositoryImpl.kt | 7 -- .../java/com/puzzle/data/ExampleUnitTest.kt | 17 ---- .../repository/TermsRepositoryImplTest.kt | 5 ++ .../database/ExampleInstrumentedTest.kt | 24 ----- .../com/puzzle/database/dao/TermsDaoTest.kt | 88 +++++++++++++++++++ .../java/com/puzzle/database/PieceDatabase.kt | 4 +- .../database/dao/{TermDao.kt => TermsDao.kt} | 2 +- .../di/{DaoModule.kt => DaosModule.kt} | 8 +- .../com/puzzle/database/di/DatabaseModule.kt | 2 +- .../source/term/LocalTermDataSource.kt | 8 +- .../puzzle/auth/ExampleInstrumentedTest.kt | 22 ----- .../matching/ExampleInstrumentedTest.kt | 22 ----- .../puzzle/mypage/ExampleInstrumentedTest.kt | 22 ----- .../puzzle/setting/ExampleInstrumentedTest.kt | 22 ----- 15 files changed, 109 insertions(+), 148 deletions(-) delete mode 100644 core/data/src/test/java/com/puzzle/data/ExampleUnitTest.kt create mode 100644 core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt delete mode 100644 core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt create mode 100644 core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt rename core/database/src/main/java/com/puzzle/database/dao/{TermDao.kt => TermsDao.kt} (96%) rename core/database/src/main/java/com/puzzle/database/di/{DaoModule.kt => DaosModule.kt} (68%) delete mode 100644 feature/auth/src/androidTest/java/com/puzzle/auth/ExampleInstrumentedTest.kt delete mode 100644 feature/matching/src/androidTest/java/com/puzzle/matching/ExampleInstrumentedTest.kt delete mode 100644 feature/mypage/src/androidTest/java/com/puzzle/mypage/ExampleInstrumentedTest.kt delete mode 100644 feature/setting/src/androidTest/java/com/puzzle/setting/ExampleInstrumentedTest.kt diff --git a/build-logic/src/main/java/com/puzzle/build/logic/TestAndroid.kt b/build-logic/src/main/java/com/puzzle/build/logic/TestAndroid.kt index 58291218..43440561 100644 --- a/build-logic/src/main/java/com/puzzle/build/logic/TestAndroid.kt +++ b/build-logic/src/main/java/com/puzzle/build/logic/TestAndroid.kt @@ -15,6 +15,10 @@ internal fun Project.configureJUnitAndroid() { unitTests.all { it.useJUnitPlatform() } } + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + val libs = extensions.libs dependencies { "androidTestImplementation"(libs.findLibrary("androidx.test.ext").get()) diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index d52d5678..70682601 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.puzzle.data.repository -import android.util.Log import com.puzzle.database.model.terms.TermEntity import com.puzzle.database.source.term.LocalTermDataSource import com.puzzle.domain.model.terms.Term @@ -14,15 +13,11 @@ class TermsRepositoryImpl @Inject constructor( private val localTermDataSource: LocalTermDataSource, ) : TermsRepository { override suspend fun loadTerms(): Result = runCatching { - Log.d("test", "loadTerms 호출") - val terms = termDataSource.loadTerms() .getOrThrow() .toDomain() .filter { it.termId != UNKNOWN_INT } - Log.d("test", terms.toString()) - val termsEntity = terms.map { TermEntity( termId = it.termId, @@ -33,8 +28,6 @@ class TermsRepositoryImpl @Inject constructor( ) } - Log.d("test", termsEntity.toString()) - localTermDataSource.clearAndInsertTerms(termsEntity) } diff --git a/core/data/src/test/java/com/puzzle/data/ExampleUnitTest.kt b/core/data/src/test/java/com/puzzle/data/ExampleUnitTest.kt deleted file mode 100644 index bd567504..00000000 --- a/core/data/src/test/java/com/puzzle/data/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.puzzle.data - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt new file mode 100644 index 00000000..3570a526 --- /dev/null +++ b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt @@ -0,0 +1,5 @@ +package com.puzzle.data.repository + +import org.junit.jupiter.api.Assertions.* + +class TermsRepositoryImplTest \ No newline at end of file diff --git a/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt b/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt deleted file mode 100644 index 7c24a6d6..00000000 --- a/core/database/src/androidTest/java/com/puzzle/database/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.puzzle.database - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.puzzle.database.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt new file mode 100644 index 00000000..19424619 --- /dev/null +++ b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt @@ -0,0 +1,88 @@ +package com.puzzle.database.dao + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.puzzle.database.PieceDatabase +import com.puzzle.database.model.terms.TermEntity +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TermsDaoTest { + private lateinit var termsDao: TermsDao + private lateinit var db: PieceDatabase + + @Before + fun setUp() { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder( + context = context, + klass = PieceDatabase::class.java + ).build() + termsDao = db.termsDao() + } + + @After + fun tearDown() { + db.close() + } + + @Test + fun 약관을_삽입하고_조회한다() = runTest { + // given + val expected = listOf( + TermEntity(1, "이용약관", "내용1", true, "2024-01-01"), + TermEntity(2, "개인정보처리방침", "내용2", false, "2024-01-02") + ) + + // when + termsDao.insertTerms(*expected.toTypedArray()) + val actual = termsDao.getTerms() + + // then + assertEquals(expected, actual) + } + + @Test + fun 약관을_모두_삭제한다() = runTest { + // given + val terms = listOf( + TermEntity(1, "이용약관", "내용1", true, "2024-01-01"), + TermEntity(2, "개인정보처리방침", "내용2", false, "2024-01-02") + ) + termsDao.insertTerms(*terms.toTypedArray()) + + // when + termsDao.clearTerms() + val actual = termsDao.getTerms() + + // then + val expected = emptyList() + assertEquals(expected, actual) + } + + @Test + fun 약관을_삭제하고_다시_삽입한다() = runTest { + // given + val oldTerms = listOf( + TermEntity(1, "이전 약관", "이전 내용", true, "2024-01-01") + ) + termsDao.insertTerms(*oldTerms.toTypedArray()) + + // when + val expected = listOf( + TermEntity(2, "새로운 약관", "새로운 내용", false, "2024-02-01") + ) + termsDao.clearAndInsertTerms(*expected.toTypedArray()) + val actual = termsDao.getTerms() + + // then + assertEquals(expected, actual) + } +} \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt index 08da566b..28716a21 100644 --- a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt +++ b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt @@ -2,7 +2,7 @@ package com.puzzle.database import androidx.room.Database import androidx.room.RoomDatabase -import com.puzzle.database.dao.TermDao +import com.puzzle.database.dao.TermsDao import com.puzzle.database.model.terms.TermEntity @Database( @@ -12,7 +12,7 @@ import com.puzzle.database.model.terms.TermEntity version = 1, ) internal abstract class PieceDatabase : RoomDatabase() { - abstract fun termDao(): TermDao + abstract fun termsDao(): TermsDao companion object { internal const val NAME = "piece-database" diff --git a/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt b/core/database/src/main/java/com/puzzle/database/dao/TermsDao.kt similarity index 96% rename from core/database/src/main/java/com/puzzle/database/dao/TermDao.kt rename to core/database/src/main/java/com/puzzle/database/dao/TermsDao.kt index 171604d8..abed2014 100644 --- a/core/database/src/main/java/com/puzzle/database/dao/TermDao.kt +++ b/core/database/src/main/java/com/puzzle/database/dao/TermsDao.kt @@ -8,7 +8,7 @@ import androidx.room.Transaction import com.puzzle.database.model.terms.TermEntity @Dao -interface TermDao { +interface TermsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTerms(vararg terms: TermEntity) diff --git a/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt b/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt similarity index 68% rename from core/database/src/main/java/com/puzzle/database/di/DaoModule.kt rename to core/database/src/main/java/com/puzzle/database/di/DaosModule.kt index 9cece83c..6a432fde 100644 --- a/core/database/src/main/java/com/puzzle/database/di/DaoModule.kt +++ b/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt @@ -1,7 +1,7 @@ package com.puzzle.database.di import com.puzzle.database.PieceDatabase -import com.puzzle.database.dao.TermDao +import com.puzzle.database.dao.TermsDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -9,9 +9,9 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -internal object DaoModule { +internal object DaosModule { @Provides - fun providesTopicsDao( + fun providesTermsDao( database: PieceDatabase, - ): TermDao = database.termDao() + ): TermsDao = database.termsDao() } \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt index 2dd6d0a2..062c15eb 100644 --- a/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt +++ b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt @@ -15,7 +15,7 @@ import javax.inject.Singleton internal object DatabaseModule { @Provides @Singleton - fun providesNiaDatabase( + fun providesPieceDatabase( @ApplicationContext context: Context, ): PieceDatabase = Room.databaseBuilder( context, diff --git a/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt b/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt index a8d57b87..3c9da99e 100644 --- a/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt +++ b/core/database/src/main/java/com/puzzle/database/source/term/LocalTermDataSource.kt @@ -1,15 +1,15 @@ package com.puzzle.database.source.term -import com.puzzle.database.dao.TermDao +import com.puzzle.database.dao.TermsDao import com.puzzle.database.model.terms.TermEntity import javax.inject.Inject import javax.inject.Singleton @Singleton class LocalTermDataSource @Inject constructor( - private val termDao: TermDao, + private val termsDao: TermsDao, ) { - suspend fun getTerms() = termDao.getTerms() + suspend fun getTerms() = termsDao.getTerms() suspend fun clearAndInsertTerms(terms: List) = - termDao.clearAndInsertTerms(*terms.toTypedArray()) + termsDao.clearAndInsertTerms(*terms.toTypedArray()) } diff --git a/feature/auth/src/androidTest/java/com/puzzle/auth/ExampleInstrumentedTest.kt b/feature/auth/src/androidTest/java/com/puzzle/auth/ExampleInstrumentedTest.kt deleted file mode 100644 index 95428875..00000000 --- a/feature/auth/src/androidTest/java/com/puzzle/auth/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.puzzle.auth - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.auth.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/feature/matching/src/androidTest/java/com/puzzle/matching/ExampleInstrumentedTest.kt b/feature/matching/src/androidTest/java/com/puzzle/matching/ExampleInstrumentedTest.kt deleted file mode 100644 index 431e3e19..00000000 --- a/feature/matching/src/androidTest/java/com/puzzle/matching/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.puzzle.matching - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.matching.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/feature/mypage/src/androidTest/java/com/puzzle/mypage/ExampleInstrumentedTest.kt b/feature/mypage/src/androidTest/java/com/puzzle/mypage/ExampleInstrumentedTest.kt deleted file mode 100644 index 5c8e1c43..00000000 --- a/feature/mypage/src/androidTest/java/com/puzzle/mypage/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.puzzle.mypage - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.mypage.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/feature/setting/src/androidTest/java/com/puzzle/setting/ExampleInstrumentedTest.kt b/feature/setting/src/androidTest/java/com/puzzle/setting/ExampleInstrumentedTest.kt deleted file mode 100644 index 8d6f6fc1..00000000 --- a/feature/setting/src/androidTest/java/com/puzzle/setting/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.puzzle.setting - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.etc.test", appContext.packageName) - } -} \ No newline at end of file From b34d8c2b86d92bf9ed729d7d0d900e9dbf1c8767 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 03:36:53 +0900 Subject: [PATCH 17/26] =?UTF-8?q?[PC-256]=20Database=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../com/puzzle/database/dao/TermsDaoTest.kt | 19 ++++++++++--------- .../java/com/puzzle/database/PieceDatabase.kt | 7 ++++--- .../database/converter/PieceConverters.kt | 17 +++++++++++++++++ .../puzzle/database/model/terms/TermEntity.kt | 5 ++--- 4 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt diff --git a/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt index 19424619..67924fb5 100644 --- a/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt +++ b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.puzzle.common.parseDateTime import com.puzzle.database.PieceDatabase import com.puzzle.database.model.terms.TermEntity import kotlinx.coroutines.test.runTest @@ -34,11 +35,11 @@ class TermsDaoTest { } @Test - fun 약관을_삽입하고_조회한다() = runTest { + fun 약관을_삽입하고_조회할_수_있다() = runTest { // given val expected = listOf( - TermEntity(1, "이용약관", "내용1", true, "2024-01-01"), - TermEntity(2, "개인정보처리방침", "내용2", false, "2024-01-02") + TermEntity(1, "이용약관", "내용1", true, "2024-06-01T00:00:00".parseDateTime()), + TermEntity(2, "개인정보처리방침", "내용2", false, "2024-06-01T00:00:00".parseDateTime()) ) // when @@ -50,11 +51,11 @@ class TermsDaoTest { } @Test - fun 약관을_모두_삭제한다() = runTest { + fun 약관을_모두_삭제할_수_있다() = runTest { // given val terms = listOf( - TermEntity(1, "이용약관", "내용1", true, "2024-01-01"), - TermEntity(2, "개인정보처리방침", "내용2", false, "2024-01-02") + TermEntity(1, "이용약관", "내용1", true, "2024-06-01T00:00:00".parseDateTime()), + TermEntity(2, "개인정보처리방침", "내용2", false, "2024-06-01T00:00:00".parseDateTime()) ) termsDao.insertTerms(*terms.toTypedArray()) @@ -68,16 +69,16 @@ class TermsDaoTest { } @Test - fun 약관을_삭제하고_다시_삽입한다() = runTest { + fun 이전_약관을_삭제하고_새로운_약관을_삽입할_수_있다() = runTest { // given val oldTerms = listOf( - TermEntity(1, "이전 약관", "이전 내용", true, "2024-01-01") + TermEntity(1, "이전 약관", "이전 내용", true, "2024-06-01T00:00:00".parseDateTime()) ) termsDao.insertTerms(*oldTerms.toTypedArray()) // when val expected = listOf( - TermEntity(2, "새로운 약관", "새로운 내용", false, "2024-02-01") + TermEntity(2, "새로운 약관", "새로운 내용", false, "2024-06-01T00:00:00".parseDateTime()) ) termsDao.clearAndInsertTerms(*expected.toTypedArray()) val actual = termsDao.getTerms() diff --git a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt index 28716a21..237f14ed 100644 --- a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt +++ b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt @@ -2,15 +2,16 @@ package com.puzzle.database import androidx.room.Database import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.puzzle.database.converter.PieceConverters import com.puzzle.database.dao.TermsDao import com.puzzle.database.model.terms.TermEntity @Database( - entities = [ - TermEntity::class, - ], + entities = [TermEntity::class], version = 1, ) +@TypeConverters(PieceConverters::class) internal abstract class PieceDatabase : RoomDatabase() { abstract fun termsDao(): TermsDao diff --git a/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt b/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt new file mode 100644 index 00000000..231c1068 --- /dev/null +++ b/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt @@ -0,0 +1,17 @@ +package com.puzzle.database.converter + +import androidx.room.TypeConverter +import com.puzzle.common.parseDateTime +import java.time.LocalDateTime + +class PieceConverters { + @TypeConverter + fun fromLocalDate(date: LocalDateTime?): String? { + return date?.toString() + } + + @TypeConverter + fun toLocalDate(dateString: String?): LocalDateTime? { + return dateString?.parseDateTime() + } +} \ No newline at end of file diff --git a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt index 14756fc8..824246de 100644 --- a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt +++ b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt @@ -3,7 +3,6 @@ package com.puzzle.database.model.terms import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.puzzle.common.parseDateTime import com.puzzle.domain.model.terms.Term import java.time.LocalDateTime @@ -14,13 +13,13 @@ data class TermEntity( val title: String?, val content: String?, val required: Boolean?, - @ColumnInfo(name = "start_date") val startDate: String?, + @ColumnInfo(name = "start_date") val startDate: LocalDateTime?, ) { fun toDomain() = Term( termId = termId ?: -1, title = title ?: "UNKNOWN", content = content ?: "UNKNOWN", required = required ?: false, - startDate = startDate?.parseDateTime() ?: LocalDateTime.MIN + startDate = startDate ?: LocalDateTime.MIN ) } From 854bdb0668dd7b277507c34df094b263f33fc986 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 03:42:56 +0900 Subject: [PATCH 18/26] =?UTF-8?q?[PC-256]=20TimeUtil=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/puzzle/common/TimeUtilTest.kt | 47 +++++++++++++++++++ .../java/com/puzzle/auth/ExampleUnitTest.kt | 16 ------- 2 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt delete mode 100644 feature/auth/src/test/java/com/puzzle/auth/ExampleUnitTest.kt diff --git a/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt b/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt new file mode 100644 index 00000000..4b84e72e --- /dev/null +++ b/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt @@ -0,0 +1,47 @@ +package com.puzzle.common + +import org.junit.Assert.assertEquals +import org.junit.jupiter.api.Test +import java.time.LocalDateTime + +class TimeUtilTest { + + @Test + fun `올바른 형식의 문자열을 LocalDateTime으로 변환할 수 있다`() { + // given + val dateTimeString = "2024-06-01T00:00:00" + val expected = LocalDateTime.parse(dateTimeString) + + // when + val actual = dateTimeString.parseDateTime() + + // then + assertEquals(expected, actual) + } + + @Test + fun `null 값을 파싱하려고 할 경우 LocalDateTime_MIN을 반환한다`() { + // given + val nullString: String? = null + val expected = LocalDateTime.MIN + + // when + val actual = nullString.parseDateTime() + + // then + assertEquals(expected, actual) + } + + @Test + fun `형식에 맞지 않은 문자열을 파싱하려고 할 경우 LocalDateTime_MIN을 반환한다`() { + // given + val invalidString = "invalid-date-format" + val expected = LocalDateTime.MIN + + // when + val actual = invalidString.parseDateTime() + + // then + assertEquals(expected, actual) + } +} \ No newline at end of file diff --git a/feature/auth/src/test/java/com/puzzle/auth/ExampleUnitTest.kt b/feature/auth/src/test/java/com/puzzle/auth/ExampleUnitTest.kt deleted file mode 100644 index 1214d72a..00000000 --- a/feature/auth/src/test/java/com/puzzle/auth/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.puzzle.auth - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file From db3ca77612f7d60c8deb17fb273cd1756b70b383 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 04:05:33 +0900 Subject: [PATCH 19/26] =?UTF-8?q?[PC-256]=20TermsRepositoryImpl=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../puzzle/data/ExampleInstrumentedTest.kt | 24 ---- .../data/repository/TermsRepositoryImpl.kt | 2 +- .../repository/TermsRepositoryImplTest.kt | 105 +++++++++++++++++- 3 files changed, 104 insertions(+), 27 deletions(-) delete mode 100644 core/data/src/androidTest/java/com/puzzle/data/ExampleInstrumentedTest.kt diff --git a/core/data/src/androidTest/java/com/puzzle/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/puzzle/data/ExampleInstrumentedTest.kt deleted file mode 100644 index 60180280..00000000 --- a/core/data/src/androidTest/java/com/puzzle/data/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.puzzle.data - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.puzzle.data.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 70682601..4659fd5b 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -24,7 +24,7 @@ class TermsRepositoryImpl @Inject constructor( title = it.title, content = it.content, required = it.required, - startDate = it.startDate.toString(), + startDate = it.startDate, ) } diff --git a/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt index 3570a526..9dafd3a8 100644 --- a/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt +++ b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt @@ -1,5 +1,106 @@ package com.puzzle.data.repository -import org.junit.jupiter.api.Assertions.* +import com.puzzle.database.source.term.LocalTermDataSource +import com.puzzle.network.model.UNKNOWN_INT +import com.puzzle.network.model.terms.LoadTermsResponse +import com.puzzle.network.model.terms.TermResponse +import com.puzzle.network.source.TermDataSource +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.just +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test -class TermsRepositoryImplTest \ No newline at end of file +class TermsRepositoryImplTest { + + private lateinit var termDataSource: TermDataSource + private lateinit var localTermDataSource: LocalTermDataSource + private lateinit var termsRepository: TermsRepositoryImpl + + @BeforeEach + fun setUp() { + termDataSource = mockk() + localTermDataSource = mockk() + termsRepository = TermsRepositoryImpl(termDataSource, localTermDataSource) + } + + @Test + fun `약관을 새로 갱신할 경우 id값이 올바르게 내려오지 않은 약관은 무시한다`() = runTest { + // given + val invalidTerm = TermResponse( + termId = UNKNOWN_INT, + title = "Invalid", + content = "Invalid Content", + required = false, + startDate = "2024-06-01T00:00:00", + ) + val validTerm = TermResponse( + termId = 1, + title = "Valid", + content = "Valid Content", + required = true, + startDate = "2024-06-01T00:00:00", + ) + + coEvery { termDataSource.loadTerms() } returns + Result.success(LoadTermsResponse(listOf(invalidTerm, validTerm))) + coEvery { localTermDataSource.clearAndInsertTerms(any()) } just Runs + + // when + val result = termsRepository.loadTerms() + + // then + assertTrue(result.isSuccess) + coVerify(exactly = 1) { + localTermDataSource.clearAndInsertTerms( + match { + it.size == 1 && it.first().termId == validTerm.termId + } + ) + } + } + + @Test + fun `갱신한 데이터는 로컬 데이터베이스에 저장한다`() = runTest { + // given + val validTerms = listOf( + TermResponse( + termId = 1, + title = "Valid1", + content = "Content1", + required = true, + startDate = "2024-06-01T00:00:00" + ), + TermResponse( + termId = 2, + title = "Valid2", + content = "Content2", + required = false, + startDate = "2024-06-01T00:00:00" + ) + ) + + coEvery { termDataSource.loadTerms() } returns Result.success(LoadTermsResponse(validTerms)) + coEvery { localTermDataSource.clearAndInsertTerms(any()) } just Runs + + // when + termsRepository.loadTerms() + + // then + coVerify(exactly = 1) { + localTermDataSource.clearAndInsertTerms( + match { + it.size == validTerms.size && it.all { entity -> + validTerms.any { term -> + term.termId == entity.termId && term.title == entity.title + } + } + } + ) + } + } +} From 526a328b5ace58e103dfad22b688c52db958130d Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 6 Jan 2025 04:20:29 +0900 Subject: [PATCH 20/26] =?UTF-8?q?[PC-256]=20LocalDateBase=20Entity=20Nulla?= =?UTF-8?q?lbe=20->=20NonNullable=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../main/res/xml/network_security_config.xml | 2 +- .../kotlin/com/puzzle/common/TimeUtilTest.kt | 2 +- .../com/puzzle/database/dao/TermsDaoTest.kt | 2 +- .../java/com/puzzle/database/PieceDatabase.kt | 2 +- .../database/converter/PieceConverters.kt | 2 +- .../java/com/puzzle/database/di/DaosModule.kt | 2 +- .../com/puzzle/database/di/DatabaseModule.kt | 2 +- .../puzzle/database/model/terms/TermEntity.kt | 20 +++++++++---------- .../domain/repository/TermsRepository.kt | 2 +- .../graph/registration/RegistrationScreen.kt | 2 +- settings.gradle.kts | 12 ++++++----- 12 files changed, 27 insertions(+), 25 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e30e1265..f7e77992 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,4 +41,4 @@ dependencies { implementation(projects.presentation) implementation(projects.core.data) -} \ No newline at end of file +} diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 84da531b..85e7ad8e 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt b/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt index 4b84e72e..c7a765f4 100644 --- a/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt +++ b/core/common/src/test/kotlin/com/puzzle/common/TimeUtilTest.kt @@ -44,4 +44,4 @@ class TimeUtilTest { // then assertEquals(expected, actual) } -} \ No newline at end of file +} diff --git a/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt index 67924fb5..260fb6df 100644 --- a/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt +++ b/core/database/src/androidTest/java/com/puzzle/database/dao/TermsDaoTest.kt @@ -86,4 +86,4 @@ class TermsDaoTest { // then assertEquals(expected, actual) } -} \ No newline at end of file +} diff --git a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt index 237f14ed..52685244 100644 --- a/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt +++ b/core/database/src/main/java/com/puzzle/database/PieceDatabase.kt @@ -18,4 +18,4 @@ internal abstract class PieceDatabase : RoomDatabase() { companion object { internal const val NAME = "piece-database" } -} \ No newline at end of file +} diff --git a/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt b/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt index 231c1068..5cb2f1df 100644 --- a/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt +++ b/core/database/src/main/java/com/puzzle/database/converter/PieceConverters.kt @@ -14,4 +14,4 @@ class PieceConverters { fun toLocalDate(dateString: String?): LocalDateTime? { return dateString?.parseDateTime() } -} \ No newline at end of file +} diff --git a/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt b/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt index 6a432fde..53c31113 100644 --- a/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt +++ b/core/database/src/main/java/com/puzzle/database/di/DaosModule.kt @@ -14,4 +14,4 @@ internal object DaosModule { fun providesTermsDao( database: PieceDatabase, ): TermsDao = database.termsDao() -} \ No newline at end of file +} diff --git a/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt index 062c15eb..457905c2 100644 --- a/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt +++ b/core/database/src/main/java/com/puzzle/database/di/DatabaseModule.kt @@ -22,4 +22,4 @@ internal object DatabaseModule { PieceDatabase::class.java, PieceDatabase.NAME, ).build() -} \ No newline at end of file +} diff --git a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt index 824246de..e1474e39 100644 --- a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt +++ b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt @@ -9,17 +9,17 @@ import java.time.LocalDateTime @Entity(tableName = "term") data class TermEntity( @PrimaryKey - @ColumnInfo(name = "term_id") val termId: Int?, - val title: String?, - val content: String?, - val required: Boolean?, - @ColumnInfo(name = "start_date") val startDate: LocalDateTime?, + @ColumnInfo(name = "term_id") val termId: Int, + val title: String, + val content: String, + val required: Boolean, + @ColumnInfo(name = "start_date") val startDate: LocalDateTime, ) { fun toDomain() = Term( - termId = termId ?: -1, - title = title ?: "UNKNOWN", - content = content ?: "UNKNOWN", - required = required ?: false, - startDate = startDate ?: LocalDateTime.MIN + termId = termId, + title = title, + content = content, + required = required, + startDate = startDate, ) } diff --git a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt index 291dde57..4649741e 100644 --- a/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt +++ b/core/domain/src/main/java/com/puzzle/domain/repository/TermsRepository.kt @@ -5,4 +5,4 @@ import com.puzzle.domain.model.terms.Term interface TermsRepository { suspend fun loadTerms(): Result suspend fun getTerms(): Result> -} \ No newline at end of file +} diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index 7c6f7bff..8b498de1 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -112,4 +112,4 @@ private fun RegistrationScreen( .padding(bottom = 10.dp), ) } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b6eefdbd..c74ff13e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,16 +24,18 @@ dependencyResolutionManagement { rootProject.name = "Piece" include(":app") -include(":feature:auth") -include(":feature:mypage") -include(":feature:matching") -include(":feature:setting") + include(":core:domain") include(":core:designsystem") include(":core:data") include(":core:network") include(":core:navigation") include(":core:common-ui") -include(":presentation") include(":core:database") include(":core:common") + +include(":feature:auth") +include(":feature:mypage") +include(":feature:matching") +include(":feature:setting") +include(":presentation") From 9af33c84839e35c1e75f51a657397a4ba7458128 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:14:11 +0900 Subject: [PATCH 21/26] =?UTF-8?q?[PC-256]=20agreeAllTerms=20->=20allTermsA?= =?UTF-8?q?greed=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20navigate?= =?UTF-8?q?=EB=A5=BC=20SideEffect=EB=A1=9C=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../graph/registration/RegistrationScreen.kt | 2 +- .../registration/RegistrationViewModel.kt | 41 +++++++++++-------- .../contract/RegistrationSideEffect.kt | 6 ++- .../contract/RegistrationState.kt | 2 +- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index 8b498de1..58b3e08f 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -78,7 +78,7 @@ private fun RegistrationScreen( ) PieceCheckList( - checked = state.agreeAllTerms, + checked = state.allTermsAgreed, label = "약관 전체 동의", containerColor = PieceTheme.colors.light3, onCheckedChange = checkAllTerms, diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index 7a835571..447157df 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -6,6 +6,7 @@ import com.airbnb.mvrx.hilt.AssistedViewModelFactory import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory import com.puzzle.auth.graph.registration.contract.RegistrationIntent import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect +import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect.Navigate import com.puzzle.auth.graph.registration.contract.RegistrationState import com.puzzle.domain.model.error.ErrorHelper import com.puzzle.domain.usecase.terms.GetTermsUseCase @@ -28,9 +29,7 @@ class RegistrationViewModel @AssistedInject constructor( ) : MavericksViewModel(initialState) { private val intents = Channel(BUFFERED) - - private val _sideEffect = Channel(BUFFERED) - val sideEffect = _sideEffect.receiveAsFlow() + private val sideEffects = Channel(BUFFERED) init { fetchTerms() @@ -38,26 +37,40 @@ class RegistrationViewModel @AssistedInject constructor( intents.receiveAsFlow() .onEach(::processIntent) .launchIn(viewModelScope) - } - private fun fetchTerms() = viewModelScope.launch { - getTermsUseCase().onSuccess { - setState { copy(terms = it) } - }.onFailure { errorHelper.sendError(it) } + sideEffects.receiveAsFlow() + .onEach(::handleSideEffect) + .launchIn(viewModelScope) } internal fun onIntent(intent: RegistrationIntent) = viewModelScope.launch { intents.send(intent) } - private fun processIntent(intent: RegistrationIntent) { + private suspend fun processIntent(intent: RegistrationIntent) { when (intent) { - is RegistrationIntent.Navigate -> navigationHelper.navigate(intent.navigationEvent) + is RegistrationIntent.Navigate -> onSideEffect(Navigate(intent.navigationEvent)) is RegistrationIntent.CheckTerm -> checkTerm(intent.termId) is RegistrationIntent.CheckAllTerms -> checkAllTerms() } } + private suspend fun onSideEffect(sideEffect: RegistrationSideEffect) { + sideEffects.send(sideEffect) + } + + private fun handleSideEffect(sideEffect: RegistrationSideEffect) { + when (sideEffect) { + is Navigate -> navigationHelper.navigate(sideEffect.navigationEvent) + } + } + + private fun fetchTerms() = viewModelScope.launch { + getTermsUseCase().onSuccess { + setState { copy(terms = it) } + }.onFailure { errorHelper.sendError(it) } + } + private fun checkTerm(termId: Int) = setState { val updatedTermsCheckedInfo = termsCheckedInfo.toMutableMap().apply { this[termId] = !(this[termId] ?: false) @@ -67,7 +80,7 @@ class RegistrationViewModel @AssistedInject constructor( } private fun checkAllTerms() = setState { - if (agreeAllTerms) { + if (allTermsAgreed) { copy(termsCheckedInfo = mutableMapOf()) } else { val updatedTermsCheckedInfo = termsCheckedInfo.toMutableMap() @@ -80,12 +93,6 @@ class RegistrationViewModel @AssistedInject constructor( } } - private fun handleSideEffect(sideEffect: RegistrationSideEffect) { - when (sideEffect) { - else -> Unit - } - } - @AssistedFactory interface Factory : AssistedViewModelFactory { override fun create(state: RegistrationState): RegistrationViewModel diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationSideEffect.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationSideEffect.kt index 286396f9..cad6aa7f 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationSideEffect.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationSideEffect.kt @@ -1,3 +1,7 @@ package com.puzzle.auth.graph.registration.contract -sealed class RegistrationSideEffect \ No newline at end of file +import com.puzzle.navigation.NavigationEvent + +sealed class RegistrationSideEffect { + data class Navigate(val navigationEvent: NavigationEvent) : RegistrationSideEffect() +} diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt index 0838963d..2dc7c451 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationState.kt @@ -7,5 +7,5 @@ data class RegistrationState( val terms: List = emptyList(), val termsCheckedInfo: MutableMap = mutableMapOf(), ) : MavericksState { - val agreeAllTerms = terms.all { termsCheckedInfo.getOrDefault(it.termId, false) } + val allTermsAgreed = terms.all { termsCheckedInfo.getOrDefault(it.termId, false) } } From f894dff167ff7a9c051d20c4bb3d3c3de8f05f68 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:16:16 +0900 Subject: [PATCH 22/26] =?UTF-8?q?[PC-256]=20showXXX=20->=20XXXEnabled?= =?UTF-8?q?=EB=A1=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/puzzle/designsystem/component/Check.kt | 6 +++--- .../main/java/com/puzzle/designsystem/component/TopBar.kt | 4 ++-- .../puzzle/auth/graph/registration/RegistrationScreen.kt | 2 +- .../com/puzzle/matching/graph/detail/MatchingDetailRoute.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt index 905f9dda..aff087b5 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Check.kt @@ -50,7 +50,7 @@ fun PieceCheckList( label: String, onCheckedChange: () -> Unit, modifier: Modifier = Modifier, - showArrow: Boolean = false, + arrowEnabled: Boolean = false, containerColor: Color = PieceTheme.colors.white, onArrowClick: () -> Unit = {}, ) { @@ -76,7 +76,7 @@ fun PieceCheckList( modifier = Modifier.weight(1f), ) - if (showArrow) { + if (arrowEnabled) { Image( painter = painterResource(R.drawable.ic_arrow_right), contentDescription = "", @@ -127,7 +127,7 @@ fun PreviewPieceCheckList() { PieceCheckList( checked = false, - showArrow = true, + arrowEnabled = true, label = "약관 전체 동의", onCheckedChange = {}, onArrowClick = {}, diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt index 6588ede2..5305d2cf 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt @@ -102,7 +102,7 @@ fun PieceSubCloseTopBar( title: String, onCloseClick: () -> Unit, modifier: Modifier = Modifier, - showCloseButton: Boolean = true, + closeButtonEnabled: Boolean = true, contentColor: Color = PieceTheme.colors.black, ) { Box(modifier = modifier.fillMaxWidth()) { @@ -113,7 +113,7 @@ fun PieceSubCloseTopBar( modifier = Modifier.align(Alignment.Center), ) - if (showCloseButton) { + if (closeButtonEnabled) { Image( painter = painterResource(R.drawable.ic_close), contentDescription = "닫기 버튼", diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index 58b3e08f..b2f48300 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -90,7 +90,7 @@ private fun RegistrationScreen( state.terms.forEach { termInfo -> PieceCheckList( checked = state.termsCheckedInfo.getOrDefault(termInfo.termId, false), - showArrow = true, + arrowEnabled = true, label = termInfo.title, onCheckedChange = { checkTerm(termInfo.termId) }, onArrowClick = {}, diff --git a/feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailRoute.kt b/feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailRoute.kt index ff849695..5036f469 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailRoute.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/graph/detail/MatchingDetailRoute.kt @@ -178,7 +178,7 @@ private fun MatchingDetailScreen( PieceSubCloseTopBar( title = state.currentPage.title, onCloseClick = onCloseClick, - showCloseButton = !(showDialog && dialogType == DialogType.PROFILE_IMAGE_DETAIL), + closeButtonEnabled = !(showDialog && dialogType == DialogType.PROFILE_IMAGE_DETAIL), modifier = Modifier .fillMaxWidth() .height(topBarHeight) From 79afd27150e27739cafad5cffc2503a8f7cdbdea Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:18:02 +0900 Subject: [PATCH 23/26] =?UTF-8?q?[PC-256]=20getResult=20->=20unwrapData?= =?UTF-8?q?=EB=A1=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/puzzle/network/model/ApiResponse.kt | 2 +- .../main/java/com/puzzle/network/source/AuthDataSource.kt | 8 ++++---- .../main/java/com/puzzle/network/source/TermDataSource.kt | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt index 90e9c72c..5654c8e3 100644 --- a/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt +++ b/core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt @@ -11,7 +11,7 @@ data class ApiResponse( val data: T?, ) -internal fun Result>.getResult(): Result { +internal fun Result>.unwrapData(): Result { return this.map { response -> response.data ?: return Result.failure( HttpResponseException( diff --git a/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt index 620cc90e..fb402bc7 100644 --- a/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/AuthDataSource.kt @@ -7,7 +7,7 @@ import com.puzzle.network.model.auth.LoginOauthResponse import com.puzzle.network.model.auth.RequestAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeRequest import com.puzzle.network.model.auth.VerifyAuthCodeResponse -import com.puzzle.network.model.getResult +import com.puzzle.network.model.unwrapData import javax.inject.Inject import javax.inject.Singleton @@ -21,11 +21,11 @@ class AuthDataSource @Inject constructor( providerName = provider.apiValue, token = token, ) - ).getResult() + ).unwrapData() suspend fun requestAuthCode(phoneNumber: String): Result = pieceApi.requestAuthCode(RequestAuthCodeRequest(phoneNumber)) - .getResult() + .unwrapData() suspend fun verifyAuthCode(phoneNumber: String, code: String): Result = pieceApi.verifyAuthCode( @@ -33,5 +33,5 @@ class AuthDataSource @Inject constructor( phoneNumber = phoneNumber, code = code, ) - ).getResult() + ).unwrapData() } diff --git a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt index 7be7fb7b..1f9b78ec 100644 --- a/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt +++ b/core/network/src/main/java/com/puzzle/network/source/TermDataSource.kt @@ -1,7 +1,7 @@ package com.puzzle.network.source import com.puzzle.network.api.PieceApi -import com.puzzle.network.model.getResult +import com.puzzle.network.model.unwrapData import com.puzzle.network.model.terms.LoadTermsResponse import javax.inject.Inject import javax.inject.Singleton @@ -10,5 +10,5 @@ import javax.inject.Singleton class TermDataSource @Inject constructor( private val pieceApi: PieceApi, ) { - suspend fun loadTerms(): Result = pieceApi.loadTerms().getResult() + suspend fun loadTerms(): Result = pieceApi.loadTerms().unwrapData() } From 5074087a91d82d69cdf159ccc30c4b755f4a3ba2 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:19:52 +0900 Subject: [PATCH 24/26] =?UTF-8?q?[PC-256]=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20bypass=EC=8B=9C=ED=82=A4=EA=B8=B0=EB=A7=8C=20?= =?UTF-8?q?=ED=95=98=EB=8D=98=20UseCase=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../puzzle/domain/usecase/terms/GetTermsUseCase.kt | 11 ----------- .../puzzle/domain/usecase/terms/LoadTermsUseCase.kt | 10 ---------- .../auth/graph/registration/RegistrationViewModel.kt | 6 +++--- .../java/com/puzzle/presentation/MainViewModel.kt | 6 +++--- 4 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt delete mode 100644 core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt diff --git a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt deleted file mode 100644 index 19ac9e85..00000000 --- a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/GetTermsUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.puzzle.domain.usecase.terms - -import com.puzzle.domain.model.terms.Term -import com.puzzle.domain.repository.TermsRepository -import javax.inject.Inject - -class GetTermsUseCase @Inject constructor( - private val termsRepository: TermsRepository, -) { - suspend operator fun invoke(): Result> = termsRepository.getTerms() -} diff --git a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt b/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt deleted file mode 100644 index a9f7a8eb..00000000 --- a/core/domain/src/main/java/com/puzzle/domain/usecase/terms/LoadTermsUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.puzzle.domain.usecase.terms - -import com.puzzle.domain.repository.TermsRepository -import javax.inject.Inject - -class LoadTermsUseCase @Inject constructor( - private val termsRepository: TermsRepository, -) { - suspend operator fun invoke(): Result = termsRepository.loadTerms() -} diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index 447157df..27f44786 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -9,7 +9,7 @@ import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect.Navigate import com.puzzle.auth.graph.registration.contract.RegistrationState import com.puzzle.domain.model.error.ErrorHelper -import com.puzzle.domain.usecase.terms.GetTermsUseCase +import com.puzzle.domain.repository.TermsRepository import com.puzzle.navigation.NavigationHelper import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -23,7 +23,7 @@ import kotlinx.coroutines.launch class RegistrationViewModel @AssistedInject constructor( @Assisted initialState: RegistrationState, - private val getTermsUseCase: GetTermsUseCase, + private val termsRepository: TermsRepository, private val navigationHelper: NavigationHelper, private val errorHelper: ErrorHelper, ) : MavericksViewModel(initialState) { @@ -66,7 +66,7 @@ class RegistrationViewModel @AssistedInject constructor( } private fun fetchTerms() = viewModelScope.launch { - getTermsUseCase().onSuccess { + termsRepository.getTerms().onSuccess { setState { copy(terms = it) } }.onFailure { errorHelper.sendError(it) } } diff --git a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt index 4295a87d..f7a06df4 100644 --- a/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt +++ b/presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope import com.puzzle.common.event.EventHelper import com.puzzle.domain.model.error.ErrorHelper import com.puzzle.domain.model.error.HttpResponseException -import com.puzzle.domain.usecase.terms.LoadTermsUseCase +import com.puzzle.domain.repository.TermsRepository import com.puzzle.navigation.NavigationHelper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -13,7 +13,7 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( - private val loadTermsUseCase: LoadTermsUseCase, + private val termsRepository: TermsRepository, internal val navigationHelper: NavigationHelper, internal val eventHelper: EventHelper, private val errorHelper: ErrorHelper, @@ -38,7 +38,7 @@ class MainViewModel @Inject constructor( } private fun loadTerms() = viewModelScope.launch { - loadTermsUseCase().onFailure { + termsRepository.loadTerms().onFailure { errorHelper.sendError(it) } } From f14ff20f6401009cf7c2110e368b125e7e1b5255 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:20:54 +0900 Subject: [PATCH 25/26] =?UTF-8?q?[PC-256]=20TermEntity,=20termId=20->=20id?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/puzzle/data/repository/TermsRepositoryImpl.kt | 2 +- .../com/puzzle/data/repository/TermsRepositoryImplTest.kt | 4 ++-- .../main/java/com/puzzle/database/model/terms/TermEntity.kt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt index 4659fd5b..225a74ed 100644 --- a/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt +++ b/core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt @@ -20,7 +20,7 @@ class TermsRepositoryImpl @Inject constructor( val termsEntity = terms.map { TermEntity( - termId = it.termId, + id = it.termId, title = it.title, content = it.content, required = it.required, diff --git a/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt index 9dafd3a8..35d9df38 100644 --- a/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt +++ b/core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt @@ -58,7 +58,7 @@ class TermsRepositoryImplTest { coVerify(exactly = 1) { localTermDataSource.clearAndInsertTerms( match { - it.size == 1 && it.first().termId == validTerm.termId + it.size == 1 && it.first().id == validTerm.termId } ) } @@ -96,7 +96,7 @@ class TermsRepositoryImplTest { match { it.size == validTerms.size && it.all { entity -> validTerms.any { term -> - term.termId == entity.termId && term.title == entity.title + term.termId == entity.id && term.title == entity.title } } } diff --git a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt index e1474e39..89182334 100644 --- a/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt +++ b/core/database/src/main/java/com/puzzle/database/model/terms/TermEntity.kt @@ -9,14 +9,14 @@ import java.time.LocalDateTime @Entity(tableName = "term") data class TermEntity( @PrimaryKey - @ColumnInfo(name = "term_id") val termId: Int, + @ColumnInfo(name = "id") val id: Int, val title: String, val content: String, val required: Boolean, @ColumnInfo(name = "start_date") val startDate: LocalDateTime, ) { fun toDomain() = Term( - termId = termId, + termId = id, title = title, content = content, required = required, From 2d236cc5d29d29883aab1200f1607b6644653fe9 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Tue, 7 Jan 2025 13:29:39 +0900 Subject: [PATCH 26/26] =?UTF-8?q?[PC-256]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Intent=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/graph/registration/RegistrationScreen.kt | 3 ++- .../auth/graph/registration/RegistrationViewModel.kt | 11 +++++------ .../graph/registration/contract/RegistrationIntent.kt | 3 --- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt index b2f48300..23015919 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import com.puzzle.auth.graph.registration.contract.RegistrationIntent +import com.puzzle.auth.graph.registration.contract.RegistrationSideEffect import com.puzzle.auth.graph.registration.contract.RegistrationState import com.puzzle.designsystem.component.PieceCheckList import com.puzzle.designsystem.component.PieceSolidButton @@ -34,7 +35,7 @@ internal fun RegistrationRoute( state = state, checkAllTerms = { viewModel.onIntent(RegistrationIntent.CheckAllTerms) }, checkTerm = { viewModel.onIntent(RegistrationIntent.CheckTerm(it)) }, - navigate = { event -> viewModel.onIntent(RegistrationIntent.Navigate(event)) } + navigate = { event -> viewModel.onSideEffect(RegistrationSideEffect.Navigate(event)) } ) } diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt index 27f44786..8579cf4b 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/RegistrationViewModel.kt @@ -47,18 +47,17 @@ class RegistrationViewModel @AssistedInject constructor( intents.send(intent) } - private suspend fun processIntent(intent: RegistrationIntent) { + internal fun onSideEffect(sideEffect: RegistrationSideEffect) = viewModelScope.launch { + sideEffects.send(sideEffect) + } + + private fun processIntent(intent: RegistrationIntent) { when (intent) { - is RegistrationIntent.Navigate -> onSideEffect(Navigate(intent.navigationEvent)) is RegistrationIntent.CheckTerm -> checkTerm(intent.termId) is RegistrationIntent.CheckAllTerms -> checkAllTerms() } } - private suspend fun onSideEffect(sideEffect: RegistrationSideEffect) { - sideEffects.send(sideEffect) - } - private fun handleSideEffect(sideEffect: RegistrationSideEffect) { when (sideEffect) { is Navigate -> navigationHelper.navigate(sideEffect.navigationEvent) diff --git a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt index fa57cbc0..bdaf85d0 100644 --- a/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt +++ b/feature/auth/src/main/java/com/puzzle/auth/graph/registration/contract/RegistrationIntent.kt @@ -1,9 +1,6 @@ package com.puzzle.auth.graph.registration.contract -import com.puzzle.navigation.NavigationEvent - sealed class RegistrationIntent { data object CheckAllTerms : RegistrationIntent() data class CheckTerm(val termId: Int) : RegistrationIntent() - data class Navigate(val navigationEvent: NavigationEvent) : RegistrationIntent() }