-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] SnackbarHost 설정 및 Snackbar Compose 마이그레이션 #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9d91c71
7da6f47
77e1d8f
3aae98f
3adc1d4
15eaf8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package com.daedan.festabook.presentation.common.component | ||
|
|
||
| import androidx.compose.material3.Snackbar | ||
| import androidx.compose.material3.SnackbarData | ||
| import androidx.compose.material3.SnackbarDuration | ||
| import androidx.compose.material3.SnackbarHostState | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.rememberCoroutineScope | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.res.stringResource | ||
| import com.daedan.festabook.R | ||
| import com.daedan.festabook.data.util.ApiResultException | ||
| import com.daedan.festabook.presentation.theme.FestabookColor | ||
| import kotlinx.coroutines.CoroutineScope | ||
| import kotlinx.coroutines.launch | ||
| import kotlin.reflect.KClass | ||
|
|
||
| @Composable | ||
| fun FestabookSnackbar( | ||
| data: SnackbarData, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| Snackbar( | ||
| modifier = modifier, | ||
| snackbarData = data, | ||
| actionColor = FestabookColor.accentBlue, | ||
| ) | ||
| } | ||
|
|
||
| class SnackbarManager( | ||
| val hostState: SnackbarHostState, | ||
| val scope: CoroutineScope, | ||
| private val actionLabel: String, | ||
| private val errorMessages: Map<KClass<out ApiResultException>, String>, | ||
| private val defaultErrorMessage: String, | ||
| ) { | ||
| fun show(message: String) { | ||
| hostState.currentSnackbarData?.dismiss() | ||
| scope.launch { | ||
| hostState.showSnackbar( | ||
| message = message, | ||
| duration = SnackbarDuration.Short, | ||
| actionLabel = actionLabel, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| fun showError(throwable: Throwable) { | ||
| val message = errorMessages[throwable::class] ?: defaultErrorMessage | ||
| show(message) | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun rememberAppSnackbarManager( | ||
| snackbarHostState: SnackbarHostState, | ||
| scope: CoroutineScope = rememberCoroutineScope(), | ||
| ): SnackbarManager { | ||
| val clientErrorMessage = stringResource(R.string.error_client_exception) | ||
| val serverErrorMessage = stringResource(R.string.error_server_exception) | ||
| val networkErrorMessage = stringResource(R.string.error_network_exception) | ||
| val unknownErrorMessage = stringResource(R.string.error_unknown_exception) | ||
| val actionLabel = stringResource(R.string.fail_snackbar_confirm) | ||
|
|
||
| val errorMessages = | ||
| remember { | ||
| mapOf( | ||
| ApiResultException.ClientException::class to clientErrorMessage, | ||
| ApiResultException.ServerException::class to serverErrorMessage, | ||
| ApiResultException.NetworkException::class to networkErrorMessage, | ||
| ApiResultException.UnknownException::class to unknownErrorMessage, | ||
| ) | ||
| } | ||
|
|
||
| return remember(snackbarHostState, scope) { | ||
| SnackbarManager(snackbarHostState, scope, actionLabel, errorMessages, unknownErrorMessage) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,16 +3,24 @@ package com.daedan.festabook.presentation.main.component | |||||||||||||||
| import androidx.activity.compose.BackHandler | ||||||||||||||||
| import androidx.compose.foundation.layout.padding | ||||||||||||||||
| import androidx.compose.material3.Scaffold | ||||||||||||||||
| import androidx.compose.material3.SnackbarHost | ||||||||||||||||
| import androidx.compose.material3.SnackbarHostState | ||||||||||||||||
| import androidx.compose.runtime.Composable | ||||||||||||||||
| import androidx.compose.runtime.remember | ||||||||||||||||
| import androidx.compose.ui.Modifier | ||||||||||||||||
| import androidx.compose.ui.graphics.graphicsLayer | ||||||||||||||||
| import androidx.compose.ui.input.pointer.PointerEventPass | ||||||||||||||||
| import androidx.compose.ui.input.pointer.pointerInput | ||||||||||||||||
| import androidx.compose.ui.res.stringResource | ||||||||||||||||
| import androidx.lifecycle.viewmodel.compose.viewModel | ||||||||||||||||
| import androidx.navigation.compose.NavHost | ||||||||||||||||
| import com.daedan.festabook.R | ||||||||||||||||
| import com.daedan.festabook.logging.DefaultFirebaseLogger | ||||||||||||||||
| import com.daedan.festabook.presentation.NotificationPermissionManager | ||||||||||||||||
| import com.daedan.festabook.presentation.common.ObserveAsEvents | ||||||||||||||||
| import com.daedan.festabook.presentation.common.component.FestabookSnackbar | ||||||||||||||||
| import com.daedan.festabook.presentation.common.component.SnackbarManager | ||||||||||||||||
| import com.daedan.festabook.presentation.common.component.rememberAppSnackbarManager | ||||||||||||||||
| import com.daedan.festabook.presentation.home.HomeViewModel | ||||||||||||||||
| import com.daedan.festabook.presentation.home.navigation.homeNavGraph | ||||||||||||||||
| import com.daedan.festabook.presentation.main.FestabookMainTab | ||||||||||||||||
|
|
@@ -52,6 +60,10 @@ fun MainScreen( | |||||||||||||||
| settingViewModel: SettingViewModel = viewModel(), | ||||||||||||||||
| ) { | ||||||||||||||||
| val navigator = rememberFestabookNavigator() | ||||||||||||||||
| val snackbarHostState = remember { SnackbarHostState() } | ||||||||||||||||
| val snackbarManager = rememberAppSnackbarManager(snackbarHostState) | ||||||||||||||||
| val backPressExitMessage = stringResource(R.string.back_press_exit_message) | ||||||||||||||||
| val noticeEnabledMessage = stringResource(R.string.setting_notice_enabled) | ||||||||||||||||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용되지 않는 변수 확인 필요
🔧 사용하지 않는 경우 제거 제안 val snackbarHostState = remember { SnackbarHostState() }
val snackbarManager = rememberAppSnackbarManager(snackbarHostState)
val backPressExitMessage = stringResource(R.string.back_press_exit_message)
-val noticeEnabledMessage = stringResource(R.string.setting_notice_enabled)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| ObserveAsEvents(flow = mainViewModel.navigateNewsEvent) { | ||||||||||||||||
| navigator.navigateToMainTab(FestabookMainTab.NEWS) | ||||||||||||||||
|
|
@@ -60,23 +72,22 @@ fun MainScreen( | |||||||||||||||
| if (isDoublePress) { | ||||||||||||||||
| onAppFinish() | ||||||||||||||||
| } else { | ||||||||||||||||
| // TODO: SnackBarHost로 변경 | ||||||||||||||||
| // showToast(getString(R.string.back_press_exit_message)) | ||||||||||||||||
| snackbarManager.show(backPressExitMessage) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| ObserveAsEvents(flow = homeViewModel.navigateToScheduleEvent) { | ||||||||||||||||
| navigator.navigateToMainTab(FestabookMainTab.SCHEDULE) | ||||||||||||||||
| } | ||||||||||||||||
| ObserveAsEvents(flow = settingViewModel.success) { | ||||||||||||||||
| // TODO: SnackBarHost로 변경 | ||||||||||||||||
| // showSnackBar(getString(R.string.setting_notice_enabled)) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| BackHandler { | ||||||||||||||||
| mainViewModel.onBackPressed() | ||||||||||||||||
| } | ||||||||||||||||
| Scaffold( | ||||||||||||||||
| // TODO: 스낵바 구현 및 하위 프래그먼트에 해당 SnackBar 적용 | ||||||||||||||||
| snackbarHost = { | ||||||||||||||||
| SnackbarHost(hostState = snackbarHostState) { data -> | ||||||||||||||||
| FestabookSnackbar(data) | ||||||||||||||||
| } | ||||||||||||||||
| }, | ||||||||||||||||
| bottomBar = { | ||||||||||||||||
| if (navigator.shouldShowBottomBar) { | ||||||||||||||||
| FestabookBottomNavigationBar( | ||||||||||||||||
|
|
@@ -124,6 +135,7 @@ fun MainScreen( | |||||||||||||||
| placeMapViewModel = placeMapViewModel, | ||||||||||||||||
| locationSource = locationSource, | ||||||||||||||||
| logger = logger, | ||||||||||||||||
| onShowErrorSnackBar = snackbarManager::showError, | ||||||||||||||||
| onStartPlaceDetail = { | ||||||||||||||||
| navigator.navigate( | ||||||||||||||||
| FestabookRoute.PlaceDetail( | ||||||||||||||||
|
|
@@ -144,6 +156,7 @@ fun MainScreen( | |||||||||||||||
| notificationPermissionManager = notificationPermissionManager, | ||||||||||||||||
| onNavigateToExplore = onNavigateToExplore, | ||||||||||||||||
| onSubscriptionConfirm = onSubscriptionConfirm, | ||||||||||||||||
| snackbarManager = snackbarManager, | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -160,6 +173,7 @@ private fun FestabookNavHost( | |||||||||||||||
| notificationPermissionManager: NotificationPermissionManager, | ||||||||||||||||
| onNavigateToExplore: () -> Unit, | ||||||||||||||||
| onSubscriptionConfirm: () -> Unit, | ||||||||||||||||
| snackbarManager: SnackbarManager, | ||||||||||||||||
| modifier: Modifier = Modifier, | ||||||||||||||||
| ) { | ||||||||||||||||
| NavHost( | ||||||||||||||||
|
|
@@ -172,23 +186,27 @@ private fun FestabookNavHost( | |||||||||||||||
| mainViewModel = mainViewModel, | ||||||||||||||||
| onNavigateToExplore = onNavigateToExplore, | ||||||||||||||||
| onSubscriptionConfirm = onSubscriptionConfirm, | ||||||||||||||||
| onShowErrorSnackbar = snackbarManager::showError, | ||||||||||||||||
| ) | ||||||||||||||||
| scheduleNavGraph( | ||||||||||||||||
| viewModel = scheduleViewModel, | ||||||||||||||||
| onShowErrorSnackbar = snackbarManager::showError, | ||||||||||||||||
| ) | ||||||||||||||||
| placeMapNavGraph( | ||||||||||||||||
| placeDetailViewModelFactory = placeDetailViewModelFactory, | ||||||||||||||||
| onBackToPreviousClick = { navigator.popBackStack() }, | ||||||||||||||||
| onShowErrorSnackbar = snackbarManager::showError, | ||||||||||||||||
| ) | ||||||||||||||||
| newsNavGraph( | ||||||||||||||||
| viewModel = newsViewModel, | ||||||||||||||||
| onShowErrorSnackbar = snackbarManager::showError, | ||||||||||||||||
| ) | ||||||||||||||||
| settingNavGraph( | ||||||||||||||||
| homeViewModel = homeViewModel, | ||||||||||||||||
| settingViewModel = settingViewModel, | ||||||||||||||||
| notificationPermissionManager = notificationPermissionManager, | ||||||||||||||||
| onShowSnackBar = { }, | ||||||||||||||||
| onShowErrorSnackBar = { }, | ||||||||||||||||
| onShowSnackBar = snackbarManager::show, | ||||||||||||||||
| onShowErrorSnackBar = snackbarManager::showError, | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when문을 사용하신 이유는 error 일때Snackbar말고 다른 상태에 대한snackbar도 사용할 경우도 있어서 확장성을 고려하신걸까요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
더 확장해서 festivalUiState의 사이드 이펙트를 이 LaunchedEffect에서 작성하도록 하고 싶었습니다.