diff --git a/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBar.kt b/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBar.kt index 3649bbff..fb63220e 100644 --- a/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBar.kt +++ b/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBar.kt @@ -16,12 +16,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp -import com.byeboo.app.R +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme @Composable fun CustomSnackBar( - message: String, + customSnackBarType: CustomSnackBarType, modifier: Modifier = Modifier, ) { Row( @@ -35,7 +35,7 @@ fun CustomSnackBar( verticalAlignment = Alignment.CenterVertically, ) { Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_alert), + imageVector = ImageVector.vectorResource(id = customSnackBarType.icon), contentDescription = "알림", tint = Color.Unspecified, ) @@ -43,7 +43,7 @@ fun CustomSnackBar( Spacer(modifier = Modifier.width(8.dp)) Text( - text = message, + text = customSnackBarType.message, style = ByeBooTheme.typography.body6, color = ByeBooTheme.colors.gray50, ) diff --git a/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBarVisuals.kt b/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBarVisuals.kt new file mode 100644 index 00000000..bb3dfae7 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/core/designsystem/component/snackbar/CustomSnackBarVisuals.kt @@ -0,0 +1,14 @@ +package com.byeboo.app.core.designsystem.component.snackbar + +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarVisuals +import com.byeboo.app.core.designsystem.type.CustomSnackBarType + +class CustomSnackBarVisuals( + val type: CustomSnackBarType, + override val actionLabel: String? = null, + override val withDismissAction: Boolean = false, + override val duration: SnackbarDuration = SnackbarDuration.Short, +) : SnackbarVisuals { + override val message: String = type.message +} diff --git a/app/src/main/java/com/byeboo/app/core/designsystem/event/SnackbarTrigger.kt b/app/src/main/java/com/byeboo/app/core/designsystem/event/SnackbarTrigger.kt index 757ac512..b1e0cd23 100644 --- a/app/src/main/java/com/byeboo/app/core/designsystem/event/SnackbarTrigger.kt +++ b/app/src/main/java/com/byeboo/app/core/designsystem/event/SnackbarTrigger.kt @@ -1,8 +1,9 @@ package com.byeboo.app.core.designsystem.event import androidx.compose.runtime.staticCompositionLocalOf +import com.byeboo.app.core.designsystem.type.CustomSnackBarType val LocalSnackBarTrigger = - staticCompositionLocalOf<(String) -> Unit> { + staticCompositionLocalOf<(CustomSnackBarType) -> Unit> { error("No SnackBar provided") } diff --git a/app/src/main/java/com/byeboo/app/core/designsystem/type/CustomSnackBarType.kt b/app/src/main/java/com/byeboo/app/core/designsystem/type/CustomSnackBarType.kt new file mode 100644 index 00000000..17ae85b2 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/core/designsystem/type/CustomSnackBarType.kt @@ -0,0 +1,21 @@ +package com.byeboo.app.core.designsystem.type + +import androidx.annotation.DrawableRes +import com.byeboo.app.R + +sealed class CustomSnackBarType( + @DrawableRes val icon: Int, + val message: String, +) { + object ALERT : CustomSnackBarType( + icon = R.drawable.ic_alert, + message = "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + ) + + class SUCCESS( + message: String, + ) : CustomSnackBarType( + icon = R.drawable.ic_success, + message = message, + ) +} diff --git a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoScreen.kt b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoScreen.kt index 6fee9c35..99895947 100644 --- a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoScreen.kt @@ -68,7 +68,7 @@ fun UserInfoRoute( when (effect) { is UserInfoSideEffect.NavigateToLoading -> navigateToLoading() is UserInfoSideEffect.ShowSnackBar -> { - showSnackBar(effect.message) + showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoState.kt b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoState.kt index f71cc945..f62553cd 100644 --- a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.auth.userinfo +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.domain.model.auth.Feeling import com.byeboo.app.domain.model.auth.NicknameValidationResult import com.byeboo.app.domain.model.auth.QuestStyle @@ -16,6 +17,6 @@ sealed interface UserInfoSideEffect { data object NavigateToLoading : UserInfoSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : UserInfoSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoViewModel.kt index 0e3efb0e..4956591c 100644 --- a/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/auth/userinfo/UserInfoViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.auth.userinfo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.model.auth.Feeling import com.byeboo.app.domain.model.auth.NicknameValidationResult @@ -147,7 +148,9 @@ class UserInfoViewModel } else { hasSubmitted = false _sideEffect.emit( - UserInfoSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + UserInfoSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/HomeScreen.kt b/app/src/main/java/com/byeboo/app/presentation/home/HomeScreen.kt index e2cfc253..fcf4ea51 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/HomeScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/HomeScreen.kt @@ -96,7 +96,7 @@ fun HomeRoute( is HomeSideEffect.NavigateToOffboardingCompletedGuide -> navigateToOffboardingCompletedGuide() is HomeSideEffect.NavigateToOffboardingNewJourney -> navigateToOffboardingNewJourney() is HomeSideEffect.ShowSnackBar -> { - showSnackBar(effect.message) + showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/HomeUiState.kt b/app/src/main/java/com/byeboo/app/presentation/home/HomeUiState.kt index 7660804a..444f5ead 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/HomeUiState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/HomeUiState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.home +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.domain.model.home.HomeStatus @@ -31,6 +32,6 @@ sealed interface HomeSideEffect { data object NavigateToOffboardingNewJourney : HomeSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : HomeSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/HomeViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/home/HomeViewModel.kt index bcde0eb5..f8235c30 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.model.JourneyStatusType import com.byeboo.app.domain.model.home.HomeStatus @@ -78,7 +79,9 @@ class HomeViewModel if (!errorMessage.contains("HTTP 404")) { hasError = true _sideEffect.emit( - HomeSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + HomeSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletScreen.kt b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletScreen.kt index e7cb5f8a..f82b315b 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletScreen.kt @@ -46,7 +46,7 @@ fun HomeAmuletRoute( viewModel.sideEffect.collect { effect -> when (effect) { is HomeAmuletSideEffect.NavigateToHomeOnboarding -> navigateToHomeOnboarding() - is HomeAmuletSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is HomeAmuletSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletState.kt b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletState.kt index ba810669..8b470668 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.home.homeamulet import com.byeboo.app.R +import com.byeboo.app.core.designsystem.type.CustomSnackBarType data class HomeAmuletState( val journey: AmuletType = AmuletType.EMOTION_FACE, @@ -12,7 +13,7 @@ sealed interface HomeAmuletSideEffect { data object NavigateToHomeOnboarding : HomeAmuletSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : HomeAmuletSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletViewModel.kt index a0243964..6f6ea95d 100644 --- a/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/home/homeamulet/HomeAmuletViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.home.homeamulet import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.repository.auth.UserRepository import com.byeboo.app.domain.repository.quest.QuestStateRepository @@ -68,7 +69,9 @@ class HomeAmuletViewModel ) }.onFailure { _sideEffect.emit( - HomeAmuletSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + HomeAmuletSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/main/MainNavHost.kt b/app/src/main/java/com/byeboo/app/presentation/main/MainNavHost.kt index 5ad2726c..636008dc 100644 --- a/app/src/main/java/com/byeboo/app/presentation/main/MainNavHost.kt +++ b/app/src/main/java/com/byeboo/app/presentation/main/MainNavHost.kt @@ -133,6 +133,21 @@ fun MainNavHost( navOptions = clearStackNavOptions, ) }, + navigateToQuestCommonAnswer = { answerId -> + navigator.navigateToQuestCommonAnswer( + answerId = answerId, + navOptions = clearStackNavOptions, + ) + }, + navigateToQuestMyAnswers = { + navigator.navigateToQuestMyAnswers(navOptions = clearStackNavOptions) + }, + navigateToQuestMyAnswerDetail = { answerId -> + navigator.navigateToQuestMyAnswerDetail( + answerId = answerId, + navOptions = clearStackNavOptions, + ) + }, navigateUp = navigator::navigateUp, paddingValues = paddingValues, ) diff --git a/app/src/main/java/com/byeboo/app/presentation/main/MainNavigator.kt b/app/src/main/java/com/byeboo/app/presentation/main/MainNavigator.kt index 33799762..e579a23d 100644 --- a/app/src/main/java/com/byeboo/app/presentation/main/MainNavigator.kt +++ b/app/src/main/java/com/byeboo/app/presentation/main/MainNavigator.kt @@ -28,6 +28,9 @@ import com.byeboo.app.presentation.quest.behavior.navigation.navigateToQuestBeha import com.byeboo.app.presentation.quest.behavior.navigation.navigateToQuestBehaviorComplete import com.byeboo.app.presentation.quest.navigation.Quest import com.byeboo.app.presentation.quest.navigation.navigateToQuest +import com.byeboo.app.presentation.quest.navigation.navigateToQuestCommonAnswer +import com.byeboo.app.presentation.quest.navigation.navigateToQuestMyAnswerDetail +import com.byeboo.app.presentation.quest.navigation.navigateToQuestMyAnswers import com.byeboo.app.presentation.quest.navigation.navigateToQuestReview import com.byeboo.app.presentation.quest.navigation.navigateToQuestStart import com.byeboo.app.presentation.quest.navigation.navigateToQuestTip @@ -198,6 +201,24 @@ class MainNavigator( navController.navigateToQuestBehaviorComplete(questId = questId, navOptions = navOptions) } + fun navigateToQuestCommonAnswer( + answerId: Long, + navOptions: NavOptions? = null, + ) { + navController.navigateToQuestCommonAnswer(answerId = answerId, navOptions = navOptions) + } + + fun navigateToQuestMyAnswers(navOptions: NavOptions? = null) { + navController.navigateToQuestMyAnswers(navOptions = navOptions) + } + + fun navigateToQuestMyAnswerDetail( + answerId: Long, + navOptions: NavOptions? = null, + ) { + navController.navigateToQuestMyAnswerDetail(answerId = answerId, navOptions = navOptions) + } + fun navigateToQuestReview( questId: Long, navOptions: NavOptions? = null, diff --git a/app/src/main/java/com/byeboo/app/presentation/main/MainScreen.kt b/app/src/main/java/com/byeboo/app/presentation/main/MainScreen.kt index fffffe73..3be9f542 100644 --- a/app/src/main/java/com/byeboo/app/presentation/main/MainScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/main/MainScreen.kt @@ -22,7 +22,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.navOptions import com.byeboo.app.core.designsystem.component.backhandler.ByeBooBackHandler import com.byeboo.app.core.designsystem.component.snackbar.CustomSnackBar +import com.byeboo.app.core.designsystem.component.snackbar.CustomSnackBarVisuals import com.byeboo.app.core.designsystem.event.LocalSnackBarTrigger +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme import com.byeboo.app.core.util.screenHeightDp import com.byeboo.app.core.util.screenWidthDp @@ -46,12 +48,16 @@ fun MainScreen( val status by viewModel.journeyStatus.collectAsStateWithLifecycle() val isMoveToQuestHome by viewModel.questHomeNavigation.collectAsStateWithLifecycle() - val onShowSnackBar: (String) -> Unit = { message -> + val onShowSnackBar: (CustomSnackBarType) -> Unit = { type -> scope.launch { snackBarHostState.currentSnackbarData?.dismiss() val job = launch { - snackBarHostState.showSnackbar(message) + snackBarHostState.showSnackbar( + CustomSnackBarVisuals( + type = type, + ), + ) } delay(3000L) job.cancel() @@ -130,7 +136,9 @@ fun MainScreen( .padding(horizontal = screenWidthDp(24.dp)) .padding(bottom = snackBarBottomInset), ) { snackBar -> - CustomSnackBar(message = snackBar.visuals.message) + val customVisuals = snackBar.visuals as? CustomSnackBarVisuals + val type = customVisuals?.type ?: CustomSnackBarType.ALERT + CustomSnackBar(customSnackBarType = type) } }, bottomBar = { diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageScreen.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageScreen.kt index 42cdb539..306095b2 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageScreen.kt @@ -143,7 +143,7 @@ fun MyPageRoute( is MyPageSideEffect.NavigateToTutorial -> navigateToTutorial() is MyPageSideEffect.NavigateToSplash -> navigateToSplash() is MyPageSideEffect.NavigateToBlockedUsers -> navigateToBlockedUsers() - is MyPageSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is MyPageSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } @@ -498,7 +498,6 @@ private fun ByeBooUniverseSection( style = ByeBooTheme.typography.body2, ) } - Spacer(modifier = Modifier.height(screenHeightDp(12.dp))) } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageState.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageState.kt index 91b2a16a..c09d6f79 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/MyPageState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.mypage import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType @Immutable data class MyPageState( @@ -37,6 +38,6 @@ sealed interface MyPageSideEffect { data object NavigateToBlockedUsers : MyPageSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : MyPageSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/MypageViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/MypageViewModel.kt index 58853b2c..c9c1d07d 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/MypageViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/MypageViewModel.kt @@ -3,6 +3,7 @@ package com.byeboo.app.presentation.mypage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.byeboo.app.BuildConfig +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.repository.auth.UserRepository import com.byeboo.app.domain.repository.fcm.FcmTokenRepository @@ -190,7 +191,9 @@ class MyPageViewModel _sideEffect.emit(MyPageSideEffect.NavigateToSplash) }.onFailure { _sideEffect.emit( - MyPageSideEffect.ShowSnackBar(message = "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + MyPageSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -206,7 +209,9 @@ class MyPageViewModel _sideEffect.emit(MyPageSideEffect.NavigateToSplash) }.onFailure { _sideEffect.emit( - MyPageSideEffect.ShowSnackBar(message = "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + MyPageSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersScreen.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersScreen.kt index ca9a53ea..09930997 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersScreen.kt @@ -55,7 +55,7 @@ fun BlockedUsersRoute( viewModel.sideEffect.collect { effect -> when (effect) { is BlockedUsersSideEffect.NavigateUp -> navigateUp() - is BlockedUsersSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is BlockedUsersSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersState.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersState.kt index 72d328de..1899cbbb 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/blockedusers/BlockedUsersState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.mypage.blockedusers import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.presentation.mypage.type.User import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -15,6 +16,6 @@ sealed interface BlockedUsersSideEffect { data object NavigateUp : BlockedUsersSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : BlockedUsersSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileScreen.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileScreen.kt index f7608994..84de851b 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileScreen.kt @@ -58,7 +58,7 @@ fun EditProfileRoute( viewModel.sideEffect.collect { effect -> when (effect) { is EditProfileSideEffect.NavigateToMyPage -> navigateToMyPage() - is EditProfileSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is EditProfileSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileState.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileState.kt index 38d97291..cfeb46d4 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.mypage.editprofile +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.domain.model.auth.NicknameValidationResult data class EditProfileState( @@ -15,6 +16,6 @@ sealed interface EditProfileSideEffect { ) : EditProfileSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : EditProfileSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileViewModel.kt index 6adbba6f..ee40d5e9 100644 --- a/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/mypage/editprofile/EditProfileViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.mypage.editprofile import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.domain.model.auth.NicknameValidationResult import com.byeboo.app.domain.model.auth.NicknameValidator import com.byeboo.app.domain.repository.auth.UserRepository @@ -75,7 +76,9 @@ class EditProfileViewModel _sideEffect.emit(EditProfileSideEffect.NavigateToMyPage(nickname)) }.onFailure { _sideEffect.emit( - EditProfileSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + EditProfileSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyState.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyState.kt index 0cf385a6..e5116df0 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.offboarding +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.presentation.offboarding.model.JourneyCard import com.byeboo.app.presentation.offboarding.model.JourneyStatus @@ -26,6 +27,6 @@ sealed interface OffboardingJourneySideEffect { ) : OffboardingJourneySideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : OffboardingJourneySideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyViewModel.kt index 857ff53b..048c24ea 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/OffboardingJourneyViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.offboarding import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.repository.offboarding.OffboardingJourneyRepository @@ -67,7 +68,7 @@ class OffboardingJourneyViewModel }.onFailure { e -> _sideEffect.emit( OffboardingJourneySideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideScreen.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideScreen.kt index 9d36fd05..b5475a31 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideScreen.kt @@ -82,7 +82,7 @@ fun OffboardingCompletedGuideRoute( is OffboardingCompletedGuideSideEffect.NavigateToHome -> navigateToHome() is OffboardingCompletedGuideSideEffect.NavigateToOffboardingNewJourney -> navigateToOffboardingNewJourney() is OffboardingCompletedGuideSideEffect.NavigateToOffboardingCompletedJourney -> navigateToOffboardingCompletedJourney() - is OffboardingCompletedGuideSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is OffboardingCompletedGuideSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideState.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideState.kt index 7f0f4b45..5029c8b3 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideState.kt @@ -1,5 +1,7 @@ package com.byeboo.app.presentation.offboarding.offboardingcompletedguide +import com.byeboo.app.core.designsystem.type.CustomSnackBarType + data class OffboardingCompletedGuideState( val nickname: String = "하츠핑", val journeyName: String = "감정 직면", @@ -13,6 +15,6 @@ sealed interface OffboardingCompletedGuideSideEffect { data object NavigateToOffboardingCompletedJourney : OffboardingCompletedGuideSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : OffboardingCompletedGuideSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideViewModel.kt index 24956dcd..b7da0076 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedguide/OffboardingCompletedGuideViewModel.kt @@ -3,6 +3,7 @@ package com.byeboo.app.presentation.offboarding.offboardingcompletedguide import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.repository.auth.UserRepository import com.byeboo.app.domain.repository.quest.QuestStateRepository @@ -54,7 +55,7 @@ class OffboardingCompletedGuideViewModel .catch { e -> _sideEffect.emit( OffboardingCompletedGuideSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) }.collect { name -> @@ -68,7 +69,7 @@ class OffboardingCompletedGuideViewModel }.onFailure { e -> _sideEffect.emit( OffboardingCompletedGuideSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedjourney/OffboardingCompletedJourneyScreen.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedjourney/OffboardingCompletedJourneyScreen.kt index 0f956a89..e8e9a3f9 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedjourney/OffboardingCompletedJourneyScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingcompletedjourney/OffboardingCompletedJourneyScreen.kt @@ -60,7 +60,7 @@ fun OffboardingCompletedJourneyRoute( navigateToOffboardingQuestCompleted( effect.journey, ) - is OffboardingJourneySideEffect.ShowSnackBar -> showSnackBar(effect.message) + is OffboardingJourneySideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedScreen.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedScreen.kt index d4dc3025..99964fd6 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedScreen.kt @@ -56,7 +56,7 @@ fun OffboardingQuestCompletedRoute( effect.questId, effect.journey, ) - is QuestCompletedSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestCompletedSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedState.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedState.kt index d4b2b669..cfc1eff5 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.offboarding.offboardingquestcompleted +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.presentation.offboarding.model.QuestCompletedGroup import com.byeboo.app.presentation.quest.model.Quest @@ -23,6 +24,6 @@ sealed interface QuestCompletedSideEffect { ) : QuestCompletedSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestCompletedSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedViewModel.kt index 9f4af4cc..92934418 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestcompleted/OffboardingQuestCompletedViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.domain.repository.auth.UserRepository import com.byeboo.app.domain.repository.offboarding.OffboardingQuestCompletedRepository @@ -60,7 +61,7 @@ class OffboardingQuestCompletedViewModel viewModelScope.launch { _sideEffect.emit( QuestCompletedSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewScreen.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewScreen.kt index 0fbc60d1..3e73b6f4 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewScreen.kt @@ -83,7 +83,7 @@ fun OffboardingQuestReviewRoute( true, effect.imageKey, ) - is OffboardingQuestReviewSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is OffboardingQuestReviewSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewState.kt b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewState.kt index 025eb5c7..12625137 100644 --- a/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/offboarding/offboardingquestreview/OffboardingQuestReviewState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.offboarding.offboardingquestreview +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import java.time.LocalDate @@ -38,6 +39,6 @@ sealed interface OffboardingQuestReviewSideEffect { ) : OffboardingQuestReviewSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : OffboardingQuestReviewSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/QuestScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/QuestScreen.kt index 8819c87e..49868917 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/QuestScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/QuestScreen.kt @@ -37,6 +37,8 @@ fun QuestRoute( navigateToQuestRecording: (Long) -> Unit, navigateToQuestBehavior: (Long) -> Unit, navigateToQuestReview: (Long) -> Unit, + navigateToCommonAnswer: (Long) -> Unit, + navigateToQuestMyAnswers: () -> Unit, paddingValues: PaddingValues, viewModel: QuestViewModel = hiltViewModel(), ) { @@ -68,8 +70,10 @@ fun QuestRoute( navigateToQuestBehavior(effect.questId) is QuestSideEffect.NavigateToQuestReview -> navigateToQuestReview(effect.questId) + is QuestSideEffect.NavigateToQuestMyAnswers -> + navigateToQuestMyAnswers() is QuestSideEffect.ShowSnackBar -> - showSnackBar(effect.message) + showSnackBar(effect.snackBarType) } } } @@ -78,11 +82,13 @@ fun QuestRoute( uiState = uiState, listState = listState, paddingValues = paddingValues, - onQuestClick = viewModel::onQuestClick, + onQuestClick = viewModel::onQuestClicked, + onMyAnswersClick = viewModel::onMyAnswersClicked, onDismissModal = viewModel::onQuitDismissModal, - onTipClick = viewModel::onTipClick, + onTipClick = viewModel::onTipClicked, onQuestStart = viewModel::onQuestStart, onTabClick = viewModel::onTabClicked, + onCommonAnswerClick = navigateToCommonAnswer, onDateChange = viewModel::onDateChange, ) } @@ -93,10 +99,12 @@ private fun QuestScreen( listState: LazyListState, paddingValues: PaddingValues, onQuestClick: (Long) -> Unit, + onMyAnswersClick: () -> Unit, onDismissModal: () -> Unit, onTipClick: () -> Unit, onQuestStart: () -> Unit, onTabClick: (QuestTab) -> Unit, + onCommonAnswerClick: (Long) -> Unit, onDateChange: (LocalDate) -> Unit, ) { if (uiState.myJourneyState.showQuitModal) { @@ -145,6 +153,8 @@ private fun QuestScreen( QuestTab.COMMON_JOURNEY -> { CommonJourneyScreen( state = uiState.commonJourneyState, + onMyAnswersClick = onMyAnswersClick, + onAnswerClick = onCommonAnswerClick, onDateChange = onDateChange, ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/QuestViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/QuestViewModel.kt index 67f22ef1..925cd6c9 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/QuestViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/QuestViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.quest import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.core.util.getFormattedDate @@ -172,7 +173,9 @@ class QuestViewModel } }.onFailure { t -> _sideEffect.emit( - QuestSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -230,7 +233,7 @@ class QuestViewModel _uiState.update { it.copy(myJourneyState = it.myJourneyState.copy(showQuitModal = false)) } } - fun onTipClick() { + fun onTipClicked() { val quest = uiState.value.myJourneyState.selectedQuest ?: return viewModelScope.launch { mixpanelUtil.trackEvent( @@ -256,7 +259,7 @@ class QuestViewModel } } - fun onQuestClick(questId: Long) { + fun onQuestClicked(questId: Long) { viewModelScope.launch { val quest = uiState.value.myJourneyState.questGroups @@ -283,6 +286,12 @@ class QuestViewModel } } + fun onMyAnswersClicked() { + viewModelScope.launch { + _sideEffect.emit(QuestSideEffect.NavigateToQuestMyAnswers) + } + } + private suspend fun handleCompletedQuestClick(quest: Quest) { mixpanelUtil.trackEvent( "quest_box_click", diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteScreen.kt index 0d6b51ae..29b5c83c 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteScreen.kt @@ -82,7 +82,7 @@ fun QuestBehaviorCompleteRoute( inAppReview(activity) } } - is QuestBehaviorCompleteSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestBehaviorCompleteSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteState.kt index cbef160e..8010145b 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.behavior import android.net.Uri +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType data class QuestBehaviorCompleteState( @@ -27,6 +28,6 @@ sealed interface QuestBehaviorCompleteSideEffect { data object ShowInAppReview : QuestBehaviorCompleteSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestBehaviorCompleteSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteViewModel.kt index 87be868a..26dbda2b 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorCompleteViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.core.util.getFormattedDate @@ -63,7 +64,7 @@ class QuestBehaviorCompleteViewModel }.onFailure { _sideEffect.emit( QuestBehaviorCompleteSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorState.kt index 3ee0773e..a550c4b1 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.behavior import android.net.Uri +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.domain.model.quest.QuestWritingState @@ -58,6 +59,6 @@ sealed interface QuestBehaviorSideEffect { data object NavigateUp : QuestBehaviorSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestBehaviorSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorViewModel.kt index 8010a183..5fa20dfb 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.core.util.MixpanelUtil @@ -87,7 +88,9 @@ class QuestBehaviorViewModel } }.onFailure { _sideEffect.emit( - QuestBehaviorSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestBehaviorSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -108,7 +111,9 @@ class QuestBehaviorViewModel } }.onFailure { _sideEffect.emit( - QuestBehaviorSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestBehaviorSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -181,7 +186,9 @@ class QuestBehaviorViewModel closeBottomSheet() }.onFailure { e -> _sideEffect.emit( - QuestBehaviorSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestBehaviorSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } @@ -338,7 +345,9 @@ class QuestBehaviorViewModel ) }.onFailure { _sideEffect.emit( - QuestBehaviorSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestBehaviorSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorWritingScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorWritingScreen.kt index f790ee3e..5a5c32ef 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorWritingScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/behavior/QuestBehaviorWritingScreen.kt @@ -94,7 +94,7 @@ fun QuestBehaviorWritingRoute( effect.questId, ) is QuestBehaviorSideEffect.NavigateUp -> navigateUp() - is QuestBehaviorSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestBehaviorSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/component/bottomsheet/MoreOptionsBottomSheet.kt b/app/src/main/java/com/byeboo/app/presentation/quest/component/bottomsheet/MoreOptionsBottomSheet.kt new file mode 100644 index 00000000..13196d26 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/component/bottomsheet/MoreOptionsBottomSheet.kt @@ -0,0 +1,125 @@ +package com.byeboo.app.presentation.quest.component.bottomsheet + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetState +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.byeboo.app.core.designsystem.component.button.ByeBooButton +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.noRippleClickable +import com.byeboo.app.core.util.screenHeightDp +import com.byeboo.app.core.util.screenWidthDp +import com.byeboo.app.presentation.quest.component.type.PostOption + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MoreOptionsBottomSheet( + topOption: T, + bottomOption: T, + onOptionClick: (T) -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + showBottomSheet: Boolean = false, + sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), +) { + if (showBottomSheet) { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + modifier = modifier, + sheetState = sheetState, + shape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp), + containerColor = ByeBooTheme.colors.gray900, + scrimColor = ByeBooTheme.colors.blackAlpha80, + dragHandle = null, + ) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = screenWidthDp(24.dp)), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + ByeBooDragHandle() + + Spacer(modifier = Modifier.height(screenHeightDp(16.dp))) + + Row( + modifier = + Modifier + .fillMaxWidth() + .noRippleClickable { onOptionClick(topOption) }, + horizontalArrangement = Arrangement.spacedBy(screenWidthDp(12.dp)), + ) { + Icon( + imageVector = ImageVector.vectorResource(topOption.optionIcon), + contentDescription = null, + tint = ByeBooTheme.colors.white, + ) + + Text( + text = topOption.optionTitle, + style = ByeBooTheme.typography.body3, + color = ByeBooTheme.colors.white, + ) + } + + HorizontalDivider( + modifier = + Modifier + .fillMaxWidth() + .padding(screenHeightDp(20.dp)), + thickness = 1.dp, + color = ByeBooTheme.colors.gray800, + ) + + Row( + modifier = + Modifier + .fillMaxWidth() + .noRippleClickable { onOptionClick(bottomOption) }, + horizontalArrangement = Arrangement.spacedBy(screenWidthDp(12.dp)), + ) { + Icon( + imageVector = ImageVector.vectorResource(bottomOption.optionIcon), + contentDescription = null, + tint = ByeBooTheme.colors.error300, + ) + + Text( + text = bottomOption.optionTitle, + style = ByeBooTheme.typography.body3, + color = ByeBooTheme.colors.error300, + ) + } + + Spacer(modifier = Modifier.height(screenHeightDp(36.dp))) + + ByeBooButton( + buttonText = "닫기", + buttonStyle = ByeBooTheme.typography.body3, + buttonTextColor = ByeBooTheme.colors.gray300, + buttonBackgroundColor = ByeBooTheme.colors.whiteAlpha5, + onClick = onDismissRequest, + modifier = Modifier.padding(bottom = screenHeightDp(10.dp)), + ) + } + } + } +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/component/card/CommonAnswerItem.kt b/app/src/main/java/com/byeboo/app/presentation/quest/component/card/CommonAnswerItem.kt index 51a51b50..4aabbc69 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/component/card/CommonAnswerItem.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/component/card/CommonAnswerItem.kt @@ -15,11 +15,13 @@ 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.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.noRippleClickable import com.byeboo.app.core.util.screenHeightDp import com.byeboo.app.core.util.screenWidthDp import com.byeboo.app.presentation.quest.model.CommonAnswerModel @@ -28,14 +30,18 @@ import com.byeboo.app.presentation.quest.model.CommonAnswerModel fun CommonAnswerItem( answer: CommonAnswerModel, modifier: Modifier = Modifier, + isExpanded: Boolean = false, + onClick: () -> Unit = {}, ) { Column( modifier = modifier .fillMaxWidth() + .clip(shape = RoundedCornerShape(12.dp)) .background( color = ByeBooTheme.colors.whiteAlpha5, - shape = RoundedCornerShape(12.dp), + ).noRippleClickable( + onClick = onClick, ).padding( horizontal = screenWidthDp(24.dp), vertical = screenHeightDp(16.dp), @@ -65,16 +71,18 @@ fun CommonAnswerItem( text = answer.content, style = ByeBooTheme.typography.body3, color = ByeBooTheme.colors.gray100, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + maxLines = if (isExpanded) Int.MAX_VALUE else 2, + overflow = if (isExpanded) TextOverflow.Visible else TextOverflow.Ellipsis, ) - Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + if (!isExpanded) { + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) - Text( - text = answer.displayTime, - style = ByeBooTheme.typography.cap2, - color = ByeBooTheme.colors.gray400, - ) + Text( + text = answer.displayTime, + style = ByeBooTheme.typography.cap2, + color = ByeBooTheme.colors.gray400, + ) + } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/component/card/MyAnswerItem.kt b/app/src/main/java/com/byeboo/app/presentation/quest/component/card/MyAnswerItem.kt new file mode 100644 index 00000000..deafa355 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/component/card/MyAnswerItem.kt @@ -0,0 +1,80 @@ +package com.byeboo.app.presentation.quest.component.card + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +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.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.noRippleClickable +import com.byeboo.app.core.util.screenHeightDp +import com.byeboo.app.core.util.screenWidthDp +import com.byeboo.app.presentation.quest.model.MyAnswerModel + +@Composable +fun MyAnswerItem( + answer: MyAnswerModel, + modifier: Modifier = Modifier, + onMyAnswerContentClick: (Long) -> Unit = {}, +) { + Column( + modifier = + modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(12.dp)) + .background( + color = ByeBooTheme.colors.whiteAlpha5, + ).noRippleClickable { + onMyAnswerContentClick(answer.answerId) + }.padding( + horizontal = screenWidthDp(24.dp), + vertical = screenHeightDp(16.dp), + ), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(screenWidthDp(4.dp)), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Q.", + color = ByeBooTheme.colors.primary200, + style = ByeBooTheme.typography.sub2, + ) + + Text( + text = answer.question, + color = ByeBooTheme.colors.gray100, + style = ByeBooTheme.typography.sub3, + ) + } + + Spacer(modifier = Modifier.height(screenHeightDp(12.dp))) + + Text( + text = answer.content, + color = ByeBooTheme.colors.gray100, + style = ByeBooTheme.typography.body3, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + + Text( + text = answer.writtenAt, + color = ByeBooTheme.colors.gray400, + style = ByeBooTheme.typography.cap2, + ) + } +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/component/type/OptionType.kt b/app/src/main/java/com/byeboo/app/presentation/quest/component/type/OptionType.kt new file mode 100644 index 00000000..eec6f3f7 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/component/type/OptionType.kt @@ -0,0 +1,25 @@ +package com.byeboo.app.presentation.quest.component.type + +import androidx.annotation.DrawableRes +import com.byeboo.app.R + +sealed interface PostOption { + val optionIcon: Int + val optionTitle: String +} + +enum class MyPostOption( + @DrawableRes override val optionIcon: Int, + override val optionTitle: String, +) : PostOption { + EDIT(R.drawable.ic_edit, "수정하기"), + DELETE(R.drawable.ic_trash, "삭제하기"), +} + +enum class OtherPostOption( + @DrawableRes override val optionIcon: Int, + override val optionTitle: String, +) : PostOption { + BLOCK(R.drawable.ic_block, "사용자 차단하기"), + REPORT(R.drawable.ic_report, "게시글 신고하기"), +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestModel.kt index 46b6ea09..cbbedb64 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestModel.kt @@ -27,3 +27,11 @@ data class CommonAnswerModel( val displayTime: String, val content: String, ) + +@Immutable +data class MyAnswerModel( + val answerId: Long, + val question: String, + val writtenAt: String, + val content: String, +) diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestState.kt index b06bd5d9..49281162 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/model/QuestState.kt @@ -1,5 +1,6 @@ package com.byeboo.app.presentation.quest.model +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType sealed class QuestState { @@ -32,8 +33,10 @@ sealed interface QuestSideEffect { val questId: Long, ) : QuestSideEffect + data object NavigateToQuestMyAnswers : QuestSideEffect + data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestNavigation.kt b/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestNavigation.kt index bebdf92a..a060ba4e 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestNavigation.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestNavigation.kt @@ -10,7 +10,10 @@ import com.byeboo.app.core.util.routeNavigation import com.byeboo.app.presentation.quest.QuestRoute import com.byeboo.app.presentation.quest.behavior.navigation.questBehaviorGraph import com.byeboo.app.presentation.quest.record.navigation.questRecordGraph -import com.byeboo.app.presentation.quest.review.QuestReviewRoute +import com.byeboo.app.presentation.quest.review.common.all.CommonAnswerRoute +import com.byeboo.app.presentation.quest.review.common.personal.MyAnswerDetailRoute +import com.byeboo.app.presentation.quest.review.common.personal.MyAnswerRoute +import com.byeboo.app.presentation.quest.review.my.QuestReviewRoute import com.byeboo.app.presentation.quest.start.QuestStartRoute import com.byeboo.app.presentation.quest.tip.QuestTipRoute @@ -40,6 +43,24 @@ fun NavController.navigateToQuestReview( navigate(QuestReview(questId), navOptions) } +fun NavController.navigateToQuestCommonAnswer( + answerId: Long, + navOptions: NavOptions? = null, +) { + navigate(QuestCommonAnswer(answerId), navOptions) +} + +fun NavController.navigateToQuestMyAnswers(navOptions: NavOptions? = null) { + navigate(QuestMyAnswers, navOptions) +} + +fun NavController.navigateToQuestMyAnswerDetail( + answerId: Long, + navOptions: NavOptions? = null, +) { + navigate(QuestMyAnswersDetail(answerId), navOptions) +} + fun NavGraphBuilder.questGraph( navigateUp: () -> Unit, navigateToQuest: () -> Unit, @@ -53,6 +74,9 @@ fun NavGraphBuilder.questGraph( navigateToQuestBehaviorEdit: (Long, Boolean, String) -> Unit, navigateToQuestTip: (Long, QuestType) -> Unit, navigateToQuestBehaviorComplete: (Long) -> Unit, + navigateToQuestCommonAnswer: (Long) -> Unit, + navigateToQuestMyAnswers: () -> Unit, + navigateToQuestMyAnswerDetail: (Long) -> Unit, paddingValues: PaddingValues, ) { routeNavigation { @@ -70,6 +94,8 @@ fun NavGraphBuilder.questGraph( navigateToQuestRecording = navigateToQuestRecording, navigateToQuestBehavior = navigateToQuestBehavior, navigateToQuestReview = navigateToQuestReview, + navigateToCommonAnswer = navigateToQuestCommonAnswer, + navigateToQuestMyAnswers = navigateToQuestMyAnswers, paddingValues = paddingValues, ) } @@ -90,6 +116,26 @@ fun NavGraphBuilder.questGraph( ) } + composable { + CommonAnswerRoute( + navigateToQuest = navigateToQuest, + paddingValues = paddingValues, + ) + } + + composable { + MyAnswerRoute( + navigateToQuestMyAnswerDetail = navigateToQuestMyAnswerDetail, + paddingValues = paddingValues, + ) + } + + composable { + MyAnswerDetailRoute( + paddingValues = paddingValues, + ) + } + questRecordGraph( navigateToQuest = navigateToQuest, navigateToQuestTip = navigateToQuestTip, diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestRoute.kt b/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestRoute.kt index 5071dc7e..9ec38868 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestRoute.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/navigation/QuestRoute.kt @@ -25,3 +25,16 @@ data class QuestTip( data class QuestReview( val questId: Long, ) : Route + +@Serializable +data class QuestCommonAnswer( + val answerId: Long, +) : Route + +@Serializable +data object QuestMyAnswers : Route + +@Serializable +data class QuestMyAnswersDetail( + val answerId: Long, +) : Route diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteScreen.kt index aed469e0..a653e1ac 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteScreen.kt @@ -67,7 +67,7 @@ fun QuestRecordingCompleteRoute( inAppReview(activity) } } - is QuestRecordingCompleteSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestRecordingCompleteSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteState.kt index e7e9f5c5..bb6944ec 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.record import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType @Immutable @@ -26,6 +27,6 @@ sealed interface QuestRecordingCompleteSideEffect { data object ShowInAppReview : QuestRecordingCompleteSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestRecordingCompleteSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteViewModel.kt index fca65dc3..8ad42dc1 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingCompleteViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.core.util.getFormattedDate @@ -63,7 +64,7 @@ class QuestRecordingCompleteViewModel }.onFailure { _sideEffect.emit( QuestRecordingCompleteSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingScreen.kt index 4efe15d2..b76a87ff 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingScreen.kt @@ -88,7 +88,7 @@ fun QuestRecordingRoute( effect.questId, ) is QuestRecordingSideEffect.NavigateUp -> navigateUp() - is QuestRecordingSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestRecordingSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingState.kt index cb4ff1bd..ebaf8796 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.record import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.domain.model.quest.QuestWritingState @@ -43,6 +44,6 @@ sealed interface QuestRecordingSideEffect { data object NavigateUp : QuestRecordingSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestRecordingSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingViewModel.kt index b78f7455..07d9cc98 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/record/QuestRecordingViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.core.util.MixpanelUtil @@ -79,7 +80,9 @@ class QuestRecordingViewModel } }.onFailure { _sideEffect.emit( - QuestRecordingSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestRecordingSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -99,7 +102,9 @@ class QuestRecordingViewModel } }.onFailure { _sideEffect.emit( - QuestRecordingSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestRecordingSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -140,7 +145,9 @@ class QuestRecordingViewModel ) }.onFailure { _sideEffect.emit( - QuestRecordingSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestRecordingSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -187,7 +194,9 @@ class QuestRecordingViewModel } }.onFailure { _sideEffect.emit( - QuestRecordingSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestRecordingSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerScreen.kt new file mode 100644 index 00000000..ea48e049 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerScreen.kt @@ -0,0 +1,109 @@ +package com.byeboo.app.presentation.quest.review.common.all + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.byeboo.app.core.designsystem.event.LocalSnackBarTrigger +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.screenHeightDp +import com.byeboo.app.presentation.quest.component.bottomsheet.MoreOptionsBottomSheet +import com.byeboo.app.presentation.quest.component.card.CommonAnswerItem +import com.byeboo.app.presentation.quest.component.text.QuestTitle +import com.byeboo.app.presentation.quest.component.type.OtherPostOption +import com.byeboo.app.presentation.quest.review.common.component.AnswerDetailTopBar +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun CommonAnswerRoute( + navigateToQuest: () -> Unit, + paddingValues: PaddingValues, + modifier: Modifier = Modifier, + viewModel: CommonAnswerViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val showSnackBar = LocalSnackBarTrigger.current + + LaunchedEffect(Unit) { + viewModel.sideEffect.collectLatest { effect -> + when (effect) { + is CommonAnswerSideEffect.NavigateToQuest -> navigateToQuest() + is CommonAnswerSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) + } + } + } + + CommonAnswerScreen( + uiState = uiState, + paddingValues = paddingValues, + onClickMoreOptions = viewModel::onClickMoreOptions, + onDismissBottomSheet = viewModel::onDismissBottomSheet, + onOptionClick = { option -> viewModel.onOptionClicked(option) }, + modifier = modifier, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun CommonAnswerScreen( + uiState: CommonAnswerState, + paddingValues: PaddingValues, + onClickMoreOptions: () -> Unit, + onDismissBottomSheet: () -> Unit, + onOptionClick: (OtherPostOption) -> Unit, + modifier: Modifier = Modifier, +) { + val scrollState = rememberScrollState() + + Column( + modifier = + modifier + .fillMaxSize() + .background(ByeBooTheme.colors.background) + .verticalScroll(scrollState) + .padding(horizontal = 24.dp) + .padding( + top = paddingValues.calculateTopPadding() + screenHeightDp(43.dp), + bottom = paddingValues.calculateBottomPadding(), + ), + ) { + AnswerDetailTopBar( + onClickMoreOptions = onClickMoreOptions, + ) + + QuestTitle( + stepNumber = 2, + questNumber = 10, + createdAt = "2025-06-01", + questQuestion = "그 사람이 싫어하기에 내가 포기해야만 했던 일은 무엇일까?", + ) + + Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) + + CommonAnswerItem( + answer = uiState.answer, + isExpanded = true, + ) + } + + MoreOptionsBottomSheet( + topOption = OtherPostOption.BLOCK, + bottomOption = OtherPostOption.REPORT, + onOptionClick = onOptionClick, + showBottomSheet = uiState.showBottomSheet, + onDismissRequest = onDismissBottomSheet, + ) +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerState.kt new file mode 100644 index 00000000..87d485b8 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerState.kt @@ -0,0 +1,24 @@ +package com.byeboo.app.presentation.quest.review.common.all + +import com.byeboo.app.core.designsystem.type.CustomSnackBarType +import com.byeboo.app.presentation.quest.model.CommonAnswerModel + +data class CommonAnswerState( + val answer: CommonAnswerModel = + CommonAnswerModel( + answerId = 0L, + writer = "", + profileIconRes = 0, + displayTime = "", + content = "", + ), + val showBottomSheet: Boolean = false, +) + +sealed interface CommonAnswerSideEffect { + data class ShowSnackBar( + val snackBarType: CustomSnackBarType, + ) : CommonAnswerSideEffect + + data object NavigateToQuest : CommonAnswerSideEffect +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerViewModel.kt new file mode 100644 index 00000000..e392073c --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/all/CommonAnswerViewModel.kt @@ -0,0 +1,80 @@ +package com.byeboo.app.presentation.quest.review.common.all + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.byeboo.app.R +import com.byeboo.app.core.designsystem.type.CustomSnackBarType +import com.byeboo.app.presentation.quest.component.type.OtherPostOption +import com.byeboo.app.presentation.quest.model.CommonAnswerModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CommonAnswerViewModel + @Inject + constructor() : ViewModel() { + private val _uiState = MutableStateFlow(CommonAnswerState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _sideEffect = MutableSharedFlow() + val sideEffect: SharedFlow = _sideEffect.asSharedFlow() + + init { + loadCommonAnswer(1L) + } + + // Todo : 더미데이터 변경 + private fun loadCommonAnswer(answerId: Long) { + val dummyAnswer = + CommonAnswerModel( + answerId = answerId, + writer = "장원영", + profileIconRes = R.drawable.ic_profile_sadness, + displayTime = "2026. 01. 30.", + content = "헤어진 첫날 밤이었어요. 혼자 집에 있는데 갑자기 모든 게 현실로 다가왔고, 이제 정말 끝났다는 걸 깨달았을 때... 그때가 제일 힘들었던 것 같아요.", + ) + + _uiState.update { it.copy(answer = dummyAnswer) } + } + + fun onClickMoreOptions() { + _uiState.update { it.copy(showBottomSheet = true) } + } + + fun onDismissBottomSheet() { + _uiState.update { it.copy(showBottomSheet = false) } + } + + fun onOptionClicked(option: OtherPostOption) { + onDismissBottomSheet() + + viewModelScope.launch { + when (option) { + OtherPostOption.BLOCK -> { + _sideEffect.emit(CommonAnswerSideEffect.NavigateToQuest) + _sideEffect.emit( + CommonAnswerSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.SUCCESS("차단이 완료되었어요. 이에 해당 사용자의 글이 노출되지 않아요."), + ), + ) + } + + OtherPostOption.REPORT -> { + _sideEffect.emit( + CommonAnswerSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.SUCCESS("신고가 접수되었어요. 처리 결과는 알림을 통해 알려드려요."), + ), + ) + } + } + } + } + } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/component/AnswerDetailTopBar.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/component/AnswerDetailTopBar.kt new file mode 100644 index 00000000..c7b9f42c --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/component/AnswerDetailTopBar.kt @@ -0,0 +1,54 @@ +package com.byeboo.app.presentation.quest.review.common.component + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.byeboo.app.R +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.noRippleClickable +import com.byeboo.app.core.util.screenHeightDp + +@Composable +fun AnswerDetailTopBar( + onClickMoreOptions: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = + modifier + .fillMaxWidth() + .padding(bottom = screenHeightDp(16.dp)), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_left), + contentDescription = null, + tint = ByeBooTheme.colors.gray50, + modifier = + Modifier.noRippleClickable( + // Todo: 뒤로가기 + ), + ) + + Spacer(modifier = Modifier.weight(1f)) + + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_overflow_menu), + contentDescription = null, + tint = ByeBooTheme.colors.white, + modifier = + Modifier + .noRippleClickable( + onClick = onClickMoreOptions, + ), + ) + } +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerDetailScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerDetailScreen.kt new file mode 100644 index 00000000..62d236e9 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerDetailScreen.kt @@ -0,0 +1,124 @@ +package com.byeboo.app.presentation.quest.review.common.personal + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.screenHeightDp +import com.byeboo.app.core.util.screenWidthDp +import com.byeboo.app.presentation.quest.component.bottomsheet.MoreOptionsBottomSheet +import com.byeboo.app.presentation.quest.component.text.QuestTitle +import com.byeboo.app.presentation.quest.component.type.MyPostOption +import com.byeboo.app.presentation.quest.review.common.component.AnswerDetailTopBar + +@Composable +fun MyAnswerDetailRoute( + paddingValues: PaddingValues, + modifier: Modifier = Modifier, + viewModel: MyAnswerViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + MyAnswerDetailScreen( + uiState = uiState, + paddingValues = paddingValues, + onClickMoreOptions = viewModel::onClickMoreOptions, + onDismissBottomSheet = viewModel::onDismissBottomSheet, + onOptionClick = { option -> viewModel.onOptionClicked(option) }, + modifier = modifier, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun MyAnswerDetailScreen( + uiState: MyAnswerState, + paddingValues: PaddingValues, + onClickMoreOptions: () -> Unit, + onDismissBottomSheet: () -> Unit, + onOptionClick: (MyPostOption) -> Unit, + modifier: Modifier = Modifier, +) { + val scrollState = rememberScrollState() + val answerState = uiState.answers.first() + + Column( + modifier = + modifier + .fillMaxSize() + .background(ByeBooTheme.colors.background) + .verticalScroll(scrollState) + .padding(horizontal = 24.dp) + .padding( + top = paddingValues.calculateTopPadding() + screenHeightDp(43.dp), + bottom = paddingValues.calculateBottomPadding(), + ), + ) { + AnswerDetailTopBar( + onClickMoreOptions = onClickMoreOptions, + ) + + // Todo: QuestWritingTitle 컴포넌트로 교체 + QuestTitle( + stepNumber = 2, + questNumber = 10, + createdAt = answerState.writtenAt, + questQuestion = "그 사람이 싫어하기에 내가 포기해야만 했던 일은 무엇일까?", + ) + + Spacer(modifier = Modifier.height(screenHeightDp(10.dp))) + + MyAnswerContent( + content = answerState.content, + ) + } + + MoreOptionsBottomSheet( + topOption = MyPostOption.EDIT, + bottomOption = MyPostOption.DELETE, + onOptionClick = onOptionClick, + showBottomSheet = uiState.showBottomSheet, + onDismissRequest = onDismissBottomSheet, + ) +} + +@Composable +private fun MyAnswerContent( + content: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier + .fillMaxWidth() + .background( + color = ByeBooTheme.colors.whiteAlpha5, + shape = RoundedCornerShape(12.dp), + ).padding( + horizontal = screenWidthDp(24.dp), + vertical = screenHeightDp(18.dp), + ), + ) { + Text( + text = content, + color = ByeBooTheme.colors.gray100, + style = ByeBooTheme.typography.body3, + ) + } +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerScreen.kt new file mode 100644 index 00000000..308979e4 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerScreen.kt @@ -0,0 +1,139 @@ +package com.byeboo.app.presentation.quest.review.common.personal + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.byeboo.app.R +import com.byeboo.app.core.designsystem.ui.theme.ByeBooTheme +import com.byeboo.app.core.util.noRippleClickable +import com.byeboo.app.core.util.screenHeightDp +import com.byeboo.app.presentation.quest.component.card.MyAnswerItem +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun MyAnswerRoute( + navigateToQuestMyAnswerDetail: (Long) -> Unit, + paddingValues: PaddingValues, + viewModel: MyAnswerViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.sideEffect.collectLatest { effect -> + when (effect) { + is MyAnswerSideEffect.NavigateToQuestMyAnswerDetail -> + navigateToQuestMyAnswerDetail(effect.answerId) + } + } + } + + MyAnswerScreen( + uiState = uiState, + paddingValues = paddingValues, + onMyAnswerContentClick = viewModel::onMyAnswerContentClicked, + ) +} + +@Composable +fun MyAnswerScreen( + uiState: MyAnswerState, + onMyAnswerContentClick: (Long) -> Unit, + paddingValues: PaddingValues, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier + .fillMaxSize() + .background(ByeBooTheme.colors.background) + .padding(horizontal = 24.dp) + .padding( + top = paddingValues.calculateTopPadding() + screenHeightDp(43.dp), + bottom = paddingValues.calculateBottomPadding(), + ), + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(screenHeightDp(20.dp)), + contentPadding = + PaddingValues(bottom = screenHeightDp(25.dp)), + modifier = Modifier.fillMaxWidth(), + ) { + item { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_left), + contentDescription = null, + tint = ByeBooTheme.colors.gray50, + modifier = + Modifier.noRippleClickable( + // Todo: 뒤로가기 + ), + ) + + Spacer(modifier = Modifier.height(screenHeightDp(16.dp))) + + Text( + text = + buildAnnotatedString { + append("하츠핑하츠님의") + append("\n") + append("공통퀘스트 답변이에요") + }, + color = ByeBooTheme.colors.gray50, + style = ByeBooTheme.typography.head2, + modifier = Modifier.padding(vertical = screenHeightDp(10.dp)), + ) + } + + if (uiState.answers.isEmpty()) { + item { + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(top = screenHeightDp(184.dp)), + ) { + Text( + text = "아직 작성한 답변이 없어요!", + style = ByeBooTheme.typography.body6, + color = ByeBooTheme.colors.gray400, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } + } + } else { + items( + items = uiState.answers, + key = { it.answerId }, + ) { answer -> + MyAnswerItem( + answer = answer, + onMyAnswerContentClick = { onMyAnswerContentClick(answer.answerId) }, + ) + } + } + } + } +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerState.kt new file mode 100644 index 00000000..9af830da --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerState.kt @@ -0,0 +1,18 @@ +package com.byeboo.app.presentation.quest.review.common.personal + +import androidx.compose.runtime.Immutable +import com.byeboo.app.presentation.quest.model.MyAnswerModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Immutable +data class MyAnswerState( + val answers: ImmutableList = persistentListOf(), + val showBottomSheet: Boolean = false, +) + +sealed interface MyAnswerSideEffect { + data class NavigateToQuestMyAnswerDetail( + val answerId: Long, + ) : MyAnswerSideEffect +} diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerViewModel.kt new file mode 100644 index 00000000..ff007c21 --- /dev/null +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/common/personal/MyAnswerViewModel.kt @@ -0,0 +1,91 @@ +package com.byeboo.app.presentation.quest.review.common.personal + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.byeboo.app.presentation.quest.component.type.MyPostOption +import com.byeboo.app.presentation.quest.model.MyAnswerModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyAnswerViewModel + @Inject + constructor() : ViewModel() { + private val _uiState = MutableStateFlow(MyAnswerState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _sideEffect = MutableSharedFlow() + val sideEffect: SharedFlow = _sideEffect.asSharedFlow() + + init { + _uiState.update { it.copy(answers = getDummyAnswers()) } + } + + private fun getDummyAnswers(): ImmutableList = + persistentListOf( + MyAnswerModel( + answerId = 1, + question = "이별 후 제일 힘들었던 순간은?", + writtenAt = "2026-01-30", + content = + "헤어진 지 벌써 일주일이 지났습니다. 처음에는 실감이 안 나서 눈물조차 나오지 않았어요. 그저 멍하니 천장만 바라보며 시간을 보냈습니다. 그런데 오늘 아침, 습관적으로 휴대폰을 확인하다가 더 이상 '굿모닝' 인사를 보낼 사람이 없다는 사실을 깨닫고 그제야 무너져 내렸습니다. 밥알이 모래알 같아서 잘 넘어가지도 않네요. 친구들은 시간이 약이라고, 더 좋은 사람 만날 거라고 위로하지만 지금 당장은 그 어떤 말도 귀에 들어오지 않습니다.\n" + + "\n" + + "우리가 함께 걷던 거리, 자주 가던 카페, 이어폰을 나눠 끼고 듣던 노래들... 세상은 여전히 그대로 굴러가는데 우리 사이만 끊어졌다는 게 도무지 믿기지가 않아요. 당신이 사무치게 밉다가도, 당장이라도 달려가 안기고 싶은 내 마음이 너무 싫습니다. 수백 번 차단했다 풀었다를 반복하며 당신의 프로필을 훔쳐보는 내가 너무 초라해 보여요.\n" + + "\n" + + "이제는 인정해야겠죠. 우리는 끝났고, 나는 혼자가 되었다는 사실을요. 오늘은 억지로라도 밖으로 나가 햇볕을 쬐었습니다.", + ), + MyAnswerModel( + answerId = 2, + question = "이별 후 제일 힘들었던 순간은2?", + writtenAt = "2026-01-30", + content = "헤어진 첫날 밤이었어요. 혼자 집에 있는데 갑자기 모든 게 현실로 다가왔고, 이제 정말 끝이라는 생각에 눈물이 멈추지 않았습니다.", + ), + MyAnswerModel( + answerId = 3, + question = "이별 후 제일 힘들었던 순간은3?", + writtenAt = "2026-01-30", + content = "헤어진 첫날 밤이었어요. 혼자 집에 있는데 갑자기 모든 게 현실로 다가왔고, 이제 정말 끝이라는 생각에 눈물이 멈추지 않았습니다.", + ), + MyAnswerModel( + answerId = 4, + question = "이별 후 제일 힘들었던 순간은4?", + writtenAt = "2026-01-30", + content = "헤어진 첫날 밤이었어요. 혼자 집에 있는데 갑자기 모든 게 현실로 다가왔고, 이제 정말 끝이라는 생각에 눈물이 멈추지 않았습니다.", + ), + ) + + fun onMyAnswerContentClicked(answerId: Long) { + viewModelScope.launch { + _sideEffect.emit(MyAnswerSideEffect.NavigateToQuestMyAnswerDetail(answerId)) + } + } + + fun onClickMoreOptions() { + _uiState.update { it.copy(showBottomSheet = true) } + } + + fun onDismissBottomSheet() { + _uiState.update { it.copy(showBottomSheet = false) } + } + + fun onOptionClicked(option: MyPostOption) { + onDismissBottomSheet() + + viewModelScope.launch { + when (option) { + MyPostOption.EDIT -> { /* TODO 수정 화면 이동 */ } + MyPostOption.DELETE -> { /* TODO 삭제 모달 띄우기 */ } + } + } + } + } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewScreen.kt similarity index 99% rename from app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewScreen.kt rename to app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewScreen.kt index ecc6bb4e..b6e337ba 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewScreen.kt @@ -1,4 +1,4 @@ -package com.byeboo.app.presentation.quest.review +package com.byeboo.app.presentation.quest.review.my import androidx.activity.compose.BackHandler import androidx.compose.foundation.background @@ -72,7 +72,7 @@ fun QuestReviewRoute( true, effect.imageKey, ) - is QuestReviewSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestReviewSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewState.kt similarity index 87% rename from app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewState.kt rename to app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewState.kt index 737cdb0f..62ec1193 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewState.kt @@ -1,5 +1,6 @@ -package com.byeboo.app.presentation.quest.review +package com.byeboo.app.presentation.quest.review.my +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import java.time.LocalDate @@ -34,6 +35,6 @@ sealed interface QuestReviewSideEffect { ) : QuestReviewSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestReviewSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewViewModel.kt similarity index 93% rename from app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewViewModel.kt rename to app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewViewModel.kt index 8eb6c3b4..ba65eb58 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/review/QuestReviewViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/review/my/QuestReviewViewModel.kt @@ -1,9 +1,10 @@ -package com.byeboo.app.presentation.quest.review +package com.byeboo.app.presentation.quest.review.my import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.designsystem.type.EmotionChipType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.domain.repository.quest.QuestRecordedDetailRepository @@ -106,7 +107,9 @@ class QuestReviewViewModel } }.onFailure { _sideEffect.emit( - QuestReviewSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestReviewSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/screen/CommonJourneyScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/screen/CommonJourneyScreen.kt index 1d85e06a..ca300a7f 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/screen/CommonJourneyScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/screen/CommonJourneyScreen.kt @@ -39,7 +39,9 @@ import java.time.LocalDate @Composable fun CommonJourneyScreen( state: CommonJourneyState, + onMyAnswersClick: () -> Unit, onDateChange: (LocalDate) -> Unit, + onAnswerClick: (Long) -> Unit, modifier: Modifier = Modifier, ) { val listState = rememberLazyListState() @@ -73,9 +75,7 @@ fun CommonJourneyScreen( middleTagType = MiddleTagType.MY_ANSWERS, textStyle = ByeBooTheme.typography.cap1, modifier = - Modifier.noRippleClickable { - // TODO: 나의 답변 모아보기 화면 이동 - }, + Modifier.noRippleClickable(onClick = onMyAnswersClick), ) Spacer(modifier = Modifier.height(screenHeightDp(16.dp))) HorizontalDivider( @@ -185,6 +185,7 @@ fun CommonJourneyScreen( ) { answer -> CommonAnswerItem( answer = answer, + onClick = { /* Todo: 네비 연결 */ }, modifier = Modifier .padding(horizontal = screenWidthDp(24.dp)) diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartScreen.kt index 5a2403d5..e90929ab 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartScreen.kt @@ -48,7 +48,7 @@ fun QuestStartRoute( when (effect) { is QuestStartSideEffect.NavigateToQuest -> navigateToQuest() is QuestStartSideEffect.NavigateToHome -> navigateToHome() - is QuestStartSideEffect.ShowSnackBar -> showSnackBar(effect.message) + is QuestStartSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartState.kt index f987bdff..80c44e7d 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.start import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType @Immutable @@ -16,6 +17,6 @@ sealed interface QuestStartSideEffect { data object NavigateToHome : QuestStartSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestStartSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartViewModel.kt index db50f020..5892e5b6 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/start/QuestStartViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.core.util.getFormattedDate @@ -88,7 +89,9 @@ class QuestStartViewModel _sideEffect.emit(QuestStartSideEffect.NavigateToQuest) } else { _sideEffect.emit( - QuestStartSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestStartSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } @@ -125,7 +128,9 @@ class QuestStartViewModel _sideEffect.emit(QuestStartSideEffect.NavigateToQuest) }.onFailure { e -> _sideEffect.emit( - QuestStartSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestStartSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipScreen.kt b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipScreen.kt index c330b834..592ca2f8 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipScreen.kt @@ -51,10 +51,10 @@ fun QuestTipRoute( val showSnackBar = LocalSnackBarTrigger.current LaunchedEffect(Unit) { - viewModel.sideEffect.collect { sideEffect -> - when (sideEffect) { + viewModel.sideEffect.collect { effect -> + when (effect) { is QuestTipSideEffect.NavigateToQuest -> navigateToQuest() - is QuestTipSideEffect.ShowSnackBar -> showSnackBar(sideEffect.message) + is QuestTipSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipState.kt b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipState.kt index 674c30f8..18297d11 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipState.kt @@ -1,6 +1,7 @@ package com.byeboo.app.presentation.quest.tip import androidx.compose.runtime.Immutable +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.model.quest.QuestType import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -26,6 +27,6 @@ sealed interface QuestTipSideEffect { data object NavigateToQuest : QuestTipSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : QuestTipSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipViewModel.kt index c69559de..29d9d0ef 100644 --- a/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/quest/tip/QuestTipViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.state.UiState import com.byeboo.app.domain.repository.quest.QuestTipRepository import com.byeboo.app.presentation.quest.model.Quest @@ -89,7 +90,9 @@ class QuestTipViewModel } _sideEffect.emit( - QuestTipSideEffect.ShowSnackBar("서버에 연결할 수 없습니다. 잠시 후 시도해 주세요."), + QuestTipSideEffect.ShowSnackBar( + snackBarType = CustomSnackBarType.ALERT, + ), ) } } diff --git a/app/src/main/java/com/byeboo/app/presentation/splash/SplashScreen.kt b/app/src/main/java/com/byeboo/app/presentation/splash/SplashScreen.kt index b39c6c85..e3ea70e8 100644 --- a/app/src/main/java/com/byeboo/app/presentation/splash/SplashScreen.kt +++ b/app/src/main/java/com/byeboo/app/presentation/splash/SplashScreen.kt @@ -71,8 +71,8 @@ fun SplashRoute( ) LaunchedEffect(Unit) { - viewModel.sideEffect.collect { sideEffect -> - when (sideEffect) { + viewModel.sideEffect.collect { effect -> + when (effect) { is SplashStateSideEffect.ShowLoginButton -> { showLoginButton = true } @@ -102,7 +102,7 @@ fun SplashRoute( } } - is SplashStateSideEffect.ShowSnackBar -> showSnackBar(sideEffect.message) + is SplashStateSideEffect.ShowSnackBar -> showSnackBar(effect.snackBarType) } } } diff --git a/app/src/main/java/com/byeboo/app/presentation/splash/SplashState.kt b/app/src/main/java/com/byeboo/app/presentation/splash/SplashState.kt index a5e2c653..57b49d8d 100644 --- a/app/src/main/java/com/byeboo/app/presentation/splash/SplashState.kt +++ b/app/src/main/java/com/byeboo/app/presentation/splash/SplashState.kt @@ -1,5 +1,7 @@ package com.byeboo.app.presentation.splash +import com.byeboo.app.core.designsystem.type.CustomSnackBarType + sealed interface SplashStateSideEffect { data object ShowLoginButton : SplashStateSideEffect @@ -16,6 +18,6 @@ sealed interface SplashStateSideEffect { data object RequestNotificationPermission : SplashStateSideEffect data class ShowSnackBar( - val message: String, + val snackBarType: CustomSnackBarType, ) : SplashStateSideEffect } diff --git a/app/src/main/java/com/byeboo/app/presentation/splash/SplashViewModel.kt b/app/src/main/java/com/byeboo/app/presentation/splash/SplashViewModel.kt index 14d51c8e..1cdde432 100644 --- a/app/src/main/java/com/byeboo/app/presentation/splash/SplashViewModel.kt +++ b/app/src/main/java/com/byeboo/app/presentation/splash/SplashViewModel.kt @@ -2,6 +2,7 @@ package com.byeboo.app.presentation.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.byeboo.app.core.designsystem.type.CustomSnackBarType import com.byeboo.app.core.util.LoginType import com.byeboo.app.core.util.MixpanelUtil import com.byeboo.app.domain.model.notification.FcmTokenModel @@ -129,7 +130,7 @@ class SplashViewModel mixpanelUtil.trackLogin(LoginType.KAKAO, false) _sideEffect.emit( SplashStateSideEffect.ShowSnackBar( - "서버에 연결할 수 없습니다. 잠시 후 시도해 주세요.", + snackBarType = CustomSnackBarType.ALERT, ), ) } diff --git a/app/src/main/res/drawable/ic_block.xml b/app/src/main/res/drawable/ic_block.xml new file mode 100644 index 00000000..53bb5b44 --- /dev/null +++ b/app/src/main/res/drawable/ic_block.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_overflow_menu.xml b/app/src/main/res/drawable/ic_overflow_menu.xml new file mode 100644 index 00000000..1de584d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_overflow_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_report.xml b/app/src/main/res/drawable/ic_report.xml new file mode 100644 index 00000000..02aedada --- /dev/null +++ b/app/src/main/res/drawable/ic_report.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_success.xml b/app/src/main/res/drawable/ic_success.xml new file mode 100644 index 00000000..d9cf8383 --- /dev/null +++ b/app/src/main/res/drawable/ic_success.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_trash.xml b/app/src/main/res/drawable/ic_trash.xml new file mode 100644 index 00000000..09bc9172 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash.xml @@ -0,0 +1,13 @@ + + + +