From aea1f699a1581ff42d3da201affd907fde3849c4 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 30 Jul 2025 19:33:18 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=EA=B2=BD=EA=B3=A0=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=97=90=EC=85=8B=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 모달용 에셋 -> ic_modal_warning으로 수정 --- .../main/res/drawable/ic_modal_warning.xml | 18 +++++++++++++++ .../src/main/res/drawable/ic_warning.xml | 23 +++++++------------ .../setting/component/block/ConfirmDialog.kt | 2 +- 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/ic_modal_warning.xml diff --git a/core/designsystem/src/main/res/drawable/ic_modal_warning.xml b/core/designsystem/src/main/res/drawable/ic_modal_warning.xml new file mode 100644 index 00000000..38d03a9a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_modal_warning.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_warning.xml b/core/designsystem/src/main/res/drawable/ic_warning.xml index fb6d8061..2cbbc64b 100644 --- a/core/designsystem/src/main/res/drawable/ic_warning.xml +++ b/core/designsystem/src/main/res/drawable/ic_warning.xml @@ -1,19 +1,12 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:fillColor="#FF6868" + android:pathData="M12,15.5C11.575,15.5 11.219,15.383 10.931,15.149C10.644,14.914 10.5,14.624 10.5,14.278V5.722C10.5,5.376 10.644,5.086 10.931,4.851C11.219,4.617 11.575,4.5 12,4.5C12.425,4.5 12.781,4.617 13.069,4.851C13.356,5.086 13.5,5.376 13.5,5.722V14.278C13.5,14.624 13.356,14.914 13.069,15.149C12.781,15.383 12.425,15.5 12,15.5Z" /> - - + android:fillColor="#FF6868" + android:pathData="M12,18m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0" /> diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/ConfirmDialog.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/ConfirmDialog.kt index 83889373..6de79180 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/ConfirmDialog.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/setting/component/block/ConfirmDialog.kt @@ -53,7 +53,7 @@ fun ConfirmDialog( horizontalAlignment = Alignment.CenterHorizontally, ) { BitnagilIcon( - id = R.drawable.ic_warning, + id = R.drawable.ic_modal_warning, tint = null, modifier = Modifier.size(55.dp), ) From aeecfb9e20e2f0fecae47e64637630ff84eff6b6 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 30 Jul 2025 19:35:52 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20BitnagilToastMessage=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/atom/BitnagilToastMessage.kt | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilToastMessage.kt diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilToastMessage.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilToastMessage.kt new file mode 100644 index 00000000..490f9092 --- /dev/null +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilToastMessage.kt @@ -0,0 +1,154 @@ +package com.threegap.bitnagil.designsystem.component.atom + +import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +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.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R +import kotlinx.coroutines.delay + +@Composable +fun BitnagilToastMessage( + text: String, + modifier: Modifier = Modifier, + @DrawableRes id: Int? = null, +) { + Box( + modifier = modifier + .background( + color = BitnagilTheme.colors.navy400, + shape = RoundedCornerShape(8.dp), + ) + .padding(vertical = 10.dp, horizontal = 20.dp), + contentAlignment = Alignment.Center, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (id != null) { + BitnagilIcon( + id = id, + tint = BitnagilTheme.colors.error, + modifier = Modifier + .padding(end = 4.dp) + .size(24.dp), + ) + } + Text( + text = text, + color = BitnagilTheme.colors.white, + style = BitnagilTheme.typography.body2Medium, + textAlign = TextAlign.Center, + ) + } + } +} + +@Composable +fun BitnagilToastContainer( + state: BitnagilToastState, + modifier: Modifier = Modifier, + duration: Long = 2000L, +) { + if (state.isVisible) { + LaunchedEffect(state.toastId) { + if (state.isVisible) { + delay(duration) + state.hide() + } + } + + AnimatedVisibility( + visible = state.isVisible, + enter = fadeIn() + slideInVertically { it / 2 }, + exit = fadeOut() + slideOutVertically { it / 2 }, + modifier = modifier, + ) { + BitnagilToastMessage( + text = state.text, + id = state.icon, + ) + } + } +} + +class BitnagilToastState { + private var _text by mutableStateOf("") + private var _icon by mutableStateOf(null) + private var _isVisible by mutableStateOf(false) + private var _toastId by mutableIntStateOf(0) + private var _lastShowTime = 0L + + val text: String get() = _text + val icon: Int? get() = _icon + val isVisible: Boolean get() = _isVisible + val toastId: Int get() = _toastId + + fun show(text: String, icon: Int? = null) { + if (shouldPreventDuplicateShow(text, icon)) { + return + } + showToast(text, icon) + } + + fun hide() { + _isVisible = false + } + + private fun shouldPreventDuplicateShow(text: String, icon: Int?): Boolean { + val currentTime = System.currentTimeMillis() + return _text == text && _icon == icon && currentTime - _lastShowTime < 500L + } + + private fun showToast(text: String, icon: Int?) { + _text = text + _icon = icon + _isVisible = true + _lastShowTime = System.currentTimeMillis() + _toastId += 1 + } +} + +@Composable +fun rememberBitnagilToast(): BitnagilToastState = remember { BitnagilToastState() } + +@Preview +@Composable +private fun BitnagilToastMessagePreview() { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + BitnagilToastMessage( + text = "버튼을 한 번 더 누르면 종료됩니다.", + id = R.drawable.ic_warning, + ) + + BitnagilToastMessage( + text = "루틴 완료 상태 저장에 실패했어요.\n다시 시도해 주세요.", + ) + } +} From e6d0f95af467c5767451b6fd48353156e62d38fb Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 30 Jul 2025 19:39:57 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat:=20=EC=A0=84=EC=97=AD=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전역에서 사용할 수 있는 BitnagilToast 를 관리하는 GlobalBitnagilToast 싱글톤 객체 추가 - MainActivity에 BitnagilToastContainer 추가 --- .../com/threegap/bitnagil/MainActivity.kt | 26 +++++++++++++++++-- .../common/toast/GlobalBitnagilToast.kt | 21 +++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/common/toast/GlobalBitnagilToast.kt diff --git a/app/src/main/java/com/threegap/bitnagil/MainActivity.kt b/app/src/main/java/com/threegap/bitnagil/MainActivity.kt index 4e5ceb87..41642f20 100644 --- a/app/src/main/java/com/threegap/bitnagil/MainActivity.kt +++ b/app/src/main/java/com/threegap/bitnagil/MainActivity.kt @@ -4,7 +4,16 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import com.threegap.bitnagil.designsystem.BitnagilTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.component.atom.BitnagilToastContainer +import com.threegap.bitnagil.designsystem.component.atom.rememberBitnagilToast +import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -14,10 +23,23 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { val mainNavigator = rememberMainNavigator() - BitnagilTheme { + val globalToast = rememberBitnagilToast() + + LaunchedEffect(globalToast) { + GlobalBitnagilToast.initialize(globalToast) + } + + Box(modifier = Modifier.fillMaxSize()) { MainScreen( navigator = mainNavigator, ) + + BitnagilToastContainer( + state = globalToast, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 100.dp), + ) } } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/common/toast/GlobalBitnagilToast.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/toast/GlobalBitnagilToast.kt new file mode 100644 index 00000000..b6eb4b24 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/toast/GlobalBitnagilToast.kt @@ -0,0 +1,21 @@ +package com.threegap.bitnagil.presentation.common.toast + +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilToastState +import java.lang.ref.WeakReference + +object GlobalBitnagilToast { + private var _toastStateRef: WeakReference? = null + + fun initialize(toastState: BitnagilToastState) { + _toastStateRef = WeakReference(toastState) + } + + fun show(text: String, icon: Int? = null) { + _toastStateRef?.get()?.show(text, icon) + } + + fun showCheck(text: String) = show(text, R.drawable.ic_check) + + fun showWarning(text: String) = show(text, R.drawable.ic_warning) +} From ed5058c6bcb38e0164e197f9f42307e515071d9b Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 30 Jul 2025 19:40:20 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20=EB=92=A4=EB=A1=9C=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EC=A2=85=EB=A3=8C=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/threegap/bitnagil/navigation/home/HomeNavHost.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt index 623a6c72..b7094717 100644 --- a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt +++ b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt @@ -25,6 +25,7 @@ import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilFloatingActionMenu import com.threegap.bitnagil.designsystem.component.atom.FloatingActionItem import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple +import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast import com.threegap.bitnagil.presentation.home.HomeScreenContainer import com.threegap.bitnagil.presentation.mypage.MyPageScreenContainer import com.threegap.bitnagil.presentation.recommendroutine.RecommendRoutineScreenContainer @@ -114,8 +115,7 @@ fun HomeNavHost( onToggle = { expanded -> showFloatingOverlay = expanded }, modifier = Modifier .align(Alignment.BottomEnd) - .padding(16.dp) - .padding(bottom = 80.dp), + .padding(end = 16.dp, bottom = 80.dp), ) } } @@ -131,6 +131,7 @@ fun DoubleBackButtonPressedHandler() { (context as? Activity)?.finish() } else { backPressedTimeMillis = System.currentTimeMillis() + GlobalBitnagilToast.showWarning("버튼을 한 번 더 누르면 종료됩니다.") } } } From 9287a724ba09a2e3a3ca508ecf24be3d43611694 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 30 Jul 2025 19:46:14 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=ED=99=88=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 루틴 완료/미완료 처리 실패 시 토스트 메시지 표시 - 감정 구슬 중복 등록 시 토스트 메시지 표시 --- .../bitnagil/presentation/home/HomeScreen.kt | 37 +++++++++++++++++-- .../presentation/home/HomeViewModel.kt | 25 +++++++++++++ .../presentation/home/model/HomeIntent.kt | 4 ++ .../presentation/home/model/HomeSideEffect.kt | 6 ++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt index 5e574b19..1bfa9476 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt @@ -26,6 +26,8 @@ import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple +import com.threegap.bitnagil.presentation.common.flow.collectAsEffect +import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast import com.threegap.bitnagil.presentation.home.component.template.CollapsibleHomeHeader import com.threegap.bitnagil.presentation.home.component.template.DeleteConfirmDialog import com.threegap.bitnagil.presentation.home.component.template.RoutineDetailsBottomSheet @@ -34,6 +36,7 @@ import com.threegap.bitnagil.presentation.home.component.template.RoutineSection import com.threegap.bitnagil.presentation.home.component.template.RoutineSortBottomSheet import com.threegap.bitnagil.presentation.home.component.template.WeeklyDatePicker import com.threegap.bitnagil.presentation.home.model.HomeIntent +import com.threegap.bitnagil.presentation.home.model.HomeSideEffect import com.threegap.bitnagil.presentation.home.model.HomeState import com.threegap.bitnagil.presentation.home.model.RoutineUiModel import com.threegap.bitnagil.presentation.home.util.rememberCollapsibleHeaderState @@ -48,6 +51,30 @@ fun HomeScreenContainer( ) { val uiState by viewModel.stateFlow.collectAsStateWithLifecycle() + viewModel.sideEffectFlow.collectAsEffect { sideEffect -> + when (sideEffect) { + is HomeSideEffect.NavigateToRegisterRoutine -> { + navigateToRegisterRoutine() + } + + is HomeSideEffect.NavigateToEmotion -> { + navigateToEmotion() + } + + is HomeSideEffect.NavigateToEditRoutine -> { + navigateToEditRoutine(sideEffect.routineId) + } + + is HomeSideEffect.ShowToastWithIcon -> { + GlobalBitnagilToast.showCheck(sideEffect.message) + } + + is HomeSideEffect.ShowToast -> { + GlobalBitnagilToast.show(sideEffect.message) + } + } + } + if (uiState.routineSortBottomSheetVisible) { RoutineSortBottomSheet( currentSortType = uiState.currentSortType, @@ -65,7 +92,7 @@ fun HomeScreenContainer( RoutineDetailsBottomSheet( routine = routine, onDismiss = { viewModel.sendIntent(HomeIntent.HideRoutineDetailsBottomSheet) }, - onEdit = navigateToEditRoutine, + onEdit = { viewModel.sendIntent(HomeIntent.NavigateToEditRoutine(routine.routineId)) }, onDelete = { if (routine.repeatDay.isEmpty()) { viewModel.deleteRoutine(routine.routineId) @@ -117,8 +144,12 @@ fun HomeScreenContainer( onShowRoutineDetailsBottomSheet = { routine -> viewModel.sendIntent(HomeIntent.ShowRoutineDetailsBottomSheet(routine)) }, - onRegisterRoutineClick = navigateToRegisterRoutine, - onRegisterEmotionClick = navigateToEmotion, + onRegisterRoutineClick = { + viewModel.sendIntent(HomeIntent.OnRegisterRoutineClick) + }, + onRegisterEmotionClick = { + viewModel.sendIntent(HomeIntent.OnRegisterEmotionClick) + }, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index ee88201d..63dd2e80 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -200,6 +200,30 @@ class HomeViewModel @Inject constructor( is HomeIntent.LoadMyEmotion -> { state.copy(myEmotion = intent.emotion) } + + is HomeIntent.OnRegisterEmotionClick -> { + if (state.myEmotion == null) { + sendSideEffect(HomeSideEffect.NavigateToEmotion) + } else { + sendSideEffect(HomeSideEffect.ShowToastWithIcon("선택한 감정 구슬이 이미 반영되었어요.")) + } + null + } + + is HomeIntent.OnRegisterRoutineClick -> { + sendSideEffect(HomeSideEffect.NavigateToRegisterRoutine) + null + } + + is HomeIntent.RoutineToggleCompletionFailure -> { + sendSideEffect(HomeSideEffect.ShowToast("루틴 완료 상태 저장에 실패했어요.\n다시 시도해 주세요.")) + null + } + + is HomeIntent.NavigateToEditRoutine -> { + sendSideEffect(HomeSideEffect.NavigateToEditRoutine(intent.routineId)) + null + } } return newState } @@ -450,6 +474,7 @@ class HomeViewModel @Inject constructor( onFailure = { error -> Log.e("HomeViewModel", "루틴 동기화 실패: ${error.message}") val backupState = backupStatesByDate[dateKey] ?: return + sendIntent(HomeIntent.RoutineToggleCompletionFailure) sendIntent(HomeIntent.LoadWeeklyRoutines(backupState)) pendingChangesByDate.remove(dateKey) backupStatesByDate.remove(dateKey) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt index f93ad5af..883b1533 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt @@ -20,6 +20,10 @@ sealed class HomeIntent : MviIntent { data class RestoreRoutinesAfterDeleteByDayFailure(val backupRoutines: RoutinesUiModel) : HomeIntent() data class ShowRoutineDetailsBottomSheet(val routine: RoutineUiModel) : HomeIntent() data class ShowDeleteConfirmDialog(val routine: RoutineUiModel) : HomeIntent() + data class NavigateToEditRoutine(val routineId: String) : HomeIntent() + data object RoutineToggleCompletionFailure : HomeIntent() + data object OnRegisterEmotionClick : HomeIntent() + data object OnRegisterRoutineClick : HomeIntent() data object OnPreviousWeekClick : HomeIntent() data object OnNextWeekClick : HomeIntent() data object ShowRoutineSortBottomSheet : HomeIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt index 7da21e4f..41db9104 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt @@ -3,5 +3,9 @@ package com.threegap.bitnagil.presentation.home.model import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect sealed interface HomeSideEffect : MviSideEffect { - data class ProcessRoutineToggle(val originalState: HomeState) : HomeSideEffect + data class ShowToast(val message: String) : HomeSideEffect + data class ShowToastWithIcon(val message: String) : HomeSideEffect + data class NavigateToEditRoutine(val routineId: String) : HomeSideEffect + data object NavigateToRegisterRoutine : HomeSideEffect + data object NavigateToEmotion : HomeSideEffect }