diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorDialogSpec.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorDialogSpec.kt deleted file mode 100644 index 2d1d9544..00000000 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/ErrorDialogSpec.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.ninecraft.booket.core.common.constants - -import androidx.annotation.StringRes - -data class ErrorDialogSpec( - val title: String? = null, - val message: String, - @StringRes val buttonLabelResId: Int, - val action: () -> Unit, -) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogEvents.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogEvents.kt new file mode 100644 index 00000000..b19347be --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogEvents.kt @@ -0,0 +1,58 @@ +package com.ninecraft.booket.core.common.event + +import com.ninecraft.booket.core.common.constants.ErrorScope +import com.ninecraft.booket.core.common.utils.isNetworkError +import retrofit2.HttpException + +fun postErrorDialog( + errorScope: ErrorScope, + exception: Throwable, + confirmLabel: String = "확인", + onConfirm: () -> Unit = {}, +) { + val (title, message) = when { + exception.isNetworkError() -> { + null to "네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요" + } + + exception is HttpException -> { + when (errorScope) { + ErrorScope.GLOBAL -> { + null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요" + } + + ErrorScope.LOGIN -> { + "로그인 오류" to "예기치 않은 오류가 발생했습니다.\n다시 로그인 해주세요." + } + + ErrorScope.AUTH_SESSION_EXPIRED -> { + null to "세션이 만료되었어요.\n다시 로그인 해주세요" + } + } + } + + else -> { + null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요" + } + } + + val spec = DialogSpec( + title = title, + message = message, + confirmLabel = confirmLabel, + onConfirm = onConfirm, + ) + + EventHelper.sendEvent(event = ReedEvent.ShowDialog(spec)) +} + +fun postLoginRequiredDialog(onConfirm: () -> Unit) { + val spec = DialogSpec( + message = "로그인이 필요한 기능입니다.\n로그인 해주세요.", + confirmLabel = "로그인 하기", + onConfirm = onConfirm, + dismissLabel = "닫기", + ) + + EventHelper.sendEvent(event = ReedEvent.ShowDialog(spec)) +} diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogSpec.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogSpec.kt new file mode 100644 index 00000000..db1866f1 --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/DialogSpec.kt @@ -0,0 +1,10 @@ +package com.ninecraft.booket.core.common.event + +data class DialogSpec( + val message: String, + val confirmLabel: String, + val onConfirm: () -> Unit, + val title: String? = null, + val dismissLabel: String? = null, + val onDismissRequest: () -> Unit = {}, +) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt deleted file mode 100644 index 2daee82e..00000000 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/ErrorEventHelper.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.ninecraft.booket.core.common.event - -import com.ninecraft.booket.core.common.constants.ErrorDialogSpec -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow -import java.util.UUID - -object ErrorEventHelper { - private val _errorEvent = Channel(Channel.BUFFERED) - val errorEvent = _errorEvent.receiveAsFlow() - - fun sendError(event: ErrorEvent) { - _errorEvent.trySend(event) - } -} - -sealed interface ErrorEvent { - data class ShowDialog( - val spec: ErrorDialogSpec, - val key: String = UUID.randomUUID().toString(), - ) : ErrorEvent -} diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/EventHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/EventHelper.kt new file mode 100644 index 00000000..3ba55163 --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/EventHelper.kt @@ -0,0 +1,17 @@ +package com.ninecraft.booket.core.common.event + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow + +object EventHelper { + private val _eventFlow = Channel(Channel.BUFFERED) + val eventFlow = _eventFlow.receiveAsFlow() + + fun sendEvent(event: ReedEvent) { + _eventFlow.trySend(event) + } +} + +sealed interface ReedEvent { + data class ShowDialog(val spec: DialogSpec) : ReedEvent +} diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt index 7474fcb8..9e43d3b4 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/utils/HandleException.kt @@ -1,11 +1,7 @@ package com.ninecraft.booket.core.common.utils -import androidx.annotation.StringRes -import com.ninecraft.booket.core.common.R -import com.ninecraft.booket.core.common.constants.ErrorDialogSpec import com.ninecraft.booket.core.common.constants.ErrorScope -import com.ninecraft.booket.core.common.event.ErrorEvent -import com.ninecraft.booket.core.common.event.ErrorEventHelper +import com.ninecraft.booket.core.common.event.postErrorDialog import com.ninecraft.booket.core.network.response.ErrorResponse import com.orhanobut.logger.Logger import kotlinx.serialization.SerializationException @@ -26,7 +22,7 @@ fun handleException( postErrorDialog( errorScope = ErrorScope.AUTH_SESSION_EXPIRED, exception = exception, - action = { + onConfirm = { onLoginRequired() }, ) @@ -51,48 +47,6 @@ fun handleException( } } -fun postErrorDialog( - errorScope: ErrorScope, - exception: Throwable, - @StringRes buttonLabelResId: Int = R.string.confirm, - action: () -> Unit = {}, -) { - val (title, message) = when { - exception.isNetworkError() -> { - null to "네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요" - } - - exception is HttpException -> { - when (errorScope) { - ErrorScope.GLOBAL -> { - null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요" - } - - ErrorScope.LOGIN -> { - "로그인 오류" to "예기치 않은 오류가 발생했습니다.\n다시 로그인 해주세요." - } - - ErrorScope.AUTH_SESSION_EXPIRED -> { - null to "세션이 만료되었어요.\n다시 로그인 해주세요" - } - } - } - - else -> { - null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요" - } - } - - val spec = ErrorDialogSpec( - title = title, - message = message, - buttonLabelResId = buttonLabelResId, - action = action, - ) - - ErrorEventHelper.sendError(event = ErrorEvent.ShowDialog(spec)) -} - private fun HttpException.parseErrorMessage(): String? { return try { val errorBody = response()?.errorBody()?.string() diff --git a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt index c49b0106..e557f31c 100644 --- a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt +++ b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import com.ninecraft.booket.core.common.analytics.AnalyticsHelper -import com.ninecraft.booket.core.common.utils.UiText +import com.ninecraft.booket.core.common.event.postLoginRequiredDialog import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.BookRepository @@ -35,7 +35,6 @@ import dagger.hilt.android.components.ActivityRetainedComponent import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -import com.ninecraft.booket.core.designsystem.R as designR class LibraryPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, @@ -127,10 +126,13 @@ class LibraryPresenter @AssistedInject constructor( is LibraryUiEvent.OnLibrarySearchClick -> { if (userState is UserState.Guest) { - scope.launch { - sideEffect = LibrarySideEffect.ShowToast(UiText.StringResource(designR.string.login_required)) - navigator.redirectToLogin() - } + postLoginRequiredDialog( + onConfirm = { + scope.launch { + navigator.redirectToLogin() + } + }, + ) } else { navigator.goTo(LibrarySearchScreen) } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 30fbfb8b..0bb0de32 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.ErrorScope -import com.ninecraft.booket.core.common.utils.postErrorDialog +import com.ninecraft.booket.core.common.event.postErrorDialog import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.feature.screens.HomeScreen diff --git a/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt b/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt index b1d44684..cdc440cb 100644 --- a/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt +++ b/feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt @@ -11,11 +11,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import com.ninecraft.booket.core.common.constants.ErrorDialogSpec -import com.ninecraft.booket.core.common.event.ErrorEvent -import com.ninecraft.booket.core.common.event.ErrorEventHelper +import com.ninecraft.booket.core.common.event.DialogSpec +import com.ninecraft.booket.core.common.event.EventHelper +import com.ninecraft.booket.core.common.event.ReedEvent import com.ninecraft.booket.core.designsystem.theme.ReedTheme import com.ninecraft.booket.core.ui.component.ReedDialog import com.ninecraft.booket.feature.screens.SplashScreen @@ -55,13 +54,13 @@ class MainActivity : ComponentActivity() { val backStack = rememberSaveableBackStack(root = SplashScreen) val navigator = rememberCircuitNavigator(backStack) - val dialogSpec = remember { mutableStateOf(null) } + val dialogSpec = remember { mutableStateOf(null) } - // 전역 에러 수신 + // 전역 이벤트 수신 LaunchedEffect(Unit) { - ErrorEventHelper.errorEvent.collect { event -> + EventHelper.eventFlow.collect { event -> when (event) { - is ErrorEvent.ShowDialog -> { + is ReedEvent.ShowDialog -> { dialogSpec.value = event.spec } } @@ -72,10 +71,10 @@ class MainActivity : ComponentActivity() { ReedDialog( title = spec.title, description = spec.message, - confirmButtonText = stringResource(spec.buttonLabelResId), - + confirmButtonText = spec.confirmLabel, + dismissButtonText = spec.dismissLabel, onConfirmRequest = { - spec.action() + spec.onConfirm() dialogSpec.value = null }, onDismissRequest = { diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt index b945e7e0..e78e03f7 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.BookStatus +import com.ninecraft.booket.core.common.event.postLoginRequiredDialog import com.ninecraft.booket.core.common.utils.UiText import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.AuthRepository @@ -38,7 +39,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch -import com.ninecraft.booket.core.designsystem.R as designR class BookSearchPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, @@ -120,13 +120,18 @@ class BookSearchPresenter @AssistedInject constructor( } fun upsertBook(isbn13: String, bookStatus: String) { - scope.launch { - if (userState is UserState.Guest) { - sideEffect = BookSearchSideEffect.ShowToast(UiText.StringResource(designR.string.login_required)) - navigator.redirectToLogin() - return@launch - } + if (userState is UserState.Guest) { + postLoginRequiredDialog( + onConfirm = { + scope.launch { + navigator.redirectToLogin() + } + }, + ) + return + } + scope.launch { repository.upsertBook(isbn13, bookStatus) .onSuccess { registeredUserBookId = it.userBookId diff --git a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt index a27d3d08..02a0761e 100644 --- a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt +++ b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt @@ -8,13 +8,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.ErrorScope -import com.ninecraft.booket.core.common.utils.postErrorDialog +import com.ninecraft.booket.core.common.event.postErrorDialog import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.RemoteConfigRepository import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.core.model.AutoLoginState import com.ninecraft.booket.core.model.OnboardingState -import com.ninecraft.booket.core.ui.R import com.ninecraft.booket.feature.screens.HomeScreen import com.ninecraft.booket.feature.screens.LoginScreen import com.ninecraft.booket.feature.screens.OnboardingScreen @@ -64,8 +63,8 @@ class SplashPresenter @AssistedInject constructor( postErrorDialog( errorScope = ErrorScope.GLOBAL, exception = exception, - buttonLabelResId = R.string.retry, - action = { checkTermsAgreement() }, + confirmLabel = "다시 시도하기", + onConfirm = { checkTermsAgreement() }, ) } }