From 548a686d62af75a48dd8359cd9a3a6aac4f7dbc3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:23:20 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[refactor]=20#89:=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=ED=99=94=EB=A9=B4=20=EC=9E=AC=EC=A7=84?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?API=20=ED=98=B8=EC=B6=9C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포즈피드 화면에 다시 진입했을 때, 포즈 목록이 이미 존재하면 초기 포즈를 다시 불러오는 API가 호출되지 않도록 수정했습니다. --- .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 24d50dbd1..86f52cfc2 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -48,7 +48,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( postSideEffect: (RandomPoseEffect) -> Unit, ) { when (intent) { - RandomPoseIntent.EnterRandomPoseScreen -> fetchInitialPoses(reduce, postSideEffect) + RandomPoseIntent.EnterRandomPoseScreen -> fetchInitialPoses(state, reduce, postSideEffect) // 튜토리얼 RandomPoseIntent.ClickLeftSwipe -> handleMovePrevious(state, reduce, postSideEffect) @@ -168,9 +168,12 @@ internal class RandomPoseViewModel @AssistedInject constructor( } private fun fetchInitialPoses( + state: RandomPoseUiState, reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, postSideEffect: (RandomPoseEffect) -> Unit, ) { + if (state.poseList.isNotEmpty()) return + viewModelScope.launch { reduce { copy(isLoading = true) } From 37ed3b607ea280ad176fa9de777f0b362dc99260 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:35:07 +0900 Subject: [PATCH 02/19] =?UTF-8?q?[feat]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=B2=AB=20=EB=B0=A9=EB=AC=B8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?UserDataStore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 랜덤 포즈 추천 화면의 첫 방문 여부를 저장하고 확인하기 위해, `UserDataStore`를 새로 추가했습니다. 이를 통해 `isFirstVisitRandomPose` 값을 `Flow`로 관찰하고, 값을 업데이트하는 기능을 구현했습니다. --- .../core/dataapi/datastore/DataStoreKey.kt | 2 ++ .../core/dataapi/repository/UserRepository.kt | 4 ++++ .../core/data/local/di/DataStoreModule.kt | 8 ++++++++ .../core/data/local/di/DataStoreQualifier.kt | 4 ++++ .../repository/impl/UserRepositoryImpl.kt | 19 +++++++++++++++++++ 5 files changed, 37 insertions(+) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt index e145d556f..cd8e57e8a 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt @@ -1,8 +1,10 @@ package com.neki.android.core.dataapi.datastore +import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey object DataStoreKey { val ACCESS_TOKEN = stringPreferencesKey("access_token") val REFRESH_TOKEN = stringPreferencesKey("refresh_token") + val IS_FIRST_VISIT_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose") } diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt index 892385422..8d24cb7ef 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt @@ -1,9 +1,13 @@ package com.neki.android.core.dataapi.repository import com.neki.android.core.model.UserInfo +import kotlinx.coroutines.flow.Flow interface UserRepository { + val isFirstVisitRandomPose: Flow + suspend fun setFirstVisitRandomPose(value: Boolean) suspend fun getUserInfo(): Result suspend fun updateUserInfo(nickname: String): Result + suspend fun updateProfileImage(mediaId: Long?): Result } diff --git a/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt b/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt index 568cc7405..3905e74ef 100644 --- a/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt +++ b/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt @@ -17,6 +17,9 @@ private val Context.authDataStore: DataStore by preferencesDataStor private const val TOKEN_DATASTORE = "token_datastore" private val Context.tokenDataStore: DataStore by preferencesDataStore(name = TOKEN_DATASTORE) +private const val USER_DATASTORE = "user_datastore" +private val Context.userDataStore: DataStore by preferencesDataStore(name = USER_DATASTORE) + @InstallIn(SingletonComponent::class) @Module internal object DataStoreModule { @@ -30,4 +33,9 @@ internal object DataStoreModule { @Singleton @Provides fun provideTokenDataStore(@ApplicationContext context: Context): DataStore = context.tokenDataStore + + @UserDataStore + @Singleton + @Provides + fun provideUserDataStore(@ApplicationContext context: Context): DataStore = context.userDataStore } diff --git a/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreQualifier.kt b/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreQualifier.kt index 87863741f..979e811c6 100644 --- a/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreQualifier.kt +++ b/core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreQualifier.kt @@ -9,3 +9,7 @@ annotation class AuthDataStore @Qualifier @Retention(AnnotationRetention.BINARY) annotation class TokenDataStore + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class UserDataStore diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index c708f61e3..ee057016d 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -1,16 +1,35 @@ package com.neki.android.core.data.repository.impl +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import com.neki.android.core.data.local.di.UserDataStore import com.neki.android.core.data.remote.api.UserService import com.neki.android.core.data.remote.model.request.UpdateProfileImageRequest import com.neki.android.core.data.remote.model.request.UpdateUserInfoRequest import com.neki.android.core.data.util.runSuspendCatching +import com.neki.android.core.dataapi.datastore.DataStoreKey import com.neki.android.core.dataapi.repository.UserRepository import com.neki.android.core.model.UserInfo +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class UserRepositoryImpl @Inject constructor( + @UserDataStore private val dataStore: DataStore, private val userService: UserService, ) : UserRepository { + override val isFirstVisitRandomPose: Flow = + dataStore.data.map { preferences -> + preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] ?: false + } + + override suspend fun setFirstVisitRandomPose(value: Boolean) { + dataStore.edit { preferences -> + preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] = value + } + } + override suspend fun getUserInfo(): Result = runSuspendCatching { userService.getUserInfo().data.toModel() } From a0670165f2330282f3638b4e72344e09c1392e1d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:41:59 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[feat]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=B2=AB=20=EB=B0=A9=EB=AC=B8=20=EC=8B=9C?= =?UTF-8?q?=EC=97=90=EB=A7=8C=20=ED=8A=9C=ED=86=A0=EB=A6=AC=EC=96=BC=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=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 랜덤 포즈 추천 화면에 처음 방문하는 사용자를 위해 튜토리얼을 표시하는 기능을 추가했습니다. `UserRepository`를 사용하여 첫 방문 여부를 확인하고, 첫 방문일 경우 튜토리얼을 노출한 뒤 방문 상태를 업데이트합니다. --- .../pose/impl/random/RandomPoseViewModel.kt | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 86f52cfc2..139046ad5 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.common.exception.RandomPoseRetryExhaustedException import com.neki.android.core.dataapi.repository.PoseRepository +import com.neki.android.core.dataapi.repository.UserRepository import com.neki.android.core.model.PeopleCount import com.neki.android.core.ui.MviIntentStore import com.neki.android.core.ui.mviIntentStore @@ -17,6 +18,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import timber.log.Timber @@ -24,6 +26,7 @@ import timber.log.Timber internal class RandomPoseViewModel @AssistedInject constructor( @Assisted private val peopleCount: PeopleCount, private val poseRepository: PoseRepository, + private val userRepository: UserRepository, @ApplicationScope private val applicationScope: CoroutineScope, ) : ViewModel() { @@ -48,7 +51,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( postSideEffect: (RandomPoseEffect) -> Unit, ) { when (intent) { - RandomPoseIntent.EnterRandomPoseScreen -> fetchInitialPoses(state, reduce, postSideEffect) + RandomPoseIntent.EnterRandomPoseScreen -> fetchInitialData(state, reduce, postSideEffect) // 튜토리얼 RandomPoseIntent.ClickLeftSwipe -> handleMovePrevious(state, reduce, postSideEffect) @@ -167,6 +170,27 @@ internal class RandomPoseViewModel @AssistedInject constructor( } } + private fun fetchInitialData( + state: RandomPoseUiState, + reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, + postSideEffect: (RandomPoseEffect) -> Unit, + ) { + checkFirstVisit(reduce) + fetchInitialPoses(state, reduce, postSideEffect) + } + + private fun checkFirstVisit( + reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, + ) { + viewModelScope.launch { + if (userRepository.isFirstVisitRandomPose.first()) { + userRepository.setFirstVisitRandomPose(false) + } else { + reduce { copy(isShowTutorial = false) } + } + } + } + private fun fetchInitialPoses( state: RandomPoseUiState, reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, From 1aa076d4682dbee5f90d0a6ffbfaf59d6095995e Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:16:00 +0900 Subject: [PATCH 04/19] =?UTF-8?q?[refactor]=20#78:=20=ED=8F=AC=EC=A6=88=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=9E=A9=20=EC=A0=81=EC=9A=A9=20=EC=8B=9C=20?= =?UTF-8?q?=EB=9E=9C=EB=8D=A4=20=ED=8F=AC=EC=A6=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EB=8F=84=20=EA=B2=B0=EA=B3=BC=EA=B0=92=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pose/impl/navigation/PoseEntryProvider.kt | 22 ++++- .../pose/impl/random/RandomPoseContract.kt | 1 + .../pose/impl/random/RandomPoseViewModel.kt | 91 ++++++++++--------- 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/navigation/PoseEntryProvider.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/navigation/PoseEntryProvider.kt index 7c338421f..a77564d3d 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/navigation/PoseEntryProvider.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/navigation/PoseEntryProvider.kt @@ -16,6 +16,7 @@ import com.neki.android.feature.pose.impl.detail.PoseDetailViewModel import com.neki.android.feature.pose.impl.main.PoseIntent import com.neki.android.feature.pose.impl.main.PoseRoute import com.neki.android.feature.pose.impl.main.PoseViewModel +import com.neki.android.feature.pose.impl.random.RandomPoseIntent import com.neki.android.feature.pose.impl.random.RandomPoseRoute import com.neki.android.feature.pose.impl.random.RandomPoseViewModel import dagger.Module @@ -68,12 +69,23 @@ private fun EntryProviderScope.poseEntry(navigator: Navigator) { } entry { key -> + val resultBus = LocalResultEventBus.current + val viewModel = hiltViewModel( + creationCallback = { factory -> + factory.create(key.peopleCount) + }, + ) + + ResultEffect(resultBus) { result -> + when (result) { + is PoseResult.ScrapChanged -> { + viewModel.store.onIntent(RandomPoseIntent.ScrapChanged(result.poseId, result.isScrapped)) + } + } + } + RandomPoseRoute( - viewModel = hiltViewModel( - creationCallback = { factory -> - factory.create(key.peopleCount) - }, - ), + viewModel = viewModel, navigateBack = navigator::goBack, navigateToPoseDetail = navigator::navigateToPoseDetail, ) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt index 27e34861b..7a7c21597 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt @@ -33,6 +33,7 @@ sealed interface RandomPoseIntent { data object ClickScrapIcon : RandomPoseIntent data object ClickLeftSwipe : RandomPoseIntent data object ClickRightSwipe : RandomPoseIntent + data class ScrapChanged(val poseId: Long, val isScrapped: Boolean) : RandomPoseIntent } sealed interface RandomPoseEffect { diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 139046ad5..0683faf40 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -66,60 +66,61 @@ internal class RandomPoseViewModel @AssistedInject constructor( } } - RandomPoseIntent.ClickScrapIcon -> handleScrapToggle(state, reduce) + RandomPoseIntent.ClickScrapIcon -> { + val currentPost = state.currentPose ?: return + handleScrapToggle(currentPost.id, !currentPost.isScrapped, reduce) + } + + is RandomPoseIntent.ScrapChanged -> handleScrapToggle(intent.poseId, intent.isScrapped, reduce) } } private fun handleScrapToggle( - state: RandomPoseUiState, + poseId: Long, + newScrapStatus: Boolean, reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, ) { - state.currentPose?.let { currentPose -> - val poseId = currentPose.id - val newScrapStatus = !currentPose.isScrapped - - // UI 즉시 업데이트 - reduce { - copy( - poseList = poseList.map { pose -> - if (pose.id == poseId) { - pose.copy(isScrapped = newScrapStatus) - } else { - pose - } - }.toImmutableList(), - ) - } + // UI 즉시 업데이트 + reduce { + copy( + poseList = poseList.map { pose -> + if (pose.id == poseId) { + pose.copy(isScrapped = newScrapStatus) + } else { + pose + } + }.toImmutableList(), + ) + } - // 해당 포즈의 이전 Job 취소 후 새로운 Job 시작 - scrapJobs[poseId]?.cancel() - scrapJobs[poseId] = viewModelScope.launch { - delay(500) - val committedScrap = store.uiState.value.committedScraps[poseId] - if (committedScrap == newScrapStatus || committedScrap == null) return@launch - - poseRepository.updateScrap(poseId, newScrapStatus) - .onSuccess { - Timber.d("updateScrap success for poseId: $poseId") - reduce { - copy(committedScraps = committedScraps + (poseId to newScrapStatus)) - } + // 해당 포즈의 이전 Job 취소 후 새로운 Job 시작 + scrapJobs[poseId]?.cancel() + scrapJobs[poseId] = viewModelScope.launch { + delay(500) + val committedScrap = store.uiState.value.committedScraps[poseId] + if (committedScrap == newScrapStatus || committedScrap == null) return@launch + + poseRepository.updateScrap(poseId, newScrapStatus) + .onSuccess { + Timber.d("updateScrap success for poseId: $poseId") + reduce { + copy(committedScraps = committedScraps + (poseId to newScrapStatus)) } - .onFailure { error -> - Timber.e(error, "updateScrap failed for poseId: $poseId") - reduce { - copy( - poseList = poseList.map { pose -> - if (pose.id == poseId) { - pose.copy(isScrapped = committedScrap) - } else { - pose - } - }.toImmutableList(), - ) - } + } + .onFailure { error -> + Timber.e(error, "updateScrap failed for poseId: $poseId") + reduce { + copy( + poseList = poseList.map { pose -> + if (pose.id == poseId) { + pose.copy(isScrapped = committedScrap) + } else { + pose + } + }.toImmutableList(), + ) } - } + } } } From 863eb49dc3bc189c6b773d2de8b08880b67e3596 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:50:18 +0900 Subject: [PATCH 05/19] =?UTF-8?q?[feat]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EB=A1=9C=EB=94=A9=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포즈를 불러오는 데 실패했을 때 사용자에게 "포즈를 불러오는데 실패했어요"라는 토스트 메시지를 표시하는 기능을 추가했습니다. --- .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 0683faf40..3baf5481f 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -218,11 +218,11 @@ internal class RandomPoseViewModel @AssistedInject constructor( ) } }.onFailure { error -> - Timber.e(error) reduce { copy(isLoading = false) } if (error is RandomPoseRetryExhaustedException) Timber.e(error, "중복 포즈") else Timber.e(error) + postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요")) } } } From 953ce612e03dc73662f913add0fac81d3c7f0e4b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:56:54 +0900 Subject: [PATCH 06/19] =?UTF-8?q?[refactor]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=B2=AB=20=EB=B0=A9=EB=AC=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 랜덤 포즈 추천 화면의 첫 방문 여부를 확인하는 로직의 네이밍과 흐름을 개선했습니다. `UserRepository`의 `isFirstVisitRandomPose`를 `hasVisitedRandomPose`로 변경하고, `setFirstVisitRandomPose(value: Boolean)` 메서드를 `markRandomPoseAsVisited()`로 수정하여 방문 기록을 남기는 역할임을 명확히 했습니다. 이와 함께 `RandomPoseViewModel`의 관련 로직도 수정하여 가독성을 높였습니다. --- .../neki/android/core/dataapi/repository/UserRepository.kt | 4 ++-- .../android/core/data/repository/impl/UserRepositoryImpl.kt | 6 +++--- .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt index 8d24cb7ef..ceb39d1e2 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt @@ -4,8 +4,8 @@ import com.neki.android.core.model.UserInfo import kotlinx.coroutines.flow.Flow interface UserRepository { - val isFirstVisitRandomPose: Flow - suspend fun setFirstVisitRandomPose(value: Boolean) + val hasVisitedRandomPose: Flow + suspend fun markRandomPoseAsVisited() suspend fun getUserInfo(): Result suspend fun updateUserInfo(nickname: String): Result diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index ee057016d..ee922ac65 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -19,14 +19,14 @@ class UserRepositoryImpl @Inject constructor( @UserDataStore private val dataStore: DataStore, private val userService: UserService, ) : UserRepository { - override val isFirstVisitRandomPose: Flow = + override val hasVisitedRandomPose: Flow = dataStore.data.map { preferences -> preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] ?: false } - override suspend fun setFirstVisitRandomPose(value: Boolean) { + override suspend fun markRandomPoseAsVisited() { dataStore.edit { preferences -> - preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] = value + preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] = true } } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 3baf5481f..1c452ab72 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -184,8 +184,8 @@ internal class RandomPoseViewModel @AssistedInject constructor( reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, ) { viewModelScope.launch { - if (userRepository.isFirstVisitRandomPose.first()) { - userRepository.setFirstVisitRandomPose(false) + if (userRepository.hasVisitedRandomPose.first()) { + userRepository.markRandomPoseAsVisited() } else { reduce { copy(isShowTutorial = false) } } From e017a9f60e43826654e5ab7fc471538a2e2f6174 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:32:50 +0900 Subject: [PATCH 07/19] =?UTF-8?q?[refactor]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=B2=AB=20=EB=B0=A9=EB=AC=B8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20DataStore=20Key=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `IS_FIRST_VISIT_RANDOM_POSE`에서 `HAS_VISITED_RANDOM_POSE`로 DataStore 키 이름을 변경하여, 방문 여부를 확인하는 변수임을 명확히 했습니다. 이에 따라 `UserRepositoryImpl`에서도 변경된 키를 사용하도록 수정했습니다. --- .../com/neki/android/core/dataapi/datastore/DataStoreKey.kt | 2 +- .../android/core/data/repository/impl/UserRepositoryImpl.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt index cd8e57e8a..edee9a27f 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt @@ -6,5 +6,5 @@ import androidx.datastore.preferences.core.stringPreferencesKey object DataStoreKey { val ACCESS_TOKEN = stringPreferencesKey("access_token") val REFRESH_TOKEN = stringPreferencesKey("refresh_token") - val IS_FIRST_VISIT_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose") + val HAS_VISITED_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose") } diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index ee922ac65..2e44ef9d1 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -21,12 +21,12 @@ class UserRepositoryImpl @Inject constructor( ) : UserRepository { override val hasVisitedRandomPose: Flow = dataStore.data.map { preferences -> - preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] ?: false + preferences[DataStoreKey.HAS_VISITED_RANDOM_POSE] ?: false } override suspend fun markRandomPoseAsVisited() { dataStore.edit { preferences -> - preferences[DataStoreKey.IS_FIRST_VISIT_RANDOM_POSE] = true + preferences[DataStoreKey.HAS_VISITED_RANDOM_POSE] = true } } From 0b8b19ce0d77ce13c67f6683c0ed1c7ac4f2dc0b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:34:10 +0900 Subject: [PATCH 08/19] =?UTF-8?q?[fix]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=ED=8A=9C=ED=86=A0=EB=A6=AC=EC=96=BC=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=20=EC=A1=B0=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 랜덤 포즈 추천 화면 첫 방문 시에만 튜토리얼이 노출되도록 `hasVisitedRandomPose`의 조건절을 수정했습니다. --- .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 1c452ab72..26d3ffa51 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -184,7 +184,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( reduce: (RandomPoseUiState.() -> RandomPoseUiState) -> Unit, ) { viewModelScope.launch { - if (userRepository.hasVisitedRandomPose.first()) { + if (!userRepository.hasVisitedRandomPose.first()) { userRepository.markRandomPoseAsVisited() } else { reduce { copy(isShowTutorial = false) } From 54bc957525a4cb58988ee8d7b115a17a45893abb Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:24:14 +0900 Subject: [PATCH 09/19] =?UTF-8?q?[refactor]=20#93:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=BF=BC=EB=A6=AC=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클라이언트에서 `maxRetry`를 통해 중복 포즈를 제거하던 로직을 서버 API에 `excludeIds` 파라미터를 직접 전달하여 처리하도록 변경했습니다. 이를 통해 불필요한 클라이언트 측 재시도 로직과 파라미터를 제거하고, API 호출을 최적화했습니다. --- .../core/dataapi/repository/PoseRepository.kt | 2 -- .../core/data/remote/api/PoseService.kt | 3 +- .../repository/impl/PoseRepositoryImpl.kt | 28 ++++++++----------- .../pose/impl/random/RandomPoseViewModel.kt | 2 -- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt index e81cbfb08..48077c393 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt @@ -22,14 +22,12 @@ interface PoseRepository { suspend fun getSingleRandomPose( headCount: PeopleCount, excludeIds: Set, - maxRetry: Int, ): Result suspend fun getMultipleRandomPose( headCount: PeopleCount, excludeIds: Set, poseSize: Int, - maxRetry: Int, ): Result> suspend fun updateScrap(poseId: Long, scrap: Boolean): Result diff --git a/core/data/src/main/java/com/neki/android/core/data/remote/api/PoseService.kt b/core/data/src/main/java/com/neki/android/core/data/remote/api/PoseService.kt index 5ecc93fe7..98fe8f121 100644 --- a/core/data/src/main/java/com/neki/android/core/data/remote/api/PoseService.kt +++ b/core/data/src/main/java/com/neki/android/core/data/remote/api/PoseService.kt @@ -37,9 +37,10 @@ class PoseService @Inject constructor( } // 랜덤 포즈 조회 - suspend fun getRandomPose(headCount: String): BasicResponse { + suspend fun getRandomPose(headCount: String, excludeIds: String): BasicResponse { return client.get("/api/poses/random") { parameter("headCount", headCount) + parameter("excludeIds", excludeIds) }.body() } diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt index 53084699f..db6b43e97 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt @@ -69,38 +69,34 @@ class PoseRepositoryImpl @Inject constructor( override suspend fun getSingleRandomPose( headCount: PeopleCount, excludeIds: Set, - maxRetry: Int, ): Result = runSuspendCatching { - repeat(maxRetry) { - val pose = poseService.getRandomPose(headCount = headCount.name).data.toModel() - if (pose.id !in excludeIds) { - return@runSuspendCatching pose - } - } - throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요") + val excludeIdsString = excludeIds.joinToString(",") + poseService.getRandomPose( + headCount = headCount.name, + excludeIds = excludeIdsString, + ).data.toModel() } override suspend fun getMultipleRandomPose( headCount: PeopleCount, excludeIds: Set, poseSize: Int, - maxRetry: Int, ): Result> = runSuspendCatching { val result = mutableListOf() val collectedIds = excludeIds.toMutableSet() - var retryCount = 0 + repeat(poseSize) { + val excludeIdsString = collectedIds.joinToString(",") + val pose = poseService.getRandomPose( + headCount = headCount.name, + excludeIds = excludeIdsString, + ).data.toModel() - while (result.size < poseSize && retryCount < maxRetry) { - val pose = poseService.getRandomPose(headCount = headCount.name).data.toModel() if (pose.id !in collectedIds) { result.add(pose) collectedIds.add(pose.id) - } else { - retryCount++ } } - - result.ifEmpty { throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요") } + return@runSuspendCatching result } override suspend fun updateScrap(poseId: Long, scrap: Boolean): Result = runSuspendCatching { diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 26d3ffa51..ef148ff6c 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -159,7 +159,6 @@ internal class RandomPoseViewModel @AssistedInject constructor( poseRepository.getSingleRandomPose( headCount = peopleCount, excludeIds = state.randomPoseIds, - maxRetry = PoseConst.MAXIMUM_RANDOM_POSE_RETRY_COUNT, ).onSuccess { pose -> reduce { copy(poseList = (poseList + pose).toImmutableList()) } }.onFailure { error -> @@ -207,7 +206,6 @@ internal class RandomPoseViewModel @AssistedInject constructor( headCount = peopleCount, excludeIds = emptySet(), poseSize = PoseConst.INITIAL_POSE_LOAD_COUNT, - maxRetry = PoseConst.MAXIMUM_RANDOM_POSE_RETRY_COUNT, ).onSuccess { data -> reduce { copy( From 5be40099b2fff316be65043b101912b439f3e93d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:37:37 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[refactor]=20#89:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=B6=94=EC=B2=9C=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 사용하던 커스텀 예외(`RandomPoseRetryExhaustedException`)를 삭제하고, `HttpException`을 사용하여 더 이상 추천할 포즈가 없을 때의 예외 처리를 변경했습니다. API로부터 특정 에러 코드(400)를 받으면, 사용자에게 실패 토스트 메시지를 표시하도록 로직을 수정했습니다. --- .../RandomPoseRetryExhaustedException.kt | 5 ----- .../core/dataapi/repository/PoseRepository.kt | 2 ++ .../data/repository/impl/PoseRepositoryImpl.kt | 17 ++++++++++++----- .../pose/impl/random/RandomPoseViewModel.kt | 13 ++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) delete mode 100644 core/common/src/main/java/com/neki/android/core/common/exception/RandomPoseRetryExhaustedException.kt diff --git a/core/common/src/main/java/com/neki/android/core/common/exception/RandomPoseRetryExhaustedException.kt b/core/common/src/main/java/com/neki/android/core/common/exception/RandomPoseRetryExhaustedException.kt deleted file mode 100644 index a08f3ac50..000000000 --- a/core/common/src/main/java/com/neki/android/core/common/exception/RandomPoseRetryExhaustedException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.neki.android.core.common.exception - -class RandomPoseRetryExhaustedException( - message: String, -) : RuntimeException(message) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt index 48077c393..3aee14dbf 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt @@ -6,6 +6,8 @@ import com.neki.android.core.model.Pose import com.neki.android.core.model.SortOrder import kotlinx.coroutines.flow.Flow +const val NO_MORE_RANDOM_POSE = 400 + interface PoseRepository { fun getPosesFlow( diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt index db6b43e97..422e6e803 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt @@ -3,16 +3,17 @@ package com.neki.android.core.data.repository.impl import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.neki.android.core.common.exception.RandomPoseRetryExhaustedException import com.neki.android.core.data.paging.PosePagingSource import com.neki.android.core.data.paging.ScrapPosePagingSource import com.neki.android.core.data.remote.api.PoseService import com.neki.android.core.data.util.runSuspendCatching +import com.neki.android.core.dataapi.repository.NO_MORE_RANDOM_POSE import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.model.PeopleCount import com.neki.android.core.model.Pose import com.neki.android.core.model.SortOrder import kotlinx.coroutines.flow.Flow +import retrofit2.HttpException import javax.inject.Inject private const val PAGE_SIZE = 20 @@ -86,10 +87,16 @@ class PoseRepositoryImpl @Inject constructor( val collectedIds = excludeIds.toMutableSet() repeat(poseSize) { val excludeIdsString = collectedIds.joinToString(",") - val pose = poseService.getRandomPose( - headCount = headCount.name, - excludeIds = excludeIdsString, - ).data.toModel() + val pose = try { + poseService.getRandomPose( + headCount = headCount.name, + excludeIds = excludeIdsString, + ).data.toModel() + } catch (e: HttpException) { + // Http Error Code 이지만, 클라이언트에서 성공으로 취급 + if (e.code() == NO_MORE_RANDOM_POSE) return@runSuspendCatching result + else throw e + } if (pose.id !in collectedIds) { result.add(pose) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index ef148ff6c..6f9d5fb84 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -3,7 +3,7 @@ package com.neki.android.feature.pose.impl.random import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.neki.android.core.common.coroutine.di.ApplicationScope -import com.neki.android.core.common.exception.RandomPoseRetryExhaustedException +import com.neki.android.core.dataapi.repository.NO_MORE_RANDOM_POSE import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.dataapi.repository.UserRepository import com.neki.android.core.model.PeopleCount @@ -20,6 +20,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import retrofit2.HttpException import timber.log.Timber @HiltViewModel(assistedFactory = RandomPoseViewModel.Factory::class) @@ -162,9 +163,9 @@ internal class RandomPoseViewModel @AssistedInject constructor( ).onSuccess { pose -> reduce { copy(poseList = (poseList + pose).toImmutableList()) } }.onFailure { error -> - if (error is RandomPoseRetryExhaustedException) - Timber.e(error, "중복 포즈") - else Timber.e(error) + if (error is HttpException && error.code() == NO_MORE_RANDOM_POSE) { + postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요")) + } else Timber.e(error) } } } @@ -217,10 +218,8 @@ internal class RandomPoseViewModel @AssistedInject constructor( } }.onFailure { error -> reduce { copy(isLoading = false) } - if (error is RandomPoseRetryExhaustedException) - Timber.e(error, "중복 포즈") - else Timber.e(error) postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요")) + Timber.e(error) } } } From 3c4426f6aae707d4e620304afbd18cbbf4fe584d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:41:37 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[refactor]=20#89:=20=EB=8D=94=20=EC=9D=B4?= =?UTF-8?q?=EC=83=81=20=EC=B6=94=EC=B2=9C=ED=95=A0=20=ED=8F=AC=EC=A6=88?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20API=20=EC=9A=94=EC=B2=AD=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 더 이상 추천할 새로운 포즈가 없을 경우, `hasNewPose` 상태를 `false`로 업데이트하여 불필요하게 다음 포즈를 미리 캐싱하려는 API 요청을 보내지 않도록 수정했습니다. --- .../android/feature/pose/impl/random/RandomPoseContract.kt | 1 + .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt index 7a7c21597..7f44d2c3e 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt @@ -10,6 +10,7 @@ data class RandomPoseUiState( val currentIndex: Int = 0, val poseList: ImmutableList = persistentListOf(), val committedScraps: Map = emptyMap(), + val hasNewPose: Boolean = false, ) { val currentPose: Pose? get() = poseList.getOrNull(currentIndex) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 6f9d5fb84..deaccd7c2 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -155,7 +155,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( postSideEffect(RandomPoseEffect.SwipePoseImage(nextIndex)) // 여분 포즈가 POSE_PREFETCH_THRESHOLD 이하이면 다음 포즈 미리 캐싱 - if (state.poseList.lastIndex - nextIndex < PoseConst.POSE_PREFETCH_THRESHOLD) { + if (state.poseList.lastIndex - nextIndex < PoseConst.POSE_PREFETCH_THRESHOLD && state.hasNewPose) { viewModelScope.launch { poseRepository.getSingleRandomPose( headCount = peopleCount, @@ -164,6 +164,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( reduce { copy(poseList = (poseList + pose).toImmutableList()) } }.onFailure { error -> if (error is HttpException && error.code() == NO_MORE_RANDOM_POSE) { + reduce { copy(hasNewPose = false) } postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요")) } else Timber.e(error) } From 388e033387ae45682c7f940e562830d2a69463f0 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:31:00 +0900 Subject: [PATCH 12/19] =?UTF-8?q?[refactor]=20#89:=20hasNextPost=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neki/android/feature/pose/impl/random/RandomPoseContract.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt index 7f44d2c3e..3c61f360c 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt @@ -10,7 +10,7 @@ data class RandomPoseUiState( val currentIndex: Int = 0, val poseList: ImmutableList = persistentListOf(), val committedScraps: Map = emptyMap(), - val hasNewPose: Boolean = false, + val hasNewPose: Boolean = true, ) { val currentPose: Pose? get() = poseList.getOrNull(currentIndex) From f35d94fa4a4a78fc2018d922c5d504867a3f59dd Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:32:08 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[feat]=20#89:=20API=20Exception=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서버 및 클라이언트 오류 처리를 위한 `ServerApiException`과 `ClientApiException`을 추가했습니다. 각 예외 클래스는 HTTP 상태 코드와 메시지를 포함하여 API 통신 오류를 구체적으로 나타냅니다. --- .../android/core/common/exception/ApiException.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 core/common/src/main/java/com/neki/android/core/common/exception/ApiException.kt diff --git a/core/common/src/main/java/com/neki/android/core/common/exception/ApiException.kt b/core/common/src/main/java/com/neki/android/core/common/exception/ApiException.kt new file mode 100644 index 000000000..bb2c14629 --- /dev/null +++ b/core/common/src/main/java/com/neki/android/core/common/exception/ApiException.kt @@ -0,0 +1,11 @@ +package com.neki.android.core.common.exception + +class ServerApiException( + val code: Int, + message: String, +) : Throwable(message) + +class ClientApiException( + val code: Int, + message: String, +) : Throwable(message) From 463410fc74a7c5932828d879a6aa744bb993d810 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:32:18 +0900 Subject: [PATCH 14/19] =?UTF-8?q?[refactor]=20#89=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20API=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 `HttpException`을 사용하여 처리하던 랜덤 포즈 관련 예외를 커스텀 `ClientApiException`으로 대체하여 처리하도록 수정했습니다. 모든 포즈를 불러왔을 때(NO_MORE_RANDOM_POSE), "포즈를 불러오는데 실패했어요"라는 토스트 메시지가 나타나던 문제를 "모든 포즈를 불러왔어요"로 수정하여 사용자에게 더 정확한 피드백을 제공합니다. --- .../repository/impl/PoseRepositoryImpl.kt | 24 +++++++++++++------ .../pose/impl/random/RandomPoseViewModel.kt | 6 ++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt index 422e6e803..50f848e44 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.neki.android.core.data.repository.impl import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import com.neki.android.core.common.exception.ClientApiException import com.neki.android.core.data.paging.PosePagingSource import com.neki.android.core.data.paging.ScrapPosePagingSource import com.neki.android.core.data.remote.api.PoseService @@ -12,8 +13,10 @@ import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.model.PeopleCount import com.neki.android.core.model.Pose import com.neki.android.core.model.SortOrder +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.ResponseException +import io.ktor.client.plugins.ServerResponseException import kotlinx.coroutines.flow.Flow -import retrofit2.HttpException import javax.inject.Inject private const val PAGE_SIZE = 20 @@ -72,10 +75,17 @@ class PoseRepositoryImpl @Inject constructor( excludeIds: Set, ): Result = runSuspendCatching { val excludeIdsString = excludeIds.joinToString(",") - poseService.getRandomPose( - headCount = headCount.name, - excludeIds = excludeIdsString, - ).data.toModel() + + return@runSuspendCatching try { + poseService.getRandomPose( + headCount = headCount.name, + excludeIds = excludeIdsString, + ).data.toModel() + } catch (e: ResponseException) { + if (e is ClientRequestException && e.response.status.value == NO_MORE_RANDOM_POSE) + throw ClientApiException(NO_MORE_RANDOM_POSE, e.message) + else throw e + } } override suspend fun getMultipleRandomPose( @@ -92,9 +102,9 @@ class PoseRepositoryImpl @Inject constructor( headCount = headCount.name, excludeIds = excludeIdsString, ).data.toModel() - } catch (e: HttpException) { + } catch (e: ClientRequestException) { // Http Error Code 이지만, 클라이언트에서 성공으로 취급 - if (e.code() == NO_MORE_RANDOM_POSE) return@runSuspendCatching result + if (e.response.status.value == NO_MORE_RANDOM_POSE) return@runSuspendCatching result else throw e } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index deaccd7c2..165949137 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -3,6 +3,7 @@ package com.neki.android.feature.pose.impl.random import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.neki.android.core.common.coroutine.di.ApplicationScope +import com.neki.android.core.common.exception.ClientApiException import com.neki.android.core.dataapi.repository.NO_MORE_RANDOM_POSE import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -20,7 +21,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import retrofit2.HttpException import timber.log.Timber @HiltViewModel(assistedFactory = RandomPoseViewModel.Factory::class) @@ -163,9 +163,9 @@ internal class RandomPoseViewModel @AssistedInject constructor( ).onSuccess { pose -> reduce { copy(poseList = (poseList + pose).toImmutableList()) } }.onFailure { error -> - if (error is HttpException && error.code() == NO_MORE_RANDOM_POSE) { + if (error is ClientApiException && error.code == NO_MORE_RANDOM_POSE) { reduce { copy(hasNewPose = false) } - postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요")) + postSideEffect(RandomPoseEffect.ShowToast("모든 포즈를 불러왔어요")) } else Timber.e(error) } } From 1084b202fba8227a02f89928a863f055d06f5954 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:33:43 +0900 Subject: [PATCH 15/19] =?UTF-8?q?[refactor]=20#89:=20DataStoreKey=EC=97=90?= =?UTF-8?q?=EC=84=9C=20HAS=5FVISITED=5FRANDOM=5FPOSE=20=ED=82=A4=20UserRep?= =?UTF-8?q?ository=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neki/android/core/dataapi/datastore/DataStoreKey.kt | 1 - .../core/data/repository/impl/UserRepositoryImpl.kt | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt index edee9a27f..f5d04f609 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt @@ -6,5 +6,4 @@ import androidx.datastore.preferences.core.stringPreferencesKey object DataStoreKey { val ACCESS_TOKEN = stringPreferencesKey("access_token") val REFRESH_TOKEN = stringPreferencesKey("refresh_token") - val HAS_VISITED_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose") } diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index 2e44ef9d1..6917dd119 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.neki.android.core.data.repository.impl import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import com.neki.android.core.data.local.di.UserDataStore import com.neki.android.core.data.remote.api.UserService @@ -21,12 +22,12 @@ class UserRepositoryImpl @Inject constructor( ) : UserRepository { override val hasVisitedRandomPose: Flow = dataStore.data.map { preferences -> - preferences[DataStoreKey.HAS_VISITED_RANDOM_POSE] ?: false + preferences[HAS_VISITED_RANDOM_POSE] ?: false } override suspend fun markRandomPoseAsVisited() { dataStore.edit { preferences -> - preferences[DataStoreKey.HAS_VISITED_RANDOM_POSE] = true + preferences[HAS_VISITED_RANDOM_POSE] = true } } @@ -41,4 +42,8 @@ class UserRepositoryImpl @Inject constructor( override suspend fun updateProfileImage(mediaId: Long?): Result = runSuspendCatching { userService.updateProfileImage(UpdateProfileImageRequest(mediaId)) } + + companion object { + private val HAS_VISITED_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose") + } } From 54803f168c818c9324ec0a2df10b5acdadf6cb69 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:43:54 +0900 Subject: [PATCH 16/19] =?UTF-8?q?[refactor]=20#89:=20=ED=8F=AC=EC=A6=88=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=9E=A9=20=EC=83=81=ED=83=9C=EA=B0=80=20?= =?UTF-8?q?=EC=A6=89=EC=8B=9C=20=EB=B0=98=EC=98=81=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포즈피드에서 포즈를 스크랩했을 때, 해당 상태가 즉시 UI에 반영되지 않던 문제를 해결했습니다. 새로운 포즈를 불러올 때 `committedScraps` 목록에도 스크랩 상태를 함께 업데이트하여 데이터 정합성을 맞춥니다. --- .../feature/pose/impl/random/RandomPoseViewModel.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 165949137..2d2c58f10 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -161,7 +161,12 @@ internal class RandomPoseViewModel @AssistedInject constructor( headCount = peopleCount, excludeIds = state.randomPoseIds, ).onSuccess { pose -> - reduce { copy(poseList = (poseList + pose).toImmutableList()) } + reduce { + copy( + poseList = (poseList + pose).toImmutableList(), + committedScraps = committedScraps + (pose.id to pose.isScrapped), + ) + } }.onFailure { error -> if (error is ClientApiException && error.code == NO_MORE_RANDOM_POSE) { reduce { copy(hasNewPose = false) } From d3a2d20de5c17df66e2517e2b2859bf2e2cd1c7f Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:47:38 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[chore]=20#89:=20detekt=20=EB=A3=B0=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt | 1 - .../neki/android/core/data/repository/impl/UserRepositoryImpl.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt index f5d04f609..e145d556f 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/datastore/DataStoreKey.kt @@ -1,6 +1,5 @@ package com.neki.android.core.dataapi.datastore -import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey object DataStoreKey { diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index 6917dd119..e1dd559c0 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -9,7 +9,6 @@ import com.neki.android.core.data.remote.api.UserService import com.neki.android.core.data.remote.model.request.UpdateProfileImageRequest import com.neki.android.core.data.remote.model.request.UpdateUserInfoRequest import com.neki.android.core.data.util.runSuspendCatching -import com.neki.android.core.dataapi.datastore.DataStoreKey import com.neki.android.core.dataapi.repository.UserRepository import com.neki.android.core.model.UserInfo import kotlinx.coroutines.flow.Flow From 575b6f47a48310bb5ad2ea9671168b8f4b6e6913 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:50:15 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[chore]=20#89:=20detekt=20SwallowedExcept?= =?UTF-8?q?ion=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/data/repository/impl/PoseRepositoryImpl.kt | 6 ++---- detekt-config.yml | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt index 50f848e44..49c499375 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt @@ -14,8 +14,6 @@ import com.neki.android.core.model.PeopleCount import com.neki.android.core.model.Pose import com.neki.android.core.model.SortOrder import io.ktor.client.plugins.ClientRequestException -import io.ktor.client.plugins.ResponseException -import io.ktor.client.plugins.ServerResponseException import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -81,8 +79,8 @@ class PoseRepositoryImpl @Inject constructor( headCount = headCount.name, excludeIds = excludeIdsString, ).data.toModel() - } catch (e: ResponseException) { - if (e is ClientRequestException && e.response.status.value == NO_MORE_RANDOM_POSE) + } catch (e: ClientRequestException) { + if (e.response.status.value == NO_MORE_RANDOM_POSE) throw ClientApiException(NO_MORE_RANDOM_POSE, e.message) else throw e } diff --git a/detekt-config.yml b/detekt-config.yml index f034288ca..d16a96a1c 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -52,6 +52,8 @@ formatting: exceptions: TooGenericExceptionCaught: active: false + SwallowedException: + active: false style: FunctionOnlyReturningConstant: From a853151c4f24438b822d47e0c13eee5fe6c261c3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:01:55 +0900 Subject: [PATCH 19/19] =?UTF-8?q?[refactor]=20#89:=20`markRandomPoseAsVisi?= =?UTF-8?q?ted`=20=ED=95=A8=EC=88=98=EB=AA=85=EC=9D=84=20`setRandomPoseVis?= =?UTF-8?q?ited`=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 --- .../com/neki/android/core/dataapi/repository/UserRepository.kt | 2 +- .../android/core/data/repository/impl/UserRepositoryImpl.kt | 2 +- .../android/feature/pose/impl/random/RandomPoseViewModel.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt index ceb39d1e2..e5506a604 100644 --- a/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt +++ b/core/data-api/src/main/java/com/neki/android/core/dataapi/repository/UserRepository.kt @@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow interface UserRepository { val hasVisitedRandomPose: Flow - suspend fun markRandomPoseAsVisited() + suspend fun setRandomPoseVisited() suspend fun getUserInfo(): Result suspend fun updateUserInfo(nickname: String): Result diff --git a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt index e1dd559c0..b5de53a80 100644 --- a/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/neki/android/core/data/repository/impl/UserRepositoryImpl.kt @@ -24,7 +24,7 @@ class UserRepositoryImpl @Inject constructor( preferences[HAS_VISITED_RANDOM_POSE] ?: false } - override suspend fun markRandomPoseAsVisited() { + override suspend fun setRandomPoseVisited() { dataStore.edit { preferences -> preferences[HAS_VISITED_RANDOM_POSE] = true } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 2d2c58f10..71a6aeb16 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -191,7 +191,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( ) { viewModelScope.launch { if (!userRepository.hasVisitedRandomPose.first()) { - userRepository.markRandomPoseAsVisited() + userRepository.setRandomPoseVisited() } else { reduce { copy(isShowTutorial = false) } }