From 97e2c5d0f9f241061cde1d73adfa6eaa64b416ef Mon Sep 17 00:00:00 2001 From: StopStone Date: Sat, 1 Nov 2025 00:38:37 +0900 Subject: [PATCH 01/32] =?UTF-8?q?feat:=20=EC=9D=B4=EB=B2=88=EB=8B=AC=20?= =?UTF-8?q?=EC=B1=99=EA=B8=B8=EC=82=AC=EB=9E=8C=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 홈 화면의 '이달의 리마인더' 섹션에 '전체보기' 버튼을 추가하고, 클릭 시 해당 화면으로 이동하도록 구현했습니다. - `ic_front_8` 벡터 드로어블을 추가하여 '전체보기' 버튼 아이콘으로 사용했습니다. - 전체보기 화면으로 이동하는 `navigateToMonthlyReminderAll` 네비게이션 로직과 `MonthlyReminderAllScreen` 컴포저블을 추가했습니다. --- .../presentation/feature/home/HomeScreen.kt | 69 +++++++++++++++++-- .../feature/home/navigation/HomeNavigation.kt | 5 +- .../presentation/feature/main/NearNavHost.kt | 7 ++ .../MonthlyReminderAllNavigation.kt | 23 +++++++ .../navigation/MonthlyReminderAllScreen.kt | 17 +++++ Near/app/src/main/res/drawable/ic_front_8.xml | 13 ++++ 6 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt create mode 100644 Near/app/src/main/res/drawable/ic_front_8.xml diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt index 6307c964..f76d753e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt @@ -31,9 +31,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.paint import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -74,6 +76,7 @@ internal fun HomeRoute( onAlarmClick: () -> Unit = {}, onMyPageClick: () -> Unit = {}, onAddContactClick: () -> Unit = {}, + onMonthlyReminderAllClick: () -> Unit = {}, ) { LaunchedEffect(Unit) { launch { @@ -90,6 +93,7 @@ internal fun HomeRoute( onAlarmClick = onAlarmClick, onMyPageClick = onMyPageClick, onAddContactClick = onAddContactClick, + onMonthlyReminderAllClick = onMonthlyReminderAllClick, contacts = friends.value, monthlyFriends = monthlyFriends.value, memberInfo = memberInfo.value, @@ -104,6 +108,7 @@ internal fun HomeScreen( onMyPageClick: () -> Unit = {}, onAlarmClick: () -> Unit = {}, onAddContactClick: () -> Unit = {}, + onMonthlyReminderAllClick: () -> Unit = {}, memberInfo: MemberInfo?, contacts: List, monthlyFriends: List, @@ -130,7 +135,8 @@ internal fun HomeScreen( R.drawable.img_bg, ), contentScale = ContentScale.FillBounds, - ).fillMaxSize(), + ) + .fillMaxSize(), ) { Spacer(modifier = Modifier.height(statusBarHeightDp)) Row( @@ -174,12 +180,23 @@ internal fun HomeScreen( color = NearTheme.colors.WHITE_FFFFFF, ) Spacer(modifier = Modifier.height(32.dp)) - Text( - text = stringResource(R.string.home_this_month_people), - modifier = Modifier.padding(horizontal = 24.dp), - style = NearTheme.typography.B1_16_BOLD, - color = NearTheme.colors.WHITE_FFFFFF, - ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(R.string.home_this_month_people), + modifier = Modifier.padding(horizontal = 24.dp), + style = NearTheme.typography.B1_16_BOLD, + color = NearTheme.colors.WHITE_FFFFFF, + ) + + MonthlyReminderFriendsViewAll( + modifier = Modifier.padding(horizontal = 24.dp), + onMonthlyReminderAllClick = onMonthlyReminderAllClick, + ) + } Spacer(modifier = Modifier.height(16.dp)) if (monthlyFriends.isEmpty()) { Surface( @@ -370,6 +387,36 @@ private fun PagerIndicator(pagerState: PagerState) { } } +@Composable +fun MonthlyReminderFriendsViewAll( + modifier: Modifier = Modifier, + onMonthlyReminderAllClick: () -> Unit = {}, +) { + Row( + modifier = + modifier.onNoRippleClick( + onClick = onMonthlyReminderAllClick, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "전체보기", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.WHITE_FFFFFF, + modifier = Modifier.alpha(0.8f), + ) + + Spacer(modifier = Modifier.size(6.dp)) + + Image( + painter = painterResource(id = R.drawable.ic_front_8), + colorFilter = ColorFilter.tint(NearTheme.colors.WHITE_FFFFFF), + alpha = 1f, + contentDescription = null, + ) + } +} + @Preview @Composable internal fun HomeScreenPreview() { @@ -408,3 +455,11 @@ internal fun HomeScreenPreview() { ) } } + +@Preview +@Composable +fun MonthlyReminderFriendsViewAllPreview() { + NearTheme { + MonthlyReminderFriendsViewAll() + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt index 382ddeb5..61d1b03e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt @@ -5,9 +5,6 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.alarmy.near.model.Friend -import com.alarmy.near.presentation.feature.friendprofile.FriendProfileViewModel -import com.alarmy.near.presentation.feature.friendprofileedittor.navigation.FRIEND_PROFILE_EDIT_COMPLETE_KEY import com.alarmy.near.presentation.feature.home.HomeRoute import com.alarmy.near.presentation.feature.home.HomeViewModel import kotlinx.serialization.Serializable @@ -30,6 +27,7 @@ fun NavGraphBuilder.homeNavGraph( onAlarmClick: () -> Unit = {}, onMyPageClick: () -> Unit = {}, onAddContactClick: () -> Unit = {}, + onMonthlyReminderAllClick: () -> Unit = {}, ) { composable { backStackEntry -> val viewModel: HomeViewModel = hiltViewModel() @@ -43,6 +41,7 @@ fun NavGraphBuilder.homeNavGraph( onAlarmClick = onAlarmClick, onMyPageClick = onMyPageClick, onAddContactClick = onAddContactClick, + onMonthlyReminderAllClick = onMonthlyReminderAllClick, ) } } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt index e34f35a3..502aa89b 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt @@ -23,6 +23,8 @@ import com.alarmy.near.presentation.feature.home.navigation.homeNavGraph import com.alarmy.near.presentation.feature.home.navigation.navigateToHome import com.alarmy.near.presentation.feature.login.navigation.loginNavGraph import com.alarmy.near.presentation.feature.login.navigation.navigateToLogin +import com.alarmy.near.presentation.feature.mothlyreminderall.navigation.monthlyReminderAllNavGraph +import com.alarmy.near.presentation.feature.mothlyreminderall.navigation.navigateToMonthlyReminderAll import com.alarmy.near.presentation.feature.myprofile.navigation.myProfileNavGraph import com.alarmy.near.presentation.feature.myprofile.navigation.navigateToMyProfile import com.alarmy.near.presentation.feature.myprofile.navigation.navigateToWebView @@ -87,6 +89,11 @@ internal fun NearNavHost( onMyPageClick = { navController.navigateToMyProfile() }, onAlarmClick = {}, onAddContactClick = { navController.navigateToFriendContactCycle() }, + onMonthlyReminderAllClick = { navController.navigateToMonthlyReminderAll() }, + ) + + monthlyReminderAllNavGraph( + onShowErrorSnackBar = onShowSnackbar, ) myProfileNavGraph( diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt new file mode 100644 index 00000000..d9903719 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt @@ -0,0 +1,23 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable + +@Serializable +object RouteMonthlyReminderAll + +fun NavController.navigateToMonthlyReminderAll() { + navigate(RouteMonthlyReminderAll) +} + +fun NavGraphBuilder.monthlyReminderAllNavGraph( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, +) { + composable { backStackEntry -> + MonthlyReminderAllScreen( + onShowErrorSnackBar = onShowErrorSnackBar, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt new file mode 100644 index 00000000..8e73cc87 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt @@ -0,0 +1,17 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.navigation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun MonthlyReminderAllScreen(onShowErrorSnackBar: (throwable: Throwable?) -> Unit) { + Surface( + modifier = Modifier + .fillMaxSize() + .background(Color.Black), + ) { } +} diff --git a/Near/app/src/main/res/drawable/ic_front_8.xml b/Near/app/src/main/res/drawable/ic_front_8.xml new file mode 100644 index 00000000..8c0d485f --- /dev/null +++ b/Near/app/src/main/res/drawable/ic_front_8.xml @@ -0,0 +1,13 @@ + + + From 525fc6bb1853cb4363de744cbe62ab43cf92fc9f Mon Sep 17 00:00:00 2001 From: StopStone Date: Sat, 1 Nov 2025 01:08:09 +0900 Subject: [PATCH 02/32] =?UTF-8?q?feat:=20=EC=9B=94=EB=B3=84=20=EB=A6=AC?= =?UTF-8?q?=EB=A7=88=EC=9D=B8=EB=8D=94=20=EC=B9=9C=EA=B5=AC=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 전체 리마인더 화면에서 사용될 `MonthlyReminderFriendCard` 컴포저블을 추가했습니다. - 이 카드는 친구의 이름, 리마인더 내용, D-day, 그리고 '챙김 기록하기' 버튼으로 구성됩니다. --- .../components/MonthlyReminderFriendCard.kt | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt new file mode 100644 index 00000000..3adefb4f --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -0,0 +1,124 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.ui.component.button.NearBasicButton +import com.alarmy.near.presentation.ui.extension.dropShadow +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun MonthlyReminderFriendCard() { + Card( + modifier = + Modifier + .fillMaxWidth() + .wrapContentHeight() + .dropShadow( + shape = RoundedCornerShape(12.dp), + blur = 16.dp, + spread = 0.dp, + offsetX = 0.dp, + offsetY = 4.dp, + color = Color.Black.copy(0.12f), + ).padding(horizontal = 20.dp), + colors = + CardDefaults.cardColors( + containerColor = NearTheme.colors.WHITE_FFFFFF, + ), + ) { + Spacer(modifier = Modifier.size(20.dp)) + + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier.size(32.dp), + painter = painterResource(id = R.drawable.icon_visual_cake), + contentDescription = null, + ) + + Spacer(modifier = Modifier.size(12.dp)) + + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + + Spacer(modifier = Modifier.size(6.dp)) + + Text( + text = "생일 축하 전해요", + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + } + + Spacer(modifier = Modifier.size(10.dp)) + + Text( + modifier = Modifier.align(Alignment.Top), + text = "D-DAY", + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLUE01_5AA2E9, + ) + } + + Spacer(modifier = Modifier.size(16.dp)) + + NearBasicButton( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + onClick = {}, + contentPadding = PaddingValues(12.dp), + ) { + Text( + "챙김 기록하기", + ) + } + Spacer(modifier = Modifier.size(20.dp)) + } +} + +@Preview(showBackground = true, widthDp = 360, heightDp = 700) +@Composable +fun MonthlyReminderFriendCardPreview() { + NearTheme { + MonthlyReminderFriendCard() + } +} From 2daa449dbd1ba372c5891df9944b0efc5498b7ba Mon Sep 17 00:00:00 2001 From: StopStone Date: Sat, 1 Nov 2025 01:15:18 +0900 Subject: [PATCH 03/32] =?UTF-8?q?feat:=20=EC=9B=94=EB=B3=84=20=EB=A6=AC?= =?UTF-8?q?=EB=A7=88=EC=9D=B8=EB=8D=94=20=ED=99=94=EB=A9=B4=20=EB=B9=88=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 리마인더에 표시할 내용이 없을 때 보여줄 `MonthlyReminderEmpty` 컴포저블을 추가했습니다. - 해당 컴포저블은 안내 이미지와 "이번달은 챙길 사람이 없네요."라는 텍스트로 구성됩니다. --- .../components/MonthlyReminderEmpty.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt new file mode 100644 index 00000000..8268823d --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt @@ -0,0 +1,43 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun MonthlyReminderEmpty() { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(R.drawable.img_100_character_empty), + contentDescription = null, + ) + + Spacer(modifier = Modifier.size(16.dp)) + + Text( + text = "이번달은 챙길 사람이 없네요.", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + } +} + +@Preview +@Composable +fun MonthlyReminderEmptyPreview() { + NearTheme { + MonthlyReminderEmpty() + } +} From 1bc2ad35d059f72a11aab603de9d996c9ff91c22 Mon Sep 17 00:00:00 2001 From: StopStone Date: Sat, 1 Nov 2025 01:22:54 +0900 Subject: [PATCH 04/32] =?UTF-8?q?feat:=20=EC=9B=94=EB=B3=84=20=EB=A6=AC?= =?UTF-8?q?=EB=A7=88=EC=9D=B8=EB=8D=94=20=EC=99=84=EB=A3=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 리마인더의 완료된 항목을 표시하는 `MonthlyReminderComplete` 컴포저블을 새로 추가했습니다. - 이 컴포저블은 아이콘, 이름, 날짜를 포함하며, 완료된 상태의 UI를 나타냅니다. --- .../components/MonthlyReminderComplete.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt new file mode 100644 index 00000000..4c938770 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt @@ -0,0 +1,58 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.foundation.layout.size +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.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.R +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun MonthlyReminderComplete() { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(NearTheme.colors.BG02_F4F9FD) + .padding(vertical = 18.dp, horizontal = 20.dp), + ) { + Image( + painter = painterResource(R.drawable.icon_visual_cake), + contentDescription = null, + ) + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = "신짱구", + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "25.03.20", + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) + } +} + +@Preview +@Composable +fun MonthlyReminderCompletePreview() { + NearTheme { + MonthlyReminderComplete() + } +} From 7dc900c28fade771c60a4e536f32b0bfeca7cfc6 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 17:32:48 +0900 Subject: [PATCH 05/32] =?UTF-8?q?refactor:=20=EC=9B=94=EB=B3=84=20?= =?UTF-8?q?=EB=A6=AC=EB=A7=88=EC=9D=B8=EB=8D=94=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20ViewModel=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MonthlyReminderAllScreen.kt | 22 +++++++++++++++++++ .../MonthlyReminderAllNavigation.kt | 5 ++--- .../navigation/MonthlyReminderAllScreen.kt | 17 -------------- 3 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt delete mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt new file mode 100644 index 00000000..85ebe9ba --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -0,0 +1,22 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.hilt.navigation.compose.hiltViewModel + +@Composable +fun MonthlyReminderAllScreen( + viewModel: MonthlyReminderAllViewModel = hiltViewModel(), + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, +) { + Surface( + modifier = + Modifier + .fillMaxSize() + .background(Color.Black), + ) { } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt index d9903719..ea0db6f0 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt @@ -3,6 +3,7 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import com.alarmy.near.presentation.feature.mothlyreminderall.MonthlyReminderAllScreen import kotlinx.serialization.Serializable @Serializable @@ -12,9 +13,7 @@ fun NavController.navigateToMonthlyReminderAll() { navigate(RouteMonthlyReminderAll) } -fun NavGraphBuilder.monthlyReminderAllNavGraph( - onShowErrorSnackBar: (throwable: Throwable?) -> Unit, -) { +fun NavGraphBuilder.monthlyReminderAllNavGraph(onShowErrorSnackBar: (throwable: Throwable?) -> Unit) { composable { backStackEntry -> MonthlyReminderAllScreen( onShowErrorSnackBar = onShowErrorSnackBar, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt deleted file mode 100644 index 8e73cc87..00000000 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllScreen.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.alarmy.near.presentation.feature.mothlyreminderall.navigation - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color - -@Composable -fun MonthlyReminderAllScreen(onShowErrorSnackBar: (throwable: Throwable?) -> Unit) { - Surface( - modifier = Modifier - .fillMaxSize() - .background(Color.Black), - ) { } -} From 19cf75dcc538fb33035820485de09166691d89c7 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 22:28:06 +0900 Subject: [PATCH 06/32] =?UTF-8?q?feat:=20=EC=9D=B4=EB=B2=88=EB=8B=AC=20?= =?UTF-8?q?=EC=B1=99=EA=B8=B8=20=EC=82=AC=EB=9E=8C=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UI 상태(로딩, 데이터 있음, 데이터 없음)에 따라 각각 다른 화면을 표시합니다. - 뒤로가기 버튼을 통해 이전 화면으로 이동할 수 있습니다. --- .../presentation/feature/main/NearNavHost.kt | 1 + .../MonthlyReminderAllScreen.kt | 170 +++++++++++++++++- .../MonthlyReminderAllNavigation.kt | 10 +- 3 files changed, 169 insertions(+), 12 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt index 502aa89b..c8012d1f 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt @@ -94,6 +94,7 @@ internal fun NearNavHost( monthlyReminderAllNavGraph( onShowErrorSnackBar = onShowSnackbar, + onNavigateBack = { navController.popBackStack() }, ) myProfileNavGraph( diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 85ebe9ba..32220f5f 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -1,22 +1,174 @@ package com.alarmy.near.presentation.feature.mothlyreminderall import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Surface +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.alarmy.near.R +import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderEmpty +import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderFriendCard +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel +import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIEvent +import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIState +import com.alarmy.near.presentation.ui.component.NearFrame +import com.alarmy.near.presentation.ui.component.appbar.NearTopAppbar +import com.alarmy.near.presentation.ui.theme.NearTheme +import kotlinx.coroutines.launch @Composable -fun MonthlyReminderAllScreen( +fun MonthlyReminderAllRoute( viewModel: MonthlyReminderAllViewModel = hiltViewModel(), onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onNavigateBack: () -> Unit = {}, ) { - Surface( - modifier = - Modifier - .fillMaxSize() - .background(Color.Black), - ) { } + val uiState = viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(viewModel.uiEvent) { + launch { + viewModel.uiEvent.collect { event -> + when (event) { + is MonthlyReminderAllUIEvent.NetworkError -> { + onShowErrorSnackBar(IllegalStateException("네트워크 에러가 발생했습니다.")) + } + } + } + } + } + + MonthlyReminderAllScreen( + uiState = uiState.value, + onNavigateBack = onNavigateBack, + ) +} + +@Composable +internal fun MonthlyReminderAllScreen( + modifier: Modifier = Modifier, + uiState: MonthlyReminderAllUIState, + onNavigateBack: () -> Unit = {}, +) { + NearFrame( + modifier = modifier.background(NearTheme.colors.WHITE_FFFFFF), + ) { + NearTopAppbar( + title = "이번달 챙길 사람", + onClickBackButton = onNavigateBack, + ) + + when (uiState) { + is MonthlyReminderAllUIState.Loading -> { + Box( + modifier = + Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator(color = NearTheme.colors.BLUE01_5AA2E9) + } + } + + is MonthlyReminderAllUIState.Success -> { + if (uiState.monthlyReminders.isEmpty()) { + Box( + modifier = + Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + MonthlyReminderEmpty() + } + } else { + LazyColumn( + modifier = + Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + item { + Spacer(modifier = Modifier.height(16.dp)) + } + + items( + items = uiState.monthlyReminders, + key = { it.friendId }, + ) { reminder -> + MonthlyReminderFriendCard(reminder = reminder) + } + } + } + } + } + } +} + +@Preview +@Composable +private fun MonthlyReminderAllScreenPreview() { + NearTheme { + MonthlyReminderAllScreen( + uiState = + MonthlyReminderAllUIState.Success( + monthlyReminders = + listOf( + MonthlyReminderUIModel( + friendId = "1", + name = "신짱구", + imageRes = R.drawable.icon_visual_cake, + description = "생일 축하 전해요", + nextContactAt = "2025-11-05", + daysUntilNextContact = "D-4", + ), + MonthlyReminderUIModel( + friendId = "2", + name = "김철수", + imageRes = R.drawable.icon_visual_mail, + description = "가볍게 안부인사 전해요", + nextContactAt = "2025-11-01", + daysUntilNextContact = "D-DAY", + ), + MonthlyReminderUIModel( + friendId = "3", + name = "흰둥이", + imageRes = R.drawable.icon_visual_24_heart, + description = "소중한 날 마음을 전해요", + nextContactAt = "2025-11-10", + daysUntilNextContact = "D-9", + ), + ), + ), + ) + } +} + +@Preview +@Composable +private fun MonthlyReminderAllScreenEmptyPreview() { + NearTheme { + MonthlyReminderAllScreen( + uiState = MonthlyReminderAllUIState.Success(monthlyReminders = emptyList()), + ) + } +} + +@Preview +@Composable +private fun MonthlyReminderAllScreenLoadingPreview() { + NearTheme { + MonthlyReminderAllScreen( + uiState = MonthlyReminderAllUIState.Loading, + ) + } } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt index ea0db6f0..5c2280f7 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/navigation/MonthlyReminderAllNavigation.kt @@ -3,7 +3,7 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.alarmy.near.presentation.feature.mothlyreminderall.MonthlyReminderAllScreen +import com.alarmy.near.presentation.feature.mothlyreminderall.MonthlyReminderAllRoute import kotlinx.serialization.Serializable @Serializable @@ -13,10 +13,14 @@ fun NavController.navigateToMonthlyReminderAll() { navigate(RouteMonthlyReminderAll) } -fun NavGraphBuilder.monthlyReminderAllNavGraph(onShowErrorSnackBar: (throwable: Throwable?) -> Unit) { +fun NavGraphBuilder.monthlyReminderAllNavGraph( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onNavigateBack: () -> Unit, +) { composable { backStackEntry -> - MonthlyReminderAllScreen( + MonthlyReminderAllRoute( onShowErrorSnackBar = onShowErrorSnackBar, + onNavigateBack = onNavigateBack, ) } } From eb7cf4cd0dcc397406dea3dbac47facaeeb54e61 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 22:30:40 +0900 Subject: [PATCH 07/32] =?UTF-8?q?feat:=20=EC=9D=B4=EB=8B=AC=EC=9D=98=20?= =?UTF-8?q?=EC=B1=99=EA=B9=80=20=EC=B9=9C=EA=B5=AC=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?UI=20=EB=AA=A8=EB=8D=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MonthlyReminderFriendCard.kt | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index 3adefb4f..1b7e2a80 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -22,12 +22,13 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel import com.alarmy.near.presentation.ui.component.button.NearBasicButton import com.alarmy.near.presentation.ui.extension.dropShadow import com.alarmy.near.presentation.ui.theme.NearTheme @Composable -fun MonthlyReminderFriendCard() { +fun MonthlyReminderFriendCard(reminder: MonthlyReminderUIModel) { Card( modifier = Modifier @@ -57,7 +58,7 @@ fun MonthlyReminderFriendCard() { ) { Image( modifier = Modifier.size(32.dp), - painter = painterResource(id = R.drawable.icon_visual_cake), + painter = painterResource(id = reminder.imageRes), contentDescription = null, ) @@ -67,7 +68,7 @@ fun MonthlyReminderFriendCard() { modifier = Modifier.weight(1f), ) { Text( - text = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", + text = reminder.name, overflow = TextOverflow.Ellipsis, maxLines = 1, style = NearTheme.typography.B2_14_BOLD, @@ -77,7 +78,7 @@ fun MonthlyReminderFriendCard() { Spacer(modifier = Modifier.size(6.dp)) Text( - text = "생일 축하 전해요", + text = reminder.description, overflow = TextOverflow.Ellipsis, maxLines = 1, style = NearTheme.typography.B2_14_MEDIUM, @@ -87,13 +88,20 @@ fun MonthlyReminderFriendCard() { Spacer(modifier = Modifier.size(10.dp)) + val (textStyle, textColor) = + if (reminder.daysUntilNextContact == "D-DAY") { + NearTheme.typography.B2_14_BOLD to NearTheme.colors.BLUE01_5AA2E9 + } else { + NearTheme.typography.B2_14_MEDIUM to NearTheme.colors.GRAY01_888888 + } + Text( modifier = Modifier.align(Alignment.Top), - text = "D-DAY", + text = reminder.daysUntilNextContact, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = NearTheme.typography.B2_14_BOLD, - color = NearTheme.colors.BLUE01_5AA2E9, + style = textStyle, + color = textColor, ) } @@ -119,6 +127,16 @@ fun MonthlyReminderFriendCard() { @Composable fun MonthlyReminderFriendCardPreview() { NearTheme { - MonthlyReminderFriendCard() + MonthlyReminderFriendCard( + reminder = + MonthlyReminderUIModel( + friendId = "1", + name = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", + imageRes = R.drawable.icon_visual_cake, + description = "생일 축하 전해요", + nextContactAt = "2025-11-05", + daysUntilNextContact = "D-DAY", + ), + ) } } From d85077a185a690f40cb52a12643ab3c5d57c4ec2 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 22:39:11 +0900 Subject: [PATCH 08/32] =?UTF-8?q?feat:=20iewModel=20=EB=B0=8F=20UI=20State?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `MonthlyReminderAllViewModel`을 추가하여 월별 리마인더 목록을 가져오고 UI 상태를 관리합니다. - API 응답을 `MonthlyReminderUIModel`로 변환하여 UI에 필요한 데이터를 제공합니다. - `MonthlyReminderAllUIState` sealed interface를 `Loading`, `Empty`, `Success` 상태로 정의하여 UI 상태를 관리하도록 수정했습니다. --- .../MonthlyReminderAllScreen.kt | 51 ++++++------- .../MonthlyReminderAllViewModel.kt | 75 +++++++++++++++++++ .../model/MonthlyReminderTypeInfo.kt | 34 +++++++++ .../model/MonthlyReminderUIModel.kt | 13 ++++ .../uistate/MonthlyReminderAllUIEvent.kt | 6 ++ .../uistate/MonthlyReminderAllUIState.kt | 13 ++++ 6 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 32220f5f..8c1f972b 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -80,33 +80,33 @@ internal fun MonthlyReminderAllScreen( } } + is MonthlyReminderAllUIState.Empty -> { + Box( + modifier = + Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + MonthlyReminderEmpty() + } + } + is MonthlyReminderAllUIState.Success -> { - if (uiState.monthlyReminders.isEmpty()) { - Box( - modifier = - Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - MonthlyReminderEmpty() + LazyColumn( + modifier = + Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + item { + Spacer(modifier = Modifier.height(16.dp)) } - } else { - LazyColumn( - modifier = - Modifier - .fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - item { - Spacer(modifier = Modifier.height(16.dp)) - } - items( - items = uiState.monthlyReminders, - key = { it.friendId }, - ) { reminder -> - MonthlyReminderFriendCard(reminder = reminder) - } + items( + items = uiState.monthlyReminders, + key = { it.friendId }, + ) { reminder -> + MonthlyReminderFriendCard(reminder = reminder) } } } @@ -158,7 +158,7 @@ private fun MonthlyReminderAllScreenPreview() { private fun MonthlyReminderAllScreenEmptyPreview() { NearTheme { MonthlyReminderAllScreen( - uiState = MonthlyReminderAllUIState.Success(monthlyReminders = emptyList()), + uiState = MonthlyReminderAllUIState.Empty, ) } } @@ -172,3 +172,4 @@ private fun MonthlyReminderAllScreenLoadingPreview() { ) } } + diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt new file mode 100644 index 00000000..ee5be43b --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -0,0 +1,75 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.alarmy.near.data.repository.FriendRepository +import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderTypeInfo +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel +import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIEvent +import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.time.LocalDate +import javax.inject.Inject + +@HiltViewModel +class MonthlyReminderAllViewModel + @Inject + constructor( + private val friendRepository: FriendRepository, + ) : ViewModel() { + private val _uiEvent = Channel() + val uiEvent = _uiEvent.receiveAsFlow() + + private val _uiState: MutableStateFlow = + MutableStateFlow(MonthlyReminderAllUIState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + fetchMonthlyFriends() + } + + private fun fetchMonthlyFriends() { + friendRepository + .fetchMonthlyFriends() + .onEach { monthlyFriends -> + val uiModels = convertToUIModels(monthlyFriends) + _uiState.update { + if (uiModels.isEmpty()) { + MonthlyReminderAllUIState.Empty + } else { + MonthlyReminderAllUIState.Success(uiModels) + } + } + }.catch { exception -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) + } + }.launchIn(viewModelScope) + } + + private fun convertToUIModels(monthlyFriends: List): List { + val today = LocalDate.now() + return monthlyFriends.map { friend -> + val typeInfo = MonthlyReminderTypeInfo.from(friend.type) + MonthlyReminderUIModel( + friendId = friend.friendId, + name = friend.name, + imageRes = typeInfo.imageRes, + description = typeInfo.description, + nextContactAt = friend.nextContactAt, + daysUntilNextContact = friend.daysUntilNextContact(today), + ) + } + } + } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt new file mode 100644 index 00000000..1714e3c6 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt @@ -0,0 +1,34 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.model + +import androidx.annotation.DrawableRes +import com.alarmy.near.R +import com.alarmy.near.model.monthly.MonthlyFriendType + +enum class MonthlyReminderTypeInfo( + @param:DrawableRes val imageRes: Int, + val description: String, +) { + ANNIVERSARY( + imageRes = R.drawable.icon_visual_24_heart, + description = "소중한 날 마음을 전해요", + ), + BIRTHDAY( + imageRes = R.drawable.icon_visual_cake, + description = "생일 축하 전해요", + ), + MESSAGE( + imageRes = R.drawable.icon_visual_mail, + description = "가볍게 안부인사 전해요", + ), + ; + + companion object { + fun from(type: MonthlyFriendType): MonthlyReminderTypeInfo = + when (type) { + MonthlyFriendType.ANNIVERSARY -> ANNIVERSARY + MonthlyFriendType.BIRTHDAY -> BIRTHDAY + MonthlyFriendType.MESSAGE -> MESSAGE + } + } +} + diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt new file mode 100644 index 00000000..012ec0f5 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt @@ -0,0 +1,13 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.model + +import androidx.annotation.DrawableRes + +data class MonthlyReminderUIModel( + val friendId: String, + val name: String, + @DrawableRes val imageRes: Int, + val description: String, + val nextContactAt: String, + val daysUntilNextContact: String, +) + diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt new file mode 100644 index 00000000..ff93a282 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt @@ -0,0 +1,6 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.uistate + +sealed interface MonthlyReminderAllUIEvent { + data object NetworkError : MonthlyReminderAllUIEvent +} + diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt new file mode 100644 index 00000000..3a16596e --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt @@ -0,0 +1,13 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.uistate + +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel + +sealed interface MonthlyReminderAllUIState { + data object Loading : MonthlyReminderAllUIState + + data object Empty : MonthlyReminderAllUIState + + data class Success( + val monthlyReminders: List, + ) : MonthlyReminderAllUIState +} From 7f86285f8a0c10bb8f7ec36aa47f0cbf3fd534ad Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:10:47 +0900 Subject: [PATCH 09/32] =?UTF-8?q?feat:=20=EC=9D=B4=EB=8B=AC=EC=9D=98=20?= =?UTF-8?q?=EC=B1=99=EA=B9=80=20=EC=99=84=EB=A3=8C=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../near/data/repository/DefaultFriendRepository.kt | 5 +++++ .../com/alarmy/near/data/repository/FriendRepository.kt | 9 +++++++-- .../com/alarmy/near/network/service/FriendService.kt | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt index e63d0dd0..31045cfa 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt @@ -39,6 +39,11 @@ class DefaultFriendRepository ) } + override fun fetchMonthlyCompleteFriends(): Flow> = + apiCallFlow { + friendService.fetchMonthlyCompleteFriends().map { it.toModel() } + } + override fun fetchFriendById(friendId: String): Flow = flow { emit(friendService.fetchFriendById(friendId).toModel()) diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/FriendRepository.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/FriendRepository.kt index c437c4e4..78774f86 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/repository/FriendRepository.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/FriendRepository.kt @@ -2,9 +2,9 @@ package com.alarmy.near.data.repository import com.alarmy.near.model.Friend import com.alarmy.near.model.FriendRecord -import com.alarmy.near.network.response.FriendInitItemEntity import com.alarmy.near.model.friendsummary.FriendSummary import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.network.response.FriendInitItemEntity import com.alarmy.near.presentation.feature.friendcontactcycle.model.FriendContactUIModel import kotlinx.coroutines.flow.Flow @@ -13,6 +13,8 @@ interface FriendRepository { fun fetchMonthlyFriends(): Flow> + fun fetchMonthlyCompleteFriends(): Flow> + fun fetchFriendById(friendId: String): Flow fun updateFriend( @@ -26,5 +28,8 @@ interface FriendRepository { fun recordContact(friendId: String): Flow - fun initFriends(contacts: List, providerType: String): Flow> + fun initFriends( + contacts: List, + providerType: String, + ): Flow> } diff --git a/Near/app/src/main/java/com/alarmy/near/network/service/FriendService.kt b/Near/app/src/main/java/com/alarmy/near/network/service/FriendService.kt index c7cf3864..7fe55c5e 100644 --- a/Near/app/src/main/java/com/alarmy/near/network/service/FriendService.kt +++ b/Near/app/src/main/java/com/alarmy/near/network/service/FriendService.kt @@ -22,6 +22,9 @@ interface FriendService { @GET("/friend/monthly") suspend fun fetchMonthlyFriends(): List + @GET("/friend/monthly/complete") + suspend fun fetchMonthlyCompleteFriends(): List + @GET("/friend/{friendId}") suspend fun fetchFriendById( @Path("friendId") friendId: String, From 1bf63ed528567410e6723b95e22bf2ed8e712050 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:11:06 +0900 Subject: [PATCH 10/32] =?UTF-8?q?chore:=20MonthlyReminderFriendCard=20?= =?UTF-8?q?=ED=8C=A8=EB=94=A9=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mothlyreminderall/components/MonthlyReminderFriendCard.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index 1b7e2a80..d393fc1a 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -41,7 +41,7 @@ fun MonthlyReminderFriendCard(reminder: MonthlyReminderUIModel) { offsetX = 0.dp, offsetY = 4.dp, color = Color.Black.copy(0.12f), - ).padding(horizontal = 20.dp), + ), colors = CardDefaults.cardColors( containerColor = NearTheme.colors.WHITE_FFFFFF, From 05ac850de2e87bebac7be77b0bcfac55c657b18e Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:11:36 +0900 Subject: [PATCH 11/32] =?UTF-8?q?feat:=20=EC=B1=99=EA=B9=80=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EB=AA=A9=EB=A1=9D=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `MonthlyReminderComplete` 컴포저블이 `MonthlyReminderUIModel` 데이터를 받아 실제 데이터를 표시하도록 수정했습니다. - 날짜 형식을 'yy.MM.dd'로 포맷하는 `formatDate` 함수를 추가했습니다. - 이름이 길어질 경우 말줄임표(...)로 처리되도록 `TextOverflow.Ellipsis` 속성을 적용했습니다. --- .../components/MonthlyReminderComplete.kt | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt index 4c938770..5adf204e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt @@ -14,13 +14,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel import com.alarmy.near.presentation.ui.theme.NearTheme +import java.time.LocalDate +import java.time.format.DateTimeFormatter @Composable -fun MonthlyReminderComplete() { +fun MonthlyReminderComplete(reminder: MonthlyReminderUIModel) { + val formattedDate = formatDate(reminder.nextContactAt) + Row( verticalAlignment = Alignment.CenterVertically, modifier = @@ -31,28 +37,49 @@ fun MonthlyReminderComplete() { .padding(vertical = 18.dp, horizontal = 20.dp), ) { Image( - painter = painterResource(R.drawable.icon_visual_cake), + painter = painterResource(reminder.imageRes), contentDescription = null, ) Spacer(modifier = Modifier.size(12.dp)) Text( - text = "신짱구", + text = reminder.name, style = NearTheme.typography.B2_14_BOLD, color = NearTheme.colors.BLACK_1A1A1A, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.size(10.dp)) Text( - text = "25.03.20", + text = formattedDate, style = NearTheme.typography.B2_14_MEDIUM, color = NearTheme.colors.GRAY01_888888, ) } } +private fun formatDate(dateString: String): String = + try { + val date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd")) + date.format(DateTimeFormatter.ofPattern("yy.MM.dd")) + } catch (e: Exception) { + dateString + } + @Preview @Composable fun MonthlyReminderCompletePreview() { NearTheme { - MonthlyReminderComplete() + MonthlyReminderComplete( + reminder = + MonthlyReminderUIModel( + friendId = "1", + name = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", + imageRes = R.drawable.icon_visual_cake, + description = "생일 축하 전해요", + nextContactAt = "2025-03-20", + daysUntilNextContact = "D-9", + ), + ) } } From 5b178df1d93aa49fa69f3f592bb9d4776d59e600 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:12:17 +0900 Subject: [PATCH 12/32] =?UTF-8?q?feat:=20=EC=B1=99=EA=B9=80=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MonthlyReminderAllScreen.kt | 42 ++++++++++++++++--- .../MonthlyReminderAllViewModel.kt | 41 +++++++++++++++--- .../uistate/MonthlyReminderAllUIState.kt | 1 + 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 8c1f972b..a5291397 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -6,9 +6,12 @@ import androidx.compose.foundation.layout.Box 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.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -18,6 +21,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.alarmy.near.R +import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderComplete import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderEmpty import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderFriendCard import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel @@ -61,7 +65,10 @@ internal fun MonthlyReminderAllScreen( onNavigateBack: () -> Unit = {}, ) { NearFrame( - modifier = modifier.background(NearTheme.colors.WHITE_FFFFFF), + modifier = + modifier + .background(NearTheme.colors.WHITE_FFFFFF) + .padding(horizontal = 20.dp), ) { NearTopAppbar( title = "이번달 챙길 사람", @@ -104,11 +111,34 @@ internal fun MonthlyReminderAllScreen( items( items = uiState.monthlyReminders, - key = { it.friendId }, + key = { "monthly_${it.friendId}" }, ) { reminder -> MonthlyReminderFriendCard(reminder = reminder) } + + item { + Spacer(modifier = Modifier.size(16.dp)) + } + + if (uiState.completedReminders.isNotEmpty()) { + item { + Text( + text = "챙김 완료", + style = NearTheme.typography.B2_14_BOLD, + color = NearTheme.colors.BLACK_1A1A1A, + ) + } + + items( + items = uiState.completedReminders, + key = { "completed_${it.friendId}" }, + ) { reminder -> + MonthlyReminderComplete(reminder = reminder) + } + } } + + Spacer(modifier = Modifier.size(80.dp)) } } } @@ -139,13 +169,16 @@ private fun MonthlyReminderAllScreenPreview() { nextContactAt = "2025-11-01", daysUntilNextContact = "D-DAY", ), + ), + completedReminders = + listOf( MonthlyReminderUIModel( friendId = "3", name = "흰둥이", imageRes = R.drawable.icon_visual_24_heart, description = "소중한 날 마음을 전해요", - nextContactAt = "2025-11-10", - daysUntilNextContact = "D-9", + nextContactAt = "2025-10-15", + daysUntilNextContact = "D+16", ), ), ), @@ -172,4 +205,3 @@ private fun MonthlyReminderAllScreenLoadingPreview() { ) } } - diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index ee5be43b..a55ba04e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -35,8 +35,12 @@ class MonthlyReminderAllViewModel MutableStateFlow(MonthlyReminderAllUIState.Loading) val uiState: StateFlow = _uiState.asStateFlow() + private val _monthlyReminders = MutableStateFlow>(emptyList()) + private val _completedReminders = MutableStateFlow>(emptyList()) + init { fetchMonthlyFriends() + fetchMonthlyCompleteFriends() } private fun fetchMonthlyFriends() { @@ -44,13 +48,22 @@ class MonthlyReminderAllViewModel .fetchMonthlyFriends() .onEach { monthlyFriends -> val uiModels = convertToUIModels(monthlyFriends) - _uiState.update { - if (uiModels.isEmpty()) { - MonthlyReminderAllUIState.Empty - } else { - MonthlyReminderAllUIState.Success(uiModels) - } + _monthlyReminders.value = uiModels + updateUIState() + }.catch { exception -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) } + }.launchIn(viewModelScope) + } + + private fun fetchMonthlyCompleteFriends() { + friendRepository + .fetchMonthlyCompleteFriends() + .onEach { monthlyFriends -> + val uiModels = convertToUIModels(monthlyFriends) + _completedReminders.value = uiModels + updateUIState() }.catch { exception -> viewModelScope.launch { _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) @@ -58,6 +71,22 @@ class MonthlyReminderAllViewModel }.launchIn(viewModelScope) } + private fun updateUIState() { + val monthlyList = _monthlyReminders.value + val completedList = _completedReminders.value + + _uiState.update { + if (monthlyList.isEmpty() && completedList.isEmpty()) { + MonthlyReminderAllUIState.Empty + } else { + MonthlyReminderAllUIState.Success( + monthlyReminders = monthlyList, + completedReminders = completedList, + ) + } + } + } + private fun convertToUIModels(monthlyFriends: List): List { val today = LocalDate.now() return monthlyFriends.map { friend -> diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt index 3a16596e..96acd1d7 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt @@ -9,5 +9,6 @@ sealed interface MonthlyReminderAllUIState { data class Success( val monthlyReminders: List, + val completedReminders: List, ) : MonthlyReminderAllUIState } From 767fe0c6956526703b2aa71233b6a654b52850a4 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:26:34 +0900 Subject: [PATCH 13/32] =?UTF-8?q?feat:=20=EC=97=B0=EB=9D=BD=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=EC=84=B1=EA=B3=B5=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 연락을 기록했을 때 표시되는 성공 다이얼로그 UI를 구현했습니다. - 다이얼로그는 1.5초 후 자동으로 사라집니다. --- .../MonthlyReminderAllScreen.kt | 31 ++++++++-- .../MonthlyReminderAllViewModel.kt | 16 ++++++ .../components/MonthlyReminderFriendCard.kt | 7 ++- .../components/RecordSuccessDialog.kt | 57 +++++++++++++++++++ .../uistate/MonthlyReminderAllUIEvent.kt | 2 + 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index a5291397..335a6956 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -24,6 +26,7 @@ import com.alarmy.near.R import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderComplete import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderEmpty import com.alarmy.near.presentation.feature.mothlyreminderall.components.MonthlyReminderFriendCard +import com.alarmy.near.presentation.feature.mothlyreminderall.components.RecordSuccessDialog import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIEvent import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIState @@ -39,6 +42,7 @@ fun MonthlyReminderAllRoute( onNavigateBack: () -> Unit = {}, ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle() + val recordSuccessDialogState = remember { mutableStateOf(false) } LaunchedEffect(viewModel.uiEvent) { launch { @@ -47,6 +51,10 @@ fun MonthlyReminderAllRoute( is MonthlyReminderAllUIEvent.NetworkError -> { onShowErrorSnackBar(IllegalStateException("네트워크 에러가 발생했습니다.")) } + + is MonthlyReminderAllUIEvent.RecordFriendShipSuccess -> { + recordSuccessDialogState.value = true + } } } } @@ -54,7 +62,12 @@ fun MonthlyReminderAllRoute( MonthlyReminderAllScreen( uiState = uiState.value, + recordSuccessDialogState = recordSuccessDialogState.value, onNavigateBack = onNavigateBack, + onRecordFriendShip = viewModel::onRecordFriendShip, + onDismissRecordSuccessDialog = { + recordSuccessDialogState.value = false + }, ) } @@ -62,13 +75,19 @@ fun MonthlyReminderAllRoute( internal fun MonthlyReminderAllScreen( modifier: Modifier = Modifier, uiState: MonthlyReminderAllUIState, + recordSuccessDialogState: Boolean = false, onNavigateBack: () -> Unit = {}, + onRecordFriendShip: (String) -> Unit = {}, + onDismissRecordSuccessDialog: () -> Unit = {}, ) { + if (recordSuccessDialogState) { + RecordSuccessDialog(onDismiss = onDismissRecordSuccessDialog) + } + NearFrame( modifier = modifier - .background(NearTheme.colors.WHITE_FFFFFF) - .padding(horizontal = 20.dp), + .background(NearTheme.colors.WHITE_FFFFFF), ) { NearTopAppbar( title = "이번달 챙길 사람", @@ -102,7 +121,8 @@ internal fun MonthlyReminderAllScreen( LazyColumn( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .padding(horizontal = 20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { item { @@ -113,7 +133,10 @@ internal fun MonthlyReminderAllScreen( items = uiState.monthlyReminders, key = { "monthly_${it.friendId}" }, ) { reminder -> - MonthlyReminderFriendCard(reminder = reminder) + MonthlyReminderFriendCard( + reminder = reminder, + onRecordClick = onRecordFriendShip, + ) } item { diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index a55ba04e..d022c526 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -87,6 +87,22 @@ class MonthlyReminderAllViewModel } } + fun onRecordFriendShip(friendId: String) { + friendRepository + .recordContact(friendId) + .onEach { _ -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) + } + fetchMonthlyFriends() + fetchMonthlyCompleteFriends() + }.catch { exception -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) + } + }.launchIn(viewModelScope) + } + private fun convertToUIModels(monthlyFriends: List): List { val today = LocalDate.now() return monthlyFriends.map { friend -> diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index d393fc1a..ab46936f 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -28,7 +28,10 @@ import com.alarmy.near.presentation.ui.extension.dropShadow import com.alarmy.near.presentation.ui.theme.NearTheme @Composable -fun MonthlyReminderFriendCard(reminder: MonthlyReminderUIModel) { +fun MonthlyReminderFriendCard( + reminder: MonthlyReminderUIModel, + onRecordClick: (String) -> Unit = {}, +) { Card( modifier = Modifier @@ -112,7 +115,7 @@ fun MonthlyReminderFriendCard(reminder: MonthlyReminderUIModel) { Modifier .fillMaxWidth() .padding(horizontal = 20.dp), - onClick = {}, + onClick = { onRecordClick(reminder.friendId) }, contentPadding = PaddingValues(12.dp), ) { Text( diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt new file mode 100644 index 00000000..fedd5db9 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt @@ -0,0 +1,57 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.alarmy.near.R +import com.alarmy.near.presentation.ui.theme.NearTheme +import kotlinx.coroutines.delay + +@Composable +fun RecordSuccessDialog(onDismiss: () -> Unit) { + LaunchedEffect(Unit) { + delay(1500) + onDismiss() + } + + Dialog(onDismissRequest = onDismiss) { + Column( + modifier = + Modifier + .width(255.dp) + .height(186.dp) + .background( + color = NearTheme.colors.WHITE_FFFFFF, + shape = RoundedCornerShape(16.dp), + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(R.drawable.img_100_character_success), + contentDescription = null, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + stringResource(R.string.friend_profile_info_contact_success_text), + style = NearTheme.typography.B1_16_BOLD, + color = Color(0xff222222), + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt index ff93a282..494e6a56 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt @@ -2,5 +2,7 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.uistate sealed interface MonthlyReminderAllUIEvent { data object NetworkError : MonthlyReminderAllUIEvent + + data object RecordFriendShipSuccess : MonthlyReminderAllUIEvent } From 761cdaf53698cc802af56766b119d920de77309e Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:37:58 +0900 Subject: [PATCH 14/32] =?UTF-8?q?refactor:=20=EC=B1=99=EA=B9=80=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20UI=20?= =?UTF-8?q?=EC=A6=89=EC=8B=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 리마인더 목록에서 완료 처리한 친구를 연락 완료 목록으로 즉시 이동시켜 UI에 반영하도록 수정했습니다. - 기존의 네트워크 통신을 통한 목록 갱신 방식 대신, 로컬에서 목록을 직접 업데이트하여 불필요한 API 호출을 제거하고 응답성을 개선했습니다. --- .../MonthlyReminderAllViewModel.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index d022c526..26b0242b 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -74,13 +74,15 @@ class MonthlyReminderAllViewModel private fun updateUIState() { val monthlyList = _monthlyReminders.value val completedList = _completedReminders.value + val completedFriendIds = completedList.map { it.friendId }.toSet() + val filteredMonthlyList = monthlyList.filter { it.friendId !in completedFriendIds } _uiState.update { - if (monthlyList.isEmpty() && completedList.isEmpty()) { + if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { MonthlyReminderAllUIState.Empty } else { MonthlyReminderAllUIState.Success( - monthlyReminders = monthlyList, + monthlyReminders = filteredMonthlyList, completedReminders = completedList, ) } @@ -94,8 +96,12 @@ class MonthlyReminderAllViewModel viewModelScope.launch { _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) } - fetchMonthlyFriends() - fetchMonthlyCompleteFriends() + val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } + if (recordedFriend != null) { + _monthlyReminders.value = _monthlyReminders.value.filter { it.friendId != friendId } + _completedReminders.value = listOf(recordedFriend) + _completedReminders.value + updateUIState() + } }.catch { exception -> viewModelScope.launch { _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) From 7a7334d4c6e5b46ee101267fec30c00c2d388a22 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:46:12 +0900 Subject: [PATCH 15/32] =?UTF-8?q?refactor:=20=EC=9B=94=EB=B3=84=20?= =?UTF-8?q?=EB=A6=AC=EB=A7=88=EC=9D=B8=EB=8D=94=20=EC=97=86=EC=9D=8C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mothlyreminderall/MonthlyReminderAllScreen.kt | 9 +-------- .../components/MonthlyReminderEmpty.kt | 11 ++++++++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 335a6956..f03ca889 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -107,14 +107,7 @@ internal fun MonthlyReminderAllScreen( } is MonthlyReminderAllUIState.Empty -> { - Box( - modifier = - Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - MonthlyReminderEmpty() - } + MonthlyReminderEmpty() } is MonthlyReminderAllUIState.Success -> { diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt index 8268823d..bd9a2ad5 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt @@ -1,8 +1,11 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.components import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -17,8 +20,14 @@ import com.alarmy.near.presentation.ui.theme.NearTheme @Composable fun MonthlyReminderEmpty() { Column( + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight(), horizontalAlignment = Alignment.CenterHorizontally, ) { + Spacer(modifier = Modifier.fillMaxHeight(0.28f)) + Image( painter = painterResource(R.drawable.img_100_character_empty), contentDescription = null, @@ -34,7 +43,7 @@ fun MonthlyReminderEmpty() { } } -@Preview +@Preview(showBackground = true) @Composable fun MonthlyReminderEmptyPreview() { NearTheme { From 39c7efc9ba675ac8b4253a1abfe9651623dc2ae5 Mon Sep 17 00:00:00 2001 From: stopstone Date: Sat, 1 Nov 2025 23:55:13 +0900 Subject: [PATCH 16/32] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=88=EB=8B=AC?= =?UTF-8?q?=20=EC=B1=99=EA=B8=B8=20=EC=82=AC=EB=9E=8C=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MonthlyReminderAllScreen.kt | 19 ++++++++++----- .../MonthlyReminderAllViewModel.kt | 24 +++++++++++-------- .../components/MonthlyReminderComplete.kt | 2 +- .../components/MonthlyReminderEmpty.kt | 3 ++- .../components/MonthlyReminderFriendCard.kt | 21 ++++++++-------- .../model/MonthlyReminderTypeInfo.kt | 9 +++---- .../model/MonthlyReminderUIModel.kt | 3 ++- Near/app/src/main/res/values/strings.xml | 10 ++++++++ 8 files changed, 58 insertions(+), 33 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index f03ca889..5032b30d 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -18,6 +18,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -41,6 +43,7 @@ fun MonthlyReminderAllRoute( onShowErrorSnackBar: (throwable: Throwable?) -> Unit, onNavigateBack: () -> Unit = {}, ) { + val context = LocalContext.current val uiState = viewModel.uiState.collectAsStateWithLifecycle() val recordSuccessDialogState = remember { mutableStateOf(false) } @@ -49,7 +52,11 @@ fun MonthlyReminderAllRoute( viewModel.uiEvent.collect { event -> when (event) { is MonthlyReminderAllUIEvent.NetworkError -> { - onShowErrorSnackBar(IllegalStateException("네트워크 에러가 발생했습니다.")) + onShowErrorSnackBar( + IllegalStateException( + context.getString(R.string.monthly_reminder_all_network_error) + ) + ) } is MonthlyReminderAllUIEvent.RecordFriendShipSuccess -> { @@ -90,7 +97,7 @@ internal fun MonthlyReminderAllScreen( .background(NearTheme.colors.WHITE_FFFFFF), ) { NearTopAppbar( - title = "이번달 챙길 사람", + title = stringResource(R.string.monthly_reminder_all_title), onClickBackButton = onNavigateBack, ) @@ -139,7 +146,7 @@ internal fun MonthlyReminderAllScreen( if (uiState.completedReminders.isNotEmpty()) { item { Text( - text = "챙김 완료", + text = stringResource(R.string.monthly_reminder_all_completed_section_title), style = NearTheme.typography.B2_14_BOLD, color = NearTheme.colors.BLACK_1A1A1A, ) @@ -173,7 +180,7 @@ private fun MonthlyReminderAllScreenPreview() { friendId = "1", name = "신짱구", imageRes = R.drawable.icon_visual_cake, - description = "생일 축하 전해요", + descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-11-05", daysUntilNextContact = "D-4", ), @@ -181,7 +188,7 @@ private fun MonthlyReminderAllScreenPreview() { friendId = "2", name = "김철수", imageRes = R.drawable.icon_visual_mail, - description = "가볍게 안부인사 전해요", + descriptionRes = R.string.monthly_reminder_all_type_message_description, nextContactAt = "2025-11-01", daysUntilNextContact = "D-DAY", ), @@ -192,7 +199,7 @@ private fun MonthlyReminderAllScreenPreview() { friendId = "3", name = "흰둥이", imageRes = R.drawable.icon_visual_24_heart, - description = "소중한 날 마음을 전해요", + descriptionRes = R.string.monthly_reminder_all_type_anniversary_description, nextContactAt = "2025-10-15", daysUntilNextContact = "D+16", ), diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index 26b0242b..8017442c 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -111,16 +111,20 @@ class MonthlyReminderAllViewModel private fun convertToUIModels(monthlyFriends: List): List { val today = LocalDate.now() - return monthlyFriends.map { friend -> - val typeInfo = MonthlyReminderTypeInfo.from(friend.type) - MonthlyReminderUIModel( - friendId = friend.friendId, - name = friend.name, - imageRes = typeInfo.imageRes, - description = typeInfo.description, - nextContactAt = friend.nextContactAt, - daysUntilNextContact = friend.daysUntilNextContact(today), - ) + return monthlyFriends.mapNotNull { friend -> + try { + val typeInfo = MonthlyReminderTypeInfo.from(friend.type) + MonthlyReminderUIModel( + friendId = friend.friendId, + name = friend.name, + imageRes = typeInfo.imageRes, + descriptionRes = typeInfo.descriptionRes, + nextContactAt = friend.nextContactAt, + daysUntilNextContact = friend.daysUntilNextContact(today), + ) + } catch (e: Exception) { + null + } } } } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt index 5adf204e..8b610039 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt @@ -76,7 +76,7 @@ fun MonthlyReminderCompletePreview() { friendId = "1", name = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", imageRes = R.drawable.icon_visual_cake, - description = "생일 축하 전해요", + descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-03-20", daysUntilNextContact = "D-9", ), diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt index bd9a2ad5..3e9ac6e2 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderEmpty.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R @@ -36,7 +37,7 @@ fun MonthlyReminderEmpty() { Spacer(modifier = Modifier.size(16.dp)) Text( - text = "이번달은 챙길 사람이 없네요.", + text = stringResource(R.string.monthly_reminder_all_empty_text), style = NearTheme.typography.B2_14_MEDIUM, color = NearTheme.colors.GRAY01_888888, ) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index ab46936f..526bcafc 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -78,15 +79,15 @@ fun MonthlyReminderFriendCard( color = NearTheme.colors.BLACK_1A1A1A, ) - Spacer(modifier = Modifier.size(6.dp)) + Spacer(modifier = Modifier.size(6.dp)) - Text( - text = reminder.description, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = NearTheme.typography.B2_14_MEDIUM, - color = NearTheme.colors.GRAY01_888888, - ) + Text( + text = stringResource(reminder.descriptionRes), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) } Spacer(modifier = Modifier.size(10.dp)) @@ -119,7 +120,7 @@ fun MonthlyReminderFriendCard( contentPadding = PaddingValues(12.dp), ) { Text( - "챙김 기록하기", + text = stringResource(R.string.monthly_reminder_all_record_button_text), ) } Spacer(modifier = Modifier.size(20.dp)) @@ -136,7 +137,7 @@ fun MonthlyReminderFriendCardPreview() { friendId = "1", name = "신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구신짱구", imageRes = R.drawable.icon_visual_cake, - description = "생일 축하 전해요", + descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-11-05", daysUntilNextContact = "D-DAY", ), diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt index 1714e3c6..87778504 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderTypeInfo.kt @@ -1,24 +1,25 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.model import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import com.alarmy.near.R import com.alarmy.near.model.monthly.MonthlyFriendType enum class MonthlyReminderTypeInfo( @param:DrawableRes val imageRes: Int, - val description: String, + @param:StringRes val descriptionRes: Int, ) { ANNIVERSARY( imageRes = R.drawable.icon_visual_24_heart, - description = "소중한 날 마음을 전해요", + descriptionRes = R.string.monthly_reminder_all_type_anniversary_description, ), BIRTHDAY( imageRes = R.drawable.icon_visual_cake, - description = "생일 축하 전해요", + descriptionRes = R.string.monthly_reminder_all_type_birthday_description, ), MESSAGE( imageRes = R.drawable.icon_visual_mail, - description = "가볍게 안부인사 전해요", + descriptionRes = R.string.monthly_reminder_all_type_message_description, ), ; diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt index 012ec0f5..1a8b4ad7 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt @@ -1,12 +1,13 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.model import androidx.annotation.DrawableRes +import androidx.annotation.StringRes data class MonthlyReminderUIModel( val friendId: String, val name: String, @DrawableRes val imageRes: Int, - val description: String, + @StringRes val descriptionRes: Int, val nextContactAt: String, val daysUntilNextContact: String, ) diff --git a/Near/app/src/main/res/values/strings.xml b/Near/app/src/main/res/values/strings.xml index b926446c..0022b7e5 100644 --- a/Near/app/src/main/res/values/strings.xml +++ b/Near/app/src/main/res/values/strings.xml @@ -85,6 +85,16 @@ 날짜 메모 + + 이번달 챙길 사람 + 챙김 완료 + 이번달은 챙길 사람이 없네요. + 챙김 기록하기 + 네트워크 에러가 발생했습니다. + 소중한 날 마음을 전해요 + 생일 축하 전해요 + 가볍게 안부인사 전해요 + 매일 매주 From b7146adcca0b73151c911bf2a92e1fdc881aeeb7 Mon Sep 17 00:00:00 2001 From: stopstone Date: Mon, 3 Nov 2025 16:28:09 +0900 Subject: [PATCH 17/32] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=88=EB=8B=AC?= =?UTF-8?q?=20=EC=B1=99=EA=B8=B8=20=EC=82=AC=EB=9E=8C=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EC=B1=99=EA=B9=80=20=EC=99=84=EB=A3=8C=20=EC=83=81=EB=8B=A8?= =?UTF-8?q?=20=ED=8C=A8=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 Spacer를 제거하여 레이아웃을 개선했습니다. - LazyColumn의 컨텐츠 패딩을 적용하여 상하단 여백을 조정했습니다. --- .../mothlyreminderall/MonthlyReminderAllScreen.kt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 5032b30d..64ef7df2 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box 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.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -54,8 +53,8 @@ fun MonthlyReminderAllRoute( is MonthlyReminderAllUIEvent.NetworkError -> { onShowErrorSnackBar( IllegalStateException( - context.getString(R.string.monthly_reminder_all_network_error) - ) + context.getString(R.string.monthly_reminder_all_network_error), + ), ) } @@ -125,10 +124,6 @@ internal fun MonthlyReminderAllScreen( .padding(horizontal = 20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - item { - Spacer(modifier = Modifier.height(16.dp)) - } - items( items = uiState.monthlyReminders, key = { "monthly_${it.friendId}" }, @@ -139,12 +134,10 @@ internal fun MonthlyReminderAllScreen( ) } - item { - Spacer(modifier = Modifier.size(16.dp)) - } - if (uiState.completedReminders.isNotEmpty()) { item { + Spacer(modifier = Modifier.size(16.dp)) + Text( text = stringResource(R.string.monthly_reminder_all_completed_section_title), style = NearTheme.typography.B2_14_BOLD, From 6e8b124201683e3b9a536fab80cac94349be3a4b Mon Sep 17 00:00:00 2001 From: stopstone Date: Mon, 3 Nov 2025 16:32:11 +0900 Subject: [PATCH 18/32] =?UTF-8?q?refactor:=20=EC=97=B0=EB=9D=BD=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20uiState=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MonthlyReminderAllScreen.kt | 3 ++- .../MonthlyReminderAllViewModel.kt | 19 ++++++++++--------- .../uistate/MonthlyReminderAllUIState.kt | 1 + 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 64ef7df2..b957f845 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -134,7 +134,7 @@ internal fun MonthlyReminderAllScreen( ) } - if (uiState.completedReminders.isNotEmpty()) { + if (uiState.hasCompletedReminders) { item { Spacer(modifier = Modifier.size(16.dp)) @@ -197,6 +197,7 @@ private fun MonthlyReminderAllScreenPreview() { daysUntilNextContact = "D+16", ), ), + hasCompletedReminders = true, ), ) } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index 8017442c..4a260145 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -77,17 +77,18 @@ class MonthlyReminderAllViewModel val completedFriendIds = completedList.map { it.friendId }.toSet() val filteredMonthlyList = monthlyList.filter { it.friendId !in completedFriendIds } - _uiState.update { - if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { - MonthlyReminderAllUIState.Empty - } else { - MonthlyReminderAllUIState.Success( - monthlyReminders = filteredMonthlyList, - completedReminders = completedList, - ) - } + _uiState.update { + if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { + MonthlyReminderAllUIState.Empty + } else { + MonthlyReminderAllUIState.Success( + monthlyReminders = filteredMonthlyList, + completedReminders = completedList, + hasCompletedReminders = completedList.isNotEmpty(), + ) } } + } fun onRecordFriendShip(friendId: String) { friendRepository diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt index 96acd1d7..e6151f63 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIState.kt @@ -10,5 +10,6 @@ sealed interface MonthlyReminderAllUIState { data class Success( val monthlyReminders: List, val completedReminders: List, + val hasCompletedReminders: Boolean, ) : MonthlyReminderAllUIState } From 8409fed15046d78125d05482b386a8bf34095f4a Mon Sep 17 00:00:00 2001 From: stopstone Date: Wed, 5 Nov 2025 12:52:52 +0900 Subject: [PATCH 19/32] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=88=EB=8B=AC?= =?UTF-8?q?=20=EC=B1=99=EA=B8=B8=20=EC=82=AC=EB=9E=8C=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EA=B0=84=EA=B2=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 리마인더 전체보기 화면의 카드 사이에 16dp의 간격을 추가했습니다. --- .../feature/mothlyreminderall/MonthlyReminderAllScreen.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index b957f845..51521e1f 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -122,12 +122,13 @@ internal fun MonthlyReminderAllScreen( Modifier .fillMaxSize() .padding(horizontal = 20.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), ) { items( items = uiState.monthlyReminders, key = { "monthly_${it.friendId}" }, ) { reminder -> + Spacer(modifier = Modifier.size(16.dp)) + MonthlyReminderFriendCard( reminder = reminder, onRecordClick = onRecordFriendShip, @@ -136,13 +137,14 @@ internal fun MonthlyReminderAllScreen( if (uiState.hasCompletedReminders) { item { - Spacer(modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.size(32.dp)) Text( text = stringResource(R.string.monthly_reminder_all_completed_section_title), style = NearTheme.typography.B2_14_BOLD, color = NearTheme.colors.BLACK_1A1A1A, ) + Spacer(modifier = Modifier.size(16.dp)) } items( From 3b8db78fb59b86e3bdbe315b264f98f96af3ad79 Mon Sep 17 00:00:00 2001 From: stopstone Date: Wed, 5 Nov 2025 21:17:00 +0900 Subject: [PATCH 20/32] =?UTF-8?q?feat:=20=EC=9D=B4=EB=B2=88=EB=8B=AC=20?= =?UTF-8?q?=EC=B1=99=EA=B8=B8=20=EC=82=AC=EB=9E=8C=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 리마인더 목록을 `nextContactAt`(다음 연락일) 기준으로 오름차순 정렬하여 표시하도록 수정했습니다. --- .../MonthlyReminderAllScreen.kt | 3 ++- .../MonthlyReminderAllViewModel.kt | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 51521e1f..25cf7cdb 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -1,7 +1,6 @@ package com.alarmy.near.presentation.feature.mothlyreminderall import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -152,6 +151,8 @@ internal fun MonthlyReminderAllScreen( key = { "completed_${it.friendId}" }, ) { reminder -> MonthlyReminderComplete(reminder = reminder) + + Spacer(modifier = Modifier.size(16.dp)) } } } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index 4a260145..93f227b2 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -47,7 +47,7 @@ class MonthlyReminderAllViewModel friendRepository .fetchMonthlyFriends() .onEach { monthlyFriends -> - val uiModels = convertToUIModels(monthlyFriends) + val uiModels = convertToUIModels(monthlyFriends).sortedBy { it.nextContactAt } _monthlyReminders.value = uiModels updateUIState() }.catch { exception -> @@ -77,18 +77,18 @@ class MonthlyReminderAllViewModel val completedFriendIds = completedList.map { it.friendId }.toSet() val filteredMonthlyList = monthlyList.filter { it.friendId !in completedFriendIds } - _uiState.update { - if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { - MonthlyReminderAllUIState.Empty - } else { - MonthlyReminderAllUIState.Success( - monthlyReminders = filteredMonthlyList, - completedReminders = completedList, - hasCompletedReminders = completedList.isNotEmpty(), - ) + _uiState.update { + if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { + MonthlyReminderAllUIState.Empty + } else { + MonthlyReminderAllUIState.Success( + monthlyReminders = filteredMonthlyList, + completedReminders = completedList, + hasCompletedReminders = completedList.isNotEmpty(), + ) + } } } - } fun onRecordFriendShip(friendId: String) { friendRepository @@ -99,7 +99,8 @@ class MonthlyReminderAllViewModel } val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } if (recordedFriend != null) { - _monthlyReminders.value = _monthlyReminders.value.filter { it.friendId != friendId } + _monthlyReminders.value = + _monthlyReminders.value.filter { it.friendId != friendId } _completedReminders.value = listOf(recordedFriend) + _completedReminders.value updateUIState() } From 3c61c80d1c20cd3f5b1b355f1be36db3853ca5c5 Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 15:54:58 +0900 Subject: [PATCH 21/32] =?UTF-8?q?refactor:=20=EC=9B=94=EB=B3=84=20?= =?UTF-8?q?=EB=A6=AC=EB=A7=88=EC=9D=B8=EB=8D=94=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=A1=9C=EB=94=A9=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `combine`을 사용하여 `fetchMonthlyFriends`와 `fetchMonthlyCompleteFriends` API 호출을 병렬로 처리하도록 수정했습니다. - 두 API의 응답을 모두 받은 후에 한 번에 UI 상태를 업데이트하여 데이터 로딩 중 발생할 수 있는 화면 깜빡임 현상을 개선했습니다. - API 응답을 함께 관리하기 위한 `MonthlyReminderCombinedData` data class를 추가했습니다. --- .../MonthlyReminderAllViewModel.kt | 53 +++++++++---------- .../model/MonthlyReminderCombinedData.kt | 8 +++ 2 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderCombinedData.kt diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index 93f227b2..c027cf74 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alarmy.near.data.repository.FriendRepository import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderCombinedData import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderTypeInfo import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIEvent @@ -14,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @@ -39,36 +41,31 @@ class MonthlyReminderAllViewModel private val _completedReminders = MutableStateFlow>(emptyList()) init { - fetchMonthlyFriends() - fetchMonthlyCompleteFriends() + fetchMonthlyReminders() } - private fun fetchMonthlyFriends() { - friendRepository - .fetchMonthlyFriends() - .onEach { monthlyFriends -> - val uiModels = convertToUIModels(monthlyFriends).sortedBy { it.nextContactAt } - _monthlyReminders.value = uiModels - updateUIState() - }.catch { exception -> - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) - } - }.launchIn(viewModelScope) - } - - private fun fetchMonthlyCompleteFriends() { - friendRepository - .fetchMonthlyCompleteFriends() - .onEach { monthlyFriends -> - val uiModels = convertToUIModels(monthlyFriends) - _completedReminders.value = uiModels - updateUIState() - }.catch { exception -> - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) - } - }.launchIn(viewModelScope) + private fun fetchMonthlyReminders() { + combine( + friendRepository.fetchMonthlyFriends(), + friendRepository.fetchMonthlyCompleteFriends(), + ) { monthlyFriends, completeFriends -> + MonthlyReminderCombinedData( + monthlyFriends = monthlyFriends, + completeFriends = completeFriends, + ) + }.onEach { data -> + val monthlyUIModels = + convertToUIModels(data.monthlyFriends).sortedBy { it.nextContactAt } + _monthlyReminders.value = monthlyUIModels + val completedUIModels = convertToUIModels(data.completeFriends) + _completedReminders.value = completedUIModels + // 두 데이터가 모두 준비된 후 UI 상태 업데이트 + updateUIState() + }.catch { exception -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) + } + }.launchIn(viewModelScope) } private fun updateUIState() { diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderCombinedData.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderCombinedData.kt new file mode 100644 index 00000000..2314f711 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderCombinedData.kt @@ -0,0 +1,8 @@ +package com.alarmy.near.presentation.feature.mothlyreminderall.model + +import com.alarmy.near.model.monthly.MonthlyFriend + +data class MonthlyReminderCombinedData( + val monthlyFriends: List, + val completeFriends: List, +) From 3cd540ba887601b59c3400824f82d044e769a33c Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 15:59:59 +0900 Subject: [PATCH 22/32] =?UTF-8?q?refactor:=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20"=EC=A0=84=EC=B2=B4=20=EB=B3=B4=EA=B8=B0"=20?= =?UTF-8?q?=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EB=A6=AC=EC=86=8C=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarmy/near/presentation/feature/home/HomeScreen.kt | 2 +- Near/app/src/main/res/values/strings.xml | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt index f76d753e..65d1cc24 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt @@ -400,7 +400,7 @@ fun MonthlyReminderFriendsViewAll( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "전체보기", + text = stringResource(R.string.home_monthly_friends_all), style = NearTheme.typography.B2_14_MEDIUM, color = NearTheme.colors.WHITE_FFFFFF, modifier = Modifier.alpha(0.8f), diff --git a/Near/app/src/main/res/values/strings.xml b/Near/app/src/main/res/values/strings.xml index 0022b7e5..63b8b688 100644 --- a/Near/app/src/main/res/values/strings.xml +++ b/Near/app/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ 가까워 지고 싶은 사람을\n추가해보세요. 사람 추가 사람 추가 + 전체 보기 전화걸기 @@ -107,7 +108,7 @@ 화면을 나가면 \n수정 내용은 저장되지 않아요. 확인 취소 - + 수정을 완료하시겠습니까? 저장 @@ -143,7 +144,7 @@ 편하게 의견을 남겨주세요. 그만두기 탈퇴하기 - + 탈퇴 시 계정 및 이용 내역이\n모두 삭제되며, 복구가 불가능합니다.\n정말 탈퇴하시겠습니까? 취소 @@ -156,7 +157,7 @@ 서비스 이용약관 개인정보 수집 및 이용동의 개인정보 처리방침 - + 서비스 약관 동의 약관 전체 동의 From 0f0ec163eea978c46bf9ac3dc34753874d241bf7 Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:02:08 +0900 Subject: [PATCH 23/32] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mothlyreminderall/MonthlyReminderAllScreen.kt | 10 ++-------- .../mothlyreminderall/MonthlyReminderAllViewModel.kt | 5 +++-- .../uistate/MonthlyReminderAllUIEvent.kt | 5 +++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 25cf7cdb..f95ffdd9 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -41,7 +40,6 @@ fun MonthlyReminderAllRoute( onShowErrorSnackBar: (throwable: Throwable?) -> Unit, onNavigateBack: () -> Unit = {}, ) { - val context = LocalContext.current val uiState = viewModel.uiState.collectAsStateWithLifecycle() val recordSuccessDialogState = remember { mutableStateOf(false) } @@ -49,12 +47,8 @@ fun MonthlyReminderAllRoute( launch { viewModel.uiEvent.collect { event -> when (event) { - is MonthlyReminderAllUIEvent.NetworkError -> { - onShowErrorSnackBar( - IllegalStateException( - context.getString(R.string.monthly_reminder_all_network_error), - ), - ) + is MonthlyReminderAllUIEvent.ShowError -> { + onShowErrorSnackBar(event.throwable) } is MonthlyReminderAllUIEvent.RecordFriendShipSuccess -> { diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index c027cf74..b29d6dd5 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -63,7 +63,7 @@ class MonthlyReminderAllViewModel updateUIState() }.catch { exception -> viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) + _uiEvent.send(MonthlyReminderAllUIEvent.ShowError(exception)) } }.launchIn(viewModelScope) } @@ -102,8 +102,9 @@ class MonthlyReminderAllViewModel updateUIState() } }.catch { exception -> + // 원본 exception을 전달하여 스택 트레이스와 디버깅 정보 유지 viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.NetworkError) + _uiEvent.send(MonthlyReminderAllUIEvent.ShowError(exception)) } }.launchIn(viewModelScope) } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt index 494e6a56..889c234a 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/uistate/MonthlyReminderAllUIEvent.kt @@ -1,8 +1,9 @@ package com.alarmy.near.presentation.feature.mothlyreminderall.uistate sealed interface MonthlyReminderAllUIEvent { - data object NetworkError : MonthlyReminderAllUIEvent + data class ShowError( + val throwable: Throwable?, + ) : MonthlyReminderAllUIEvent data object RecordFriendShipSuccess : MonthlyReminderAllUIEvent } - From de9c02c18034823e4cd7ea139b52ba18cefade71 Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:06:12 +0900 Subject: [PATCH 24/32] =?UTF-8?q?refactor:=20Flow=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반복적으로 사용되는 Flow의 `catch` 블록 내 에러 처리 로직을 `handleError` 확장 함수로 분리하여 공통화했습니다. - `MonthlyReminderAllViewModel` 내 `fetchMonthlyReminders`와 `onRecordFriendShip` 함수의 에러 처리 로직에 `handleError`를 적용하여 코드 중복을 제거하고 가독성을 개선했습니다. --- .../MonthlyReminderAllViewModel.kt | 85 +++++++++---------- .../near/utils/extensions/FlowExtensions.kt | 15 ++++ 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index b29d6dd5..66461486 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -9,12 +9,12 @@ import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyRemin import com.alarmy.near.presentation.feature.mothlyreminderall.model.MonthlyReminderUIModel import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIEvent import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyReminderAllUIState +import com.alarmy.near.utils.extensions.handleError import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -44,29 +44,27 @@ class MonthlyReminderAllViewModel fetchMonthlyReminders() } - private fun fetchMonthlyReminders() { - combine( - friendRepository.fetchMonthlyFriends(), - friendRepository.fetchMonthlyCompleteFriends(), - ) { monthlyFriends, completeFriends -> - MonthlyReminderCombinedData( - monthlyFriends = monthlyFriends, - completeFriends = completeFriends, - ) - }.onEach { data -> - val monthlyUIModels = - convertToUIModels(data.monthlyFriends).sortedBy { it.nextContactAt } - _monthlyReminders.value = monthlyUIModels - val completedUIModels = convertToUIModels(data.completeFriends) - _completedReminders.value = completedUIModels - // 두 데이터가 모두 준비된 후 UI 상태 업데이트 - updateUIState() - }.catch { exception -> - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.ShowError(exception)) - } - }.launchIn(viewModelScope) - } + private fun fetchMonthlyReminders() { + combine( + friendRepository.fetchMonthlyFriends(), + friendRepository.fetchMonthlyCompleteFriends(), + ) { monthlyFriends, completeFriends -> + MonthlyReminderCombinedData( + monthlyFriends = monthlyFriends, + completeFriends = completeFriends, + ) + }.onEach { data -> + val monthlyUIModels = + convertToUIModels(data.monthlyFriends).sortedBy { it.nextContactAt } + _monthlyReminders.value = monthlyUIModels + val completedUIModels = convertToUIModels(data.completeFriends) + _completedReminders.value = completedUIModels + // 두 데이터가 모두 준비된 후 UI 상태 업데이트 + updateUIState() + }.handleError(viewModelScope, _uiEvent) { exception -> + MonthlyReminderAllUIEvent.ShowError(exception) + }.launchIn(viewModelScope) + } private fun updateUIState() { val monthlyList = _monthlyReminders.value @@ -87,27 +85,24 @@ class MonthlyReminderAllViewModel } } - fun onRecordFriendShip(friendId: String) { - friendRepository - .recordContact(friendId) - .onEach { _ -> - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) - } - val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } - if (recordedFriend != null) { - _monthlyReminders.value = - _monthlyReminders.value.filter { it.friendId != friendId } - _completedReminders.value = listOf(recordedFriend) + _completedReminders.value - updateUIState() - } - }.catch { exception -> - // 원본 exception을 전달하여 스택 트레이스와 디버깅 정보 유지 - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.ShowError(exception)) - } - }.launchIn(viewModelScope) - } + fun onRecordFriendShip(friendId: String) { + friendRepository + .recordContact(friendId) + .onEach { _ -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) + } + val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } + if (recordedFriend != null) { + _monthlyReminders.value = + _monthlyReminders.value.filter { it.friendId != friendId } + _completedReminders.value = listOf(recordedFriend) + _completedReminders.value + updateUIState() + } + }.handleError(viewModelScope, _uiEvent) { exception -> + MonthlyReminderAllUIEvent.ShowError(exception) + }.launchIn(viewModelScope) + } private fun convertToUIModels(monthlyFriends: List): List { val today = LocalDate.now() diff --git a/Near/app/src/main/java/com/alarmy/near/utils/extensions/FlowExtensions.kt b/Near/app/src/main/java/com/alarmy/near/utils/extensions/FlowExtensions.kt index 3bf0e2d7..51a1260e 100644 --- a/Near/app/src/main/java/com/alarmy/near/utils/extensions/FlowExtensions.kt +++ b/Near/app/src/main/java/com/alarmy/near/utils/extensions/FlowExtensions.kt @@ -1,9 +1,24 @@ package com.alarmy.near.utils.extensions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch inline fun apiCallFlow(crossinline apiCall: suspend () -> T): Flow = flow { emit(apiCall()) } + +fun Flow.handleError( + scope: CoroutineScope, + eventChannel: Channel, + createErrorEvent: (Throwable) -> E, +): Flow = + catch { exception -> + scope.launch { + eventChannel.send(createErrorEvent(exception)) + } + } From 42fabc6e406c631d86c197610bc22af7f08923d1 Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:10:00 +0900 Subject: [PATCH 25/32] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=83=89=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `RecordSuccessDialog`에서 사용하던 하드코딩된 색상 값을 `NearTheme`의 색상으로 변경했습니다. - `NearColor`에 `BLACK_222222` 색상을 추가했습니다. --- .../mothlyreminderall/components/RecordSuccessDialog.kt | 3 +-- .../main/java/com/alarmy/near/presentation/ui/theme/Color.kt | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt index fedd5db9..c17fd226 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/RecordSuccessDialog.kt @@ -13,7 +13,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -50,7 +49,7 @@ fun RecordSuccessDialog(onDismiss: () -> Unit) { Text( stringResource(R.string.friend_profile_info_contact_success_text), style = NearTheme.typography.B1_16_BOLD, - color = Color(0xff222222), + color = NearTheme.colors.BLACK_222222, ) } } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt index 45d620b4..1cc38760 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.Color object NearColorPallete { val BLACK_1A1A1A = Color(0xFF1A1A1A) + val BLACK_222222 = Color(0xFF222222) val WHITE_FFFFFF = Color(0xFFFFFFFF) val GRAY01_888888 = Color(0xFF888888) val GRAY02_B7B7B7 = Color(0xFFB7B7B7) @@ -25,6 +26,7 @@ object NearColorPallete { @Suppress("PropertyName") data class NearColor( val BLACK_1A1A1A: Color = NearColorPallete.BLACK_1A1A1A, + val BLACK_222222: Color = NearColorPallete.BLACK_222222, val WHITE_FFFFFF: Color = NearColorPallete.WHITE_FFFFFF, val GRAY01_888888: Color = NearColorPallete.GRAY01_888888, val GRAY02_B7B7B7: Color = NearColorPallete.GRAY02_B7B7B7, From 61d61ac6e4054e346a96cdc5240600eaa3468cdb Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:17:44 +0900 Subject: [PATCH 26/32] =?UTF-8?q?chore:=20MonthlyReminderFriendCard.kt=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MonthlyReminderFriendCard.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index 526bcafc..25236792 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -79,15 +79,15 @@ fun MonthlyReminderFriendCard( color = NearTheme.colors.BLACK_1A1A1A, ) - Spacer(modifier = Modifier.size(6.dp)) + Spacer(modifier = Modifier.size(6.dp)) - Text( - text = stringResource(reminder.descriptionRes), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = NearTheme.typography.B2_14_MEDIUM, - color = NearTheme.colors.GRAY01_888888, - ) + Text( + text = stringResource(reminder.descriptionRes), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY01_888888, + ) } Spacer(modifier = Modifier.size(10.dp)) From 31a0e66df64045b97bb2bdcbc059e5fd6d184ebe Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:20:04 +0900 Subject: [PATCH 27/32] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=B2=88=EB=8B=AC?= =?UTF-8?q?=20=EC=B1=99=EA=B8=B8=EC=82=AC=EB=9E=8C=20=ED=95=98=EB=8B=A8=20?= =?UTF-8?q?=ED=8C=A8=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/mothlyreminderall/MonthlyReminderAllScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index f95ffdd9..4302fd1b 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -2,6 +2,7 @@ package com.alarmy.near.presentation.feature.mothlyreminderall import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -115,6 +116,7 @@ internal fun MonthlyReminderAllScreen( Modifier .fillMaxSize() .padding(horizontal = 20.dp), + contentPadding = PaddingValues(bottom = 80.dp), ) { items( items = uiState.monthlyReminders, @@ -150,8 +152,6 @@ internal fun MonthlyReminderAllScreen( } } } - - Spacer(modifier = Modifier.size(80.dp)) } } } From 1672ffaa89127b82438d2239321047f587a9dec9 Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:46:38 +0900 Subject: [PATCH 28/32] =?UTF-8?q?chore:=20"D-day"=EB=A5=BC=20"D-DAY"?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 연락 주기까지 남은 날짜를 표시하는 문자열을 "D-day"에서 "D-DAY"로 변경했습니다. --- .../main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt index ac4fbb1b..0006ed90 100644 --- a/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt +++ b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt @@ -13,7 +13,7 @@ data class MonthlyFriend( fun daysUntilNextContact(today: LocalDate): String { val daysBetween = getDaysBetween(today) return when { - daysBetween == 0L -> "D-day" + daysBetween == 0L -> "D-DAY" daysBetween > 0L -> "D-$daysBetween" else -> "D+${-daysBetween}" // 과거 날짜 } From c9c432658888e71dfa0c6cbc104cb66cd69be28c Mon Sep 17 00:00:00 2001 From: stopstone Date: Fri, 7 Nov 2025 16:48:40 +0900 Subject: [PATCH 29/32] =?UTF-8?q?feat:=20=EC=97=B0=EB=9D=BD=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=98=A4=EB=8A=98=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?D-day=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `MonthlyReminderUIModel`에 연락일이 오늘인지 여부를 나타내는 `isToday` 프로퍼티를 추가했습니다. - `isToday` 값에 따라 D-day 텍스트 스타일(파란색, 굵은 글씨)이 적용되도록 `MonthlyReminderFriendCard`를 수정했습니다. - 월간 리마인더 목록에서 중복된 친구 데이터가 표시되지 않도록 `distinctBy`를 사용하여 필터링 로직을 추가했습니다. --- .../MonthlyReminderAllScreen.kt | 5 +- .../MonthlyReminderAllViewModel.kt | 90 ++++++++++--------- .../components/MonthlyReminderComplete.kt | 1 + .../components/MonthlyReminderFriendCard.kt | 3 +- .../model/MonthlyReminderUIModel.kt | 1 + 5 files changed, 57 insertions(+), 43 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 4302fd1b..9080ceec 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -173,6 +173,7 @@ private fun MonthlyReminderAllScreenPreview() { descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-11-05", daysUntilNextContact = "D-4", + isToday = false, ), MonthlyReminderUIModel( friendId = "2", @@ -180,7 +181,8 @@ private fun MonthlyReminderAllScreenPreview() { imageRes = R.drawable.icon_visual_mail, descriptionRes = R.string.monthly_reminder_all_type_message_description, nextContactAt = "2025-11-01", - daysUntilNextContact = "D-DAY", + daysUntilNextContact = "D-day", + isToday = true, ), ), completedReminders = @@ -192,6 +194,7 @@ private fun MonthlyReminderAllScreenPreview() { descriptionRes = R.string.monthly_reminder_all_type_anniversary_description, nextContactAt = "2025-10-15", daysUntilNextContact = "D+16", + isToday = false, ), ), hasCompletedReminders = true, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index 66461486..df789f29 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -44,33 +44,40 @@ class MonthlyReminderAllViewModel fetchMonthlyReminders() } - private fun fetchMonthlyReminders() { - combine( - friendRepository.fetchMonthlyFriends(), - friendRepository.fetchMonthlyCompleteFriends(), - ) { monthlyFriends, completeFriends -> - MonthlyReminderCombinedData( - monthlyFriends = monthlyFriends, - completeFriends = completeFriends, - ) - }.onEach { data -> - val monthlyUIModels = - convertToUIModels(data.monthlyFriends).sortedBy { it.nextContactAt } - _monthlyReminders.value = monthlyUIModels - val completedUIModels = convertToUIModels(data.completeFriends) - _completedReminders.value = completedUIModels - // 두 데이터가 모두 준비된 후 UI 상태 업데이트 - updateUIState() - }.handleError(viewModelScope, _uiEvent) { exception -> - MonthlyReminderAllUIEvent.ShowError(exception) - }.launchIn(viewModelScope) - } + private fun fetchMonthlyReminders() { + combine( + friendRepository.fetchMonthlyFriends(), + friendRepository.fetchMonthlyCompleteFriends(), + ) { monthlyFriends, completeFriends -> + MonthlyReminderCombinedData( + monthlyFriends = monthlyFriends, + completeFriends = completeFriends, + ) + }.onEach { data -> + val monthlyUIModels = + convertToUIModels(data.monthlyFriends) + .distinctBy { it.friendId } + .sortedBy { it.nextContactAt } + _monthlyReminders.value = monthlyUIModels + val completedUIModels = + convertToUIModels(data.completeFriends) + .distinctBy { it.friendId } + _completedReminders.value = completedUIModels + // 두 데이터가 모두 준비된 후 UI 상태 업데이트 + updateUIState() + }.handleError(viewModelScope, _uiEvent) { exception -> + MonthlyReminderAllUIEvent.ShowError(exception) + }.launchIn(viewModelScope) + } private fun updateUIState() { val monthlyList = _monthlyReminders.value val completedList = _completedReminders.value val completedFriendIds = completedList.map { it.friendId }.toSet() - val filteredMonthlyList = monthlyList.filter { it.friendId !in completedFriendIds } + val filteredMonthlyList = + monthlyList + .filter { it.friendId !in completedFriendIds } + .distinctBy { it.friendId } _uiState.update { if (filteredMonthlyList.isEmpty() && completedList.isEmpty()) { @@ -78,31 +85,31 @@ class MonthlyReminderAllViewModel } else { MonthlyReminderAllUIState.Success( monthlyReminders = filteredMonthlyList, - completedReminders = completedList, + completedReminders = completedList.distinctBy { it.friendId }, hasCompletedReminders = completedList.isNotEmpty(), ) } } } - fun onRecordFriendShip(friendId: String) { - friendRepository - .recordContact(friendId) - .onEach { _ -> - viewModelScope.launch { - _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) - } - val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } - if (recordedFriend != null) { - _monthlyReminders.value = - _monthlyReminders.value.filter { it.friendId != friendId } - _completedReminders.value = listOf(recordedFriend) + _completedReminders.value - updateUIState() - } - }.handleError(viewModelScope, _uiEvent) { exception -> - MonthlyReminderAllUIEvent.ShowError(exception) - }.launchIn(viewModelScope) - } + fun onRecordFriendShip(friendId: String) { + friendRepository + .recordContact(friendId) + .onEach { _ -> + viewModelScope.launch { + _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) + } + val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } + if (recordedFriend != null) { + _monthlyReminders.value = + _monthlyReminders.value.filter { it.friendId != friendId } + _completedReminders.value = listOf(recordedFriend) + _completedReminders.value + updateUIState() + } + }.handleError(viewModelScope, _uiEvent) { exception -> + MonthlyReminderAllUIEvent.ShowError(exception) + }.launchIn(viewModelScope) + } private fun convertToUIModels(monthlyFriends: List): List { val today = LocalDate.now() @@ -116,6 +123,7 @@ class MonthlyReminderAllViewModel descriptionRes = typeInfo.descriptionRes, nextContactAt = friend.nextContactAt, daysUntilNextContact = friend.daysUntilNextContact(today), + isToday = friend.isNextContactDay(today), ) } catch (e: Exception) { null diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt index 8b610039..95a4a0d3 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderComplete.kt @@ -79,6 +79,7 @@ fun MonthlyReminderCompletePreview() { descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-03-20", daysUntilNextContact = "D-9", + isToday = false, ), ) } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt index 25236792..1d33184e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/components/MonthlyReminderFriendCard.kt @@ -93,7 +93,7 @@ fun MonthlyReminderFriendCard( Spacer(modifier = Modifier.size(10.dp)) val (textStyle, textColor) = - if (reminder.daysUntilNextContact == "D-DAY") { + if (reminder.isToday) { NearTheme.typography.B2_14_BOLD to NearTheme.colors.BLUE01_5AA2E9 } else { NearTheme.typography.B2_14_MEDIUM to NearTheme.colors.GRAY01_888888 @@ -140,6 +140,7 @@ fun MonthlyReminderFriendCardPreview() { descriptionRes = R.string.monthly_reminder_all_type_birthday_description, nextContactAt = "2025-11-05", daysUntilNextContact = "D-DAY", + isToday = true, ), ) } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt index 1a8b4ad7..74f82d6e 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/model/MonthlyReminderUIModel.kt @@ -10,5 +10,6 @@ data class MonthlyReminderUIModel( @StringRes val descriptionRes: Int, val nextContactAt: String, val daysUntilNextContact: String, + val isToday: Boolean, ) From be854844d246c437d3709b4bee00c8592b96c9e4 Mon Sep 17 00:00:00 2001 From: stopstone Date: Tue, 11 Nov 2025 00:43:28 +0900 Subject: [PATCH 30/32] =?UTF-8?q?refactor:=20MonthlyReminderAllScreen?= =?UTF-8?q?=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20launch=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LaunchedEffect` 내에서 `collect`를 사용할 때 불필요한 `launch` 블록을 제거하여 코드를 간소화했습니다. --- .../MonthlyReminderAllScreen.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt index 9080ceec..c5ac6fae 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllScreen.kt @@ -33,7 +33,6 @@ import com.alarmy.near.presentation.feature.mothlyreminderall.uistate.MonthlyRem import com.alarmy.near.presentation.ui.component.NearFrame import com.alarmy.near.presentation.ui.component.appbar.NearTopAppbar import com.alarmy.near.presentation.ui.theme.NearTheme -import kotlinx.coroutines.launch @Composable fun MonthlyReminderAllRoute( @@ -44,17 +43,15 @@ fun MonthlyReminderAllRoute( val uiState = viewModel.uiState.collectAsStateWithLifecycle() val recordSuccessDialogState = remember { mutableStateOf(false) } - LaunchedEffect(viewModel.uiEvent) { - launch { - viewModel.uiEvent.collect { event -> - when (event) { - is MonthlyReminderAllUIEvent.ShowError -> { - onShowErrorSnackBar(event.throwable) - } + LaunchedEffect(Unit) { + viewModel.uiEvent.collect { event -> + when (event) { + is MonthlyReminderAllUIEvent.ShowError -> { + onShowErrorSnackBar(event.throwable) + } - is MonthlyReminderAllUIEvent.RecordFriendShipSuccess -> { - recordSuccessDialogState.value = true - } + is MonthlyReminderAllUIEvent.RecordFriendShipSuccess -> { + recordSuccessDialogState.value = true } } } From 186e13b89e42c8250984d0b8f3e213ed172523ee Mon Sep 17 00:00:00 2001 From: stopstone Date: Tue, 11 Nov 2025 00:44:26 +0900 Subject: [PATCH 31/32] =?UTF-8?q?refactor:=20MonthlyReminderAllViewModel?= =?UTF-8?q?=20=EB=82=B4=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20private?= =?UTF-8?q?=20backing=20property=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `_monthlyReminders`와 `_completedReminders`에 대한 불필요한 `_` 접두사를 제거하여 코드를 간소화했습니다. --- .../MonthlyReminderAllViewModel.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index df789f29..d31e72d8 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -37,8 +37,8 @@ class MonthlyReminderAllViewModel MutableStateFlow(MonthlyReminderAllUIState.Loading) val uiState: StateFlow = _uiState.asStateFlow() - private val _monthlyReminders = MutableStateFlow>(emptyList()) - private val _completedReminders = MutableStateFlow>(emptyList()) + private val monthlyReminders = MutableStateFlow>(emptyList()) + private val completedReminders = MutableStateFlow>(emptyList()) init { fetchMonthlyReminders() @@ -58,11 +58,11 @@ class MonthlyReminderAllViewModel convertToUIModels(data.monthlyFriends) .distinctBy { it.friendId } .sortedBy { it.nextContactAt } - _monthlyReminders.value = monthlyUIModels + monthlyReminders.value = monthlyUIModels val completedUIModels = convertToUIModels(data.completeFriends) .distinctBy { it.friendId } - _completedReminders.value = completedUIModels + completedReminders.value = completedUIModels // 두 데이터가 모두 준비된 후 UI 상태 업데이트 updateUIState() }.handleError(viewModelScope, _uiEvent) { exception -> @@ -71,8 +71,8 @@ class MonthlyReminderAllViewModel } private fun updateUIState() { - val monthlyList = _monthlyReminders.value - val completedList = _completedReminders.value + val monthlyList = monthlyReminders.value + val completedList = completedReminders.value val completedFriendIds = completedList.map { it.friendId }.toSet() val filteredMonthlyList = monthlyList @@ -99,11 +99,11 @@ class MonthlyReminderAllViewModel viewModelScope.launch { _uiEvent.send(MonthlyReminderAllUIEvent.RecordFriendShipSuccess) } - val recordedFriend = _monthlyReminders.value.find { it.friendId == friendId } + val recordedFriend = monthlyReminders.value.find { it.friendId == friendId } if (recordedFriend != null) { - _monthlyReminders.value = - _monthlyReminders.value.filter { it.friendId != friendId } - _completedReminders.value = listOf(recordedFriend) + _completedReminders.value + monthlyReminders.value = + monthlyReminders.value.filter { it.friendId != friendId } + completedReminders.value = listOf(recordedFriend) + completedReminders.value updateUIState() } }.handleError(viewModelScope, _uiEvent) { exception -> From 49e421ed29b9231dd84f89e12aecda0b564a556d Mon Sep 17 00:00:00 2001 From: stopstone Date: Tue, 11 Nov 2025 00:47:55 +0900 Subject: [PATCH 32/32] =?UTF-8?q?refactor:=20updateUIState=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=9D=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `updateUIState` 함수의 역할을 더 명확하게 나타내기 위해 `combineRemindersToUIState`로 이름을 변경했습니다. --- .../mothlyreminderall/MonthlyReminderAllViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt index d31e72d8..08aa4205 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/mothlyreminderall/MonthlyReminderAllViewModel.kt @@ -64,13 +64,13 @@ class MonthlyReminderAllViewModel .distinctBy { it.friendId } completedReminders.value = completedUIModels // 두 데이터가 모두 준비된 후 UI 상태 업데이트 - updateUIState() + combineRemindersToUIState() }.handleError(viewModelScope, _uiEvent) { exception -> MonthlyReminderAllUIEvent.ShowError(exception) }.launchIn(viewModelScope) } - private fun updateUIState() { + private fun combineRemindersToUIState() { val monthlyList = monthlyReminders.value val completedList = completedReminders.value val completedFriendIds = completedList.map { it.friendId }.toSet() @@ -104,7 +104,7 @@ class MonthlyReminderAllViewModel monthlyReminders.value = monthlyReminders.value.filter { it.friendId != friendId } completedReminders.value = listOf(recordedFriend) + completedReminders.value - updateUIState() + combineRemindersToUIState() } }.handleError(viewModelScope, _uiEvent) { exception -> MonthlyReminderAllUIEvent.ShowError(exception)