From 9d4645d90d9861ac0b0c51779b5fc0e12ae523bf Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sat, 23 Aug 2025 14:02:12 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20monthlyFriends=20fetch=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarmy/near/data/mapper/MonthlyFriendMapper.kt | 12 ++++++++++++ .../near/data/repository/DefaultFriendRepository.kt | 12 +++++++++++- .../alarmy/near/data/repository/FriendRepository.kt | 3 +++ .../java/com/alarmy/near/model/MonthlyFriend.kt | 8 ++++++++ .../near/network/response/MonthlyFriendEntity.kt | 11 +++++++++++ .../alarmy/near/network/service/FriendService.kt | 4 ++++ .../near/presentation/feature/home/HomeScreen.kt | 3 ++- .../near/presentation/feature/home/HomeViewModel.kt | 13 ++++++++++++- 8 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/network/response/MonthlyFriendEntity.kt diff --git a/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt b/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt new file mode 100644 index 00000000..a8b8b7f3 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt @@ -0,0 +1,12 @@ +package com.alarmy.near.data.mapper + +import com.alarmy.near.model.MonthlyFriend +import com.alarmy.near.network.response.MonthlyFriendEntity + +fun MonthlyFriendEntity.toModel(): MonthlyFriend = + MonthlyFriend( + friendId = friendId, + name = name, + type = type, + nextContactAt = nextContactAt, + ) 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 dd155182..a67ab989 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 @@ -2,6 +2,7 @@ package com.alarmy.near.data.repository import com.alarmy.near.data.mapper.toModel import com.alarmy.near.model.Friend +import com.alarmy.near.model.MonthlyFriend import com.alarmy.near.network.service.FriendService import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -20,4 +21,13 @@ class DefaultFriendRepository }, ) } - } + + override fun fetchMonthlyFriends(): Flow> = + flow { + emit( + friendService.fetchMonthlyFriends().map { + it.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 b7e76baa..8f08c8ff 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 @@ -1,8 +1,11 @@ package com.alarmy.near.data.repository import com.alarmy.near.model.Friend +import com.alarmy.near.model.MonthlyFriend import kotlinx.coroutines.flow.Flow interface FriendRepository { fun fetchFriends(): Flow> + + fun fetchMonthlyFriends(): Flow> } diff --git a/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt b/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt new file mode 100644 index 00000000..060bcdac --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt @@ -0,0 +1,8 @@ +package com.alarmy.near.model + +data class MonthlyFriend( + val friendId: String, + val name: String, + val type: String, + val nextContactAt: String, +) diff --git a/Near/app/src/main/java/com/alarmy/near/network/response/MonthlyFriendEntity.kt b/Near/app/src/main/java/com/alarmy/near/network/response/MonthlyFriendEntity.kt new file mode 100644 index 00000000..e60feaff --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/network/response/MonthlyFriendEntity.kt @@ -0,0 +1,11 @@ +package com.alarmy.near.network.response + +import kotlinx.serialization.Serializable + +@Serializable +data class MonthlyFriendEntity( + val friendId: String, + val name: String, + val type: String, + val nextContactAt: String, +) 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 a412dadc..ed5eaa8c 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 @@ -1,9 +1,13 @@ package com.alarmy.near.network.service import com.alarmy.near.network.response.FriendEntity +import com.alarmy.near.network.response.MonthlyFriendEntity import retrofit2.http.GET interface FriendService { @GET("/friend/list") suspend fun fetchFriends(): List + + @GET("friends/monthly") + suspend fun fetchMonthlyFriends(): List } 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 56fa7f29..e0088986 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 @@ -67,7 +67,8 @@ internal fun HomeRoute( onAlarmClick: () -> Unit = {}, onMyPageClick: () -> Unit = {}, ) { - val uiState = viewModel.friendsFlow.collectAsStateWithLifecycle() + val friends = viewModel.friendsFlow.collectAsStateWithLifecycle() + val monthlyFriends = viewModel.monthlyFriendFlow.collectAsStateWithLifecycle() HomeScreen( onContactClick = {}, onAlarmClick = {}, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt index aba76bc8..d468297c 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.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.Friend +import com.alarmy.near.model.MonthlyFriend import com.alarmy.near.presentation.feature.home.model.HomeUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -17,7 +18,7 @@ import javax.inject.Inject class HomeViewModel @Inject constructor( - private val friendRepository: FriendRepository, + friendRepository: FriendRepository, ) : ViewModel() { // Example: 여러번 초기화되는 StateFlow private val _uiStateFlow = MutableStateFlow(HomeUiState.Loading) @@ -32,6 +33,16 @@ class HomeViewModel initialValue = emptyList(), ) + val monthlyFriendFlow: + StateFlow> = + friendRepository + .fetchMonthlyFriends() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList(), + ) + fun removeContact(id: Long) { // contactRepository.removeContact(id) } From 42ea15942f04021c73ae3c43b80f47b5cabb4322 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 15:17:24 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20friend=20list=20data=20-=20ui=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarmy/near/data/mapper/FriendMapper.kt | 27 +++++++------ .../repository/DefaultFriendRepository.kt | 7 ++-- .../near/data/repository/FriendRepository.kt | 3 +- .../com/alarmy/near/model/ContactSummary.kt | 22 ----------- .../com/alarmy/near/model/FriendSummary.kt | 13 +++++++ .../near/network/service/FriendService.kt | 2 +- .../presentation/feature/home/HomeScreen.kt | 23 ++++------- .../feature/home/HomeViewModel.kt | 5 ++- .../feature/home/component/ContactItem.kt | 33 ++++++++-------- .../feature/home/component/MyContacts.kt | 38 +++++++++---------- 10 files changed, 82 insertions(+), 91 deletions(-) delete mode 100644 Near/app/src/main/java/com/alarmy/near/model/ContactSummary.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/model/FriendSummary.kt diff --git a/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendMapper.kt b/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendMapper.kt index 4a2b19d9..fcfc70c5 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendMapper.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendMapper.kt @@ -1,16 +1,21 @@ package com.alarmy.near.data.mapper -import com.alarmy.near.model.Friend +import com.alarmy.near.model.ContactFrequency +import com.alarmy.near.model.FriendSummary import com.alarmy.near.network.response.FriendEntity -fun FriendEntity.toModel(): Friend = - Friend( - friendId = friendId, - position = position, - source = source, +fun FriendEntity.toModel(): FriendSummary = + FriendSummary( + id = friendId, name = name, - imageUrl = imageUrl, - fileName = fileName, - checkRate = checkRate, - lastContactAt = lastContactAt, - ) + profileImageUrl = imageUrl, + lastContactedAt = lastContactAt, + isContacted = true, + contactFrequency = + when (checkRate) { + in 0..29 -> ContactFrequency.LOW + in 30..69 -> ContactFrequency.MIDDLE + in 70..100 -> ContactFrequency.HIGH + else -> ContactFrequency.LOW + }, + ) 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 a67ab989..bc7b3613 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 @@ -2,6 +2,7 @@ package com.alarmy.near.data.repository import com.alarmy.near.data.mapper.toModel import com.alarmy.near.model.Friend +import com.alarmy.near.model.FriendSummary import com.alarmy.near.model.MonthlyFriend import com.alarmy.near.network.service.FriendService import kotlinx.coroutines.flow.Flow @@ -13,7 +14,7 @@ class DefaultFriendRepository constructor( private val friendService: FriendService, ) : FriendRepository { - override fun fetchFriends(): Flow> = + override fun fetchFriends(): Flow> = flow { emit( friendService.fetchFriends().map { @@ -29,5 +30,5 @@ class DefaultFriendRepository it.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 8f08c8ff..727652f6 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 @@ -1,11 +1,12 @@ package com.alarmy.near.data.repository import com.alarmy.near.model.Friend +import com.alarmy.near.model.FriendSummary import com.alarmy.near.model.MonthlyFriend import kotlinx.coroutines.flow.Flow interface FriendRepository { - fun fetchFriends(): Flow> + fun fetchFriends(): Flow> fun fetchMonthlyFriends(): Flow> } diff --git a/Near/app/src/main/java/com/alarmy/near/model/ContactSummary.kt b/Near/app/src/main/java/com/alarmy/near/model/ContactSummary.kt deleted file mode 100644 index 2a98ee89..00000000 --- a/Near/app/src/main/java/com/alarmy/near/model/ContactSummary.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.alarmy.near.model - -import androidx.compose.runtime.Immutable -import java.time.LocalDate -import java.time.format.DateTimeFormatter - -@Immutable -data class ContactSummary( - val id: String, - val name: String, - val profileImageUrl: String, - val lastContactedAt: LocalDate, - val isContacted: Boolean, - val contactFrequency: ContactFrequency, -) { - val formattedDate: String - get() = lastContactedAt.format(formatter) - - companion object { - private val formatter = DateTimeFormatter.ofPattern("MMM d, yyyy") - } -} diff --git a/Near/app/src/main/java/com/alarmy/near/model/FriendSummary.kt b/Near/app/src/main/java/com/alarmy/near/model/FriendSummary.kt new file mode 100644 index 00000000..49249003 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/FriendSummary.kt @@ -0,0 +1,13 @@ +package com.alarmy.near.model + +import androidx.compose.runtime.Immutable + +@Immutable +data class FriendSummary( + val id: String, + val name: String, + val profileImageUrl: String?, + val lastContactedAt: String?, + val isContacted: Boolean, + val contactFrequency: ContactFrequency, +) 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 ed5eaa8c..b9243592 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 @@ -8,6 +8,6 @@ interface FriendService { @GET("/friend/list") suspend fun fetchFriends(): List - @GET("friends/monthly") + @GET("friend/monthly") suspend fun fetchMonthlyFriends(): List } 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 e0088986..3935d8da 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 @@ -49,7 +49,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.alarmy.near.R import com.alarmy.near.model.ContactFrequency -import com.alarmy.near.model.ContactSummary +import com.alarmy.near.model.FriendSummary import com.alarmy.near.model.MonthlyContact import com.alarmy.near.presentation.feature.home.component.MyContacts import com.alarmy.near.presentation.ui.extension.dropShadow @@ -73,17 +73,7 @@ internal fun HomeRoute( onContactClick = {}, onAlarmClick = {}, onMyPageClick = {}, - contacts = - List(6) { - ContactSummary( - id = "2003", - name = "일이삼사오육칠팔구", - profileImageUrl = "https://search.yahoo.com/search?p=partiendo", - lastContactedAt = LocalDate.of(2025, 7, 25), - isContacted = false, - contactFrequency = ContactFrequency.LOW, - ) - }, + contacts = friends.value, monthlyContacts = emptyList(), ) } @@ -95,7 +85,7 @@ internal fun HomeScreen( onContactClick: (String) -> Unit = { _ -> }, onMyPageClick: () -> Unit = {}, onAlarmClick: () -> Unit = {}, - contacts: List, + contacts: List, monthlyContacts: List, ) { val density = LocalDensity.current @@ -119,7 +109,8 @@ internal fun HomeScreen( R.drawable.img_bg, ), contentScale = ContentScale.FillBounds, - ).fillMaxSize(), + ) + .fillMaxSize(), ) { Spacer(modifier = Modifier.height(statusBarHeightDp)) Row( @@ -349,11 +340,11 @@ internal fun HomeScreenPreview() { onContactClick = {}, contacts = List(6) { - ContactSummary( + FriendSummary( id = "2003", name = "일이삼사오육칠팔구", profileImageUrl = "https://search.yahoo.com/search?p=partiendo", - lastContactedAt = LocalDate.of(2025, 7, 25), + lastContactedAt = "2025-07-16", isContacted = false, contactFrequency = ContactFrequency.HIGH, ) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt index d468297c..145eee6b 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.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.Friend +import com.alarmy.near.model.FriendSummary import com.alarmy.near.model.MonthlyFriend import com.alarmy.near.presentation.feature.home.model.HomeUiState import dagger.hilt.android.lifecycle.HiltViewModel @@ -24,7 +25,7 @@ class HomeViewModel private val _uiStateFlow = MutableStateFlow(HomeUiState.Loading) val uiStateFlow = _uiStateFlow.asStateFlow() - val friendsFlow: StateFlow> = + val friendsFlow: StateFlow> = friendRepository .fetchFriends() .stateIn( @@ -41,7 +42,7 @@ class HomeViewModel scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList(), - ) + ) fun removeContact(id: Long) { // contactRepository.removeContact(id) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt index d9914ca0..15fd3d28 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt @@ -20,23 +20,22 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R import com.alarmy.near.model.ContactFrequency -import com.alarmy.near.model.ContactSummary +import com.alarmy.near.model.FriendSummary import com.alarmy.near.presentation.ui.extension.onNoRippleClick import com.alarmy.near.presentation.ui.theme.NearTheme -import java.time.LocalDate private const val MAX_WIDTH_OF_NAME_TEXT = 97 @Composable fun ContactItem( modifier: Modifier = Modifier, - contactSummary: ContactSummary, + friendSummary: FriendSummary, onClick: (contactId: String) -> Unit = {}, ) { Column( modifier = modifier.onNoRippleClick { - onClick(contactSummary.id) + onClick(friendSummary.id) }, horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -51,7 +50,7 @@ fun ContactItem( .align(Alignment.TopEnd) .offset(x = 4.dp, y = (-4).dp), painter = - when (contactSummary.contactFrequency) { + when (friendSummary.contactFrequency) { ContactFrequency.LOW -> painterResource(R.drawable.ic_visual_24_emoji_0) ContactFrequency.MIDDLE -> painterResource(R.drawable.ic_visual_24_emoji_50) ContactFrequency.HIGH -> painterResource(R.drawable.ic_visual_24_emoji_100) @@ -62,7 +61,7 @@ fun ContactItem( Spacer(modifier = Modifier.height(6.dp)) Text( modifier = Modifier.width(MAX_WIDTH_OF_NAME_TEXT.dp), - text = contactSummary.name, + text = friendSummary.name, style = NearTheme.typography.B2_14_BOLD, textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis, @@ -70,33 +69,35 @@ fun ContactItem( color = NearTheme.colors.BLACK_1A1A1A, ) Spacer(modifier = Modifier.height(1.dp)) - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Text( - contactSummary.formattedDate, + friendSummary.lastContactedAt ?: "", style = NearTheme.typography.FC_12_MEDIUM, textAlign = TextAlign.Center, color = NearTheme.colors.GRAY02_B7B7B7, ) Spacer(modifier = Modifier.width(2.dp)) - Image( - painter = painterResource(R.drawable.ic_12_check), - contentDescription = "", - ) + if (friendSummary.lastContactedAt != null) { + Image( + painter = painterResource(R.drawable.ic_12_check), + contentDescription = "", + ) + } } } } @Preview(showBackground = true) @Composable -fun ContactItemPreview_Default() { +fun ContactItemPreview() { ContactItem( modifier = Modifier.padding(top = 10.dp), - contactSummary = - ContactSummary( + friendSummary = + FriendSummary( id = "123L", name = "홍길동", profileImageUrl = "", - lastContactedAt = LocalDate.of(2025, 5, 31), + lastContactedAt = "2025-04-21", isContacted = true, contactFrequency = ContactFrequency.HIGH, ), diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/MyContacts.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/MyContacts.kt index 526373b3..c09f3796 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/MyContacts.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/MyContacts.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.model.ContactFrequency -import com.alarmy.near.model.ContactSummary +import com.alarmy.near.model.FriendSummary import com.alarmy.near.presentation.ui.theme.NearTheme import java.time.LocalDate @@ -27,7 +27,7 @@ private const val OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT = 34 @Composable fun MyContacts( modifier: Modifier = Modifier, - contactsWithPage: List>, + contactsWithPage: List>, pagerState: PagerState = rememberPagerState( initialPage = 0, @@ -76,7 +76,7 @@ fun MyContacts( horizontalArrangement = Arrangement.Center, ) { ContactItem( - contactSummary = contactsWithPage[page][0], + friendSummary = contactsWithPage[page][0], onClick = onContactClick, ) Spacer(modifier = Modifier.width((60 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) @@ -94,13 +94,13 @@ fun MyContacts( ) { Spacer(modifier = Modifier.height(112.dp)) ContactItem( - contactSummary = contactsWithPage[page][0], + friendSummary = contactsWithPage[page][0], onClick = onContactClick, ) Spacer(modifier = Modifier.height(16.dp)) Row { ContactItem( - contactSummary = contactsWithPage[page][1], + friendSummary = contactsWithPage[page][1], onClick = onContactClick, ) Spacer(modifier = Modifier.width((118 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) @@ -120,17 +120,17 @@ fun MyContacts( Box { ContactItem( modifier = Modifier.align(Alignment.TopCenter), - contactSummary = contactsWithPage[page][0], + friendSummary = contactsWithPage[page][0], onClick = onContactClick, ) Row(modifier = Modifier.padding(top = 92.dp, bottom = 78.dp)) { ContactItem( - contactSummary = contactsWithPage[page][1], + friendSummary = contactsWithPage[page][1], onClick = onContactClick, ) Spacer(modifier = Modifier.width((141 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) ContactItem( - contactSummary = contactsWithPage[page][2], + friendSummary = contactsWithPage[page][2], onClick = onContactClick, ) } @@ -151,17 +151,17 @@ fun MyContacts( Box { ContactItem( modifier = Modifier.align(Alignment.TopCenter), - contactSummary = contactsWithPage[page][0], + friendSummary = contactsWithPage[page][0], onClick = onContactClick, ) Row(modifier = Modifier.padding(top = 62.dp)) { ContactItem( - contactSummary = contactsWithPage[page][1], + friendSummary = contactsWithPage[page][1], onClick = onContactClick, ) Spacer(modifier = Modifier.width((138 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) ContactItem( - contactSummary = contactsWithPage[page][2], + friendSummary = contactsWithPage[page][2], onClick = onContactClick, ) } @@ -169,7 +169,7 @@ fun MyContacts( Spacer(modifier = Modifier.height(16.dp)) Row { ContactItem( - contactSummary = contactsWithPage[page][3], + friendSummary = contactsWithPage[page][3], onClick = onContactClick, ) Spacer(modifier = Modifier.width((51 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) @@ -189,17 +189,17 @@ fun MyContacts( Box { ContactItem( modifier = Modifier.align(Alignment.TopCenter), - contactSummary = contactsWithPage[page][0], + friendSummary = contactsWithPage[page][0], onClick = onContactClick, ) Row(modifier = Modifier.padding(top = 62.dp)) { ContactItem( - contactSummary = contactsWithPage[page][1], + friendSummary = contactsWithPage[page][1], onClick = onContactClick, ) Spacer(modifier = Modifier.width((138 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) ContactItem( - contactSummary = contactsWithPage[page][2], + friendSummary = contactsWithPage[page][2], onClick = onContactClick, ) } @@ -207,12 +207,12 @@ fun MyContacts( Spacer(modifier = Modifier.height(16.dp)) Row { ContactItem( - contactSummary = contactsWithPage[page][3], + friendSummary = contactsWithPage[page][3], onClick = onContactClick, ) Spacer(modifier = Modifier.width((51 - OVERFLOW_WIDTH_OF_CONTACT_ITEM_BY_NAME_TEXT).dp)) ContactItem( - contactSummary = contactsWithPage[page][4], + friendSummary = contactsWithPage[page][4], onClick = onContactClick, ) } @@ -234,11 +234,11 @@ fun MyContactsPreview() { modifier = Modifier.align(Alignment.Center), contactsWithPage = List(5) { - ContactSummary( + FriendSummary( id = "2003", name = "일이삼사오육칠팔구", profileImageUrl = "https://search.yahoo.com/search?p=partiendo", - lastContactedAt = LocalDate.of(2025, 7, 25), + lastContactedAt = "2025-04-21", isContacted = false, contactFrequency = ContactFrequency.LOW, ) From a84995c0deabf373f2af77ec2afc6d3033c36265 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 15:35:02 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20monthly=20friend=20api-ui=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../near/data/mapper/MonthlyFriendMapper.kt | 5 ++-- .../repository/DefaultFriendRepository.kt | 3 +-- .../near/data/repository/FriendRepository.kt | 3 +-- .../main/java/com/alarmy/near/model/Friend.kt | 12 --------- .../com/alarmy/near/model/MonthlyFriend.kt | 8 ------ .../MonthlyFriend.kt} | 8 +++--- .../near/model/monthly/MonthlyFriendType.kt | 23 ++++++++++++++++ .../presentation/feature/home/HomeScreen.kt | 26 +++++++++---------- .../feature/home/HomeViewModel.kt | 17 +++--------- 9 files changed, 49 insertions(+), 56 deletions(-) delete mode 100644 Near/app/src/main/java/com/alarmy/near/model/Friend.kt delete mode 100644 Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt rename Near/app/src/main/java/com/alarmy/near/model/{MonthlyContact.kt => monthly/MonthlyFriend.kt} (86%) create mode 100644 Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt diff --git a/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt b/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt index a8b8b7f3..d9ba2e49 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/mapper/MonthlyFriendMapper.kt @@ -1,12 +1,13 @@ package com.alarmy.near.data.mapper -import com.alarmy.near.model.MonthlyFriend +import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.model.monthly.MonthlyFriendType import com.alarmy.near.network.response.MonthlyFriendEntity fun MonthlyFriendEntity.toModel(): MonthlyFriend = MonthlyFriend( friendId = friendId, name = name, - type = type, + type = MonthlyFriendType.from(type), nextContactAt = nextContactAt, ) 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 bc7b3613..61ae63e7 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 @@ -1,9 +1,8 @@ package com.alarmy.near.data.repository import com.alarmy.near.data.mapper.toModel -import com.alarmy.near.model.Friend import com.alarmy.near.model.FriendSummary -import com.alarmy.near.model.MonthlyFriend +import com.alarmy.near.model.monthly.MonthlyFriend import com.alarmy.near.network.service.FriendService import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow 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 727652f6..425654fe 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 @@ -1,8 +1,7 @@ package com.alarmy.near.data.repository -import com.alarmy.near.model.Friend import com.alarmy.near.model.FriendSummary -import com.alarmy.near.model.MonthlyFriend +import com.alarmy.near.model.monthly.MonthlyFriend import kotlinx.coroutines.flow.Flow interface FriendRepository { diff --git a/Near/app/src/main/java/com/alarmy/near/model/Friend.kt b/Near/app/src/main/java/com/alarmy/near/model/Friend.kt deleted file mode 100644 index ebd71a9b..00000000 --- a/Near/app/src/main/java/com/alarmy/near/model/Friend.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.alarmy.near.model - -data class Friend( - val friendId: String, - val position: Int, - val source: String, - val name: String, - val imageUrl: String? = null, - val fileName: String? = null, - val checkRate: Int, - val lastContactAt: String? = null, -) diff --git a/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt b/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt deleted file mode 100644 index 060bcdac..00000000 --- a/Near/app/src/main/java/com/alarmy/near/model/MonthlyFriend.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.alarmy.near.model - -data class MonthlyFriend( - val friendId: String, - val name: String, - val type: String, - val nextContactAt: String, -) diff --git a/Near/app/src/main/java/com/alarmy/near/model/MonthlyContact.kt b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt similarity index 86% rename from Near/app/src/main/java/com/alarmy/near/model/MonthlyContact.kt rename to Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt index 286a7da4..370e983d 100644 --- a/Near/app/src/main/java/com/alarmy/near/model/MonthlyContact.kt +++ b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriend.kt @@ -1,13 +1,13 @@ -package com.alarmy.near.model +package com.alarmy.near.model.monthly import java.time.LocalDate import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit -data class MonthlyContact( +data class MonthlyFriend( val friendId: String, val name: String, - val type: String, + val type: MonthlyFriendType, val nextContactAt: String, ) { fun daysUntilNextContact(today: LocalDate): String { @@ -26,7 +26,7 @@ data class MonthlyContact( return ChronoUnit.DAYS.between(today, targetDate) } - companion object { + companion object Companion { private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") } } diff --git a/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt new file mode 100644 index 00000000..dedd3645 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt @@ -0,0 +1,23 @@ +package com.alarmy.near.model.monthly + +import androidx.annotation.DrawableRes +import com.alarmy.near.R + +enum class MonthlyFriendType( + @param:DrawableRes val imageSrc: Int, +) { + ANNIVERSARY(R.drawable.icon_visual_24_heart), + BIRTHDAY(R.drawable.icon_visual_cake), + MESSAGE(R.drawable.icon_visual_mail), + ; + + companion object { + private const val ERROR_MESSAGE_NOT_FOUND_MONTHLY_TYPE = "일치하는 타입이 없습니다" + + fun from(value: String): MonthlyFriendType = + entries.firstOrNull { it.name == value } + ?: throw IllegalArgumentException( + ERROR_MESSAGE_NOT_FOUND_MONTHLY_TYPE, + ) + } +} 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 3935d8da..979f9330 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 @@ -50,7 +50,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.alarmy.near.R import com.alarmy.near.model.ContactFrequency import com.alarmy.near.model.FriendSummary -import com.alarmy.near.model.MonthlyContact +import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.model.monthly.MonthlyFriendType import com.alarmy.near.presentation.feature.home.component.MyContacts import com.alarmy.near.presentation.ui.extension.dropShadow import com.alarmy.near.presentation.ui.extension.onNoRippleClick @@ -74,7 +75,7 @@ internal fun HomeRoute( onAlarmClick = {}, onMyPageClick = {}, contacts = friends.value, - monthlyContacts = emptyList(), + monthlyFriends = monthlyFriends.value, ) } @@ -86,7 +87,7 @@ internal fun HomeScreen( onMyPageClick: () -> Unit = {}, onAlarmClick: () -> Unit = {}, contacts: List, - monthlyContacts: List, + monthlyFriends: List, ) { val density = LocalDensity.current val statusBarHeightDp = with(density) { WindowInsets.statusBars.getTop(density).toDp() } @@ -109,8 +110,7 @@ internal fun HomeScreen( R.drawable.img_bg, ), contentScale = ContentScale.FillBounds, - ) - .fillMaxSize(), + ).fillMaxSize(), ) { Spacer(modifier = Modifier.height(statusBarHeightDp)) Row( @@ -161,7 +161,7 @@ internal fun HomeScreen( color = NearTheme.colors.WHITE_FFFFFF, ) Spacer(modifier = Modifier.height(16.dp)) - if (monthlyContacts.isEmpty()) { + if (monthlyFriends.isEmpty()) { Surface( modifier = Modifier @@ -193,12 +193,12 @@ internal fun HomeScreen( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { items( - count = monthlyContacts.size, + count = monthlyFriends.size, key = { - monthlyContacts[it].friendId + monthlyFriends[it].friendId }, ) { - val monthlyContact = monthlyContacts[it] + val monthlyContact = monthlyFriends[it] val now = LocalDate.now() Surface( modifier.dropShadow( @@ -219,7 +219,7 @@ internal fun HomeScreen( verticalAlignment = Alignment.CenterVertically, ) { Image( - painterResource(R.drawable.icon_visual_mail), + painterResource(monthlyContact.type.imageSrc), contentDescription = "", ) Spacer(modifier = Modifier.width(8.dp)) @@ -349,12 +349,12 @@ internal fun HomeScreenPreview() { contactFrequency = ContactFrequency.HIGH, ) }, - monthlyContacts = + monthlyFriends = List(4) { - MonthlyContact( + MonthlyFriend( friendId = "intellegat$it", name = "Stacey Stewart", - type = "ANNIVERSARY", + type = MonthlyFriendType.ANNIVERSARY, nextContactAt = "2025-09-30", ) }, diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt index 145eee6b..c2902221 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt @@ -3,15 +3,12 @@ package com.alarmy.near.presentation.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alarmy.near.data.repository.FriendRepository -import com.alarmy.near.model.Friend import com.alarmy.near.model.FriendSummary -import com.alarmy.near.model.MonthlyFriend -import com.alarmy.near.presentation.feature.home.model.HomeUiState +import com.alarmy.near.model.monthly.MonthlyFriend import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @@ -21,13 +18,10 @@ class HomeViewModel constructor( friendRepository: FriendRepository, ) : ViewModel() { - // Example: 여러번 초기화되는 StateFlow - private val _uiStateFlow = MutableStateFlow(HomeUiState.Loading) - val uiStateFlow = _uiStateFlow.asStateFlow() - val friendsFlow: StateFlow> = friendRepository .fetchFriends() + .catch { } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), @@ -38,13 +32,10 @@ class HomeViewModel StateFlow> = friendRepository .fetchMonthlyFriends() + .catch { } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList(), ) - - fun removeContact(id: Long) { - // contactRepository.removeContact(id) - } } From b8c06cab4637e430a99df886ff8bf121ddd1b752 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 16:42:37 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20dropdown=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../navigation/FriendProfileNavigation.kt | 5 +- .../presentation/feature/home/HomeScreen.kt | 71 ++++++++++++++----- .../feature/home/navigation/HomeNavigation.kt | 2 + .../presentation/feature/main/NearNavHost.kt | 4 +- Near/app/src/main/res/values/strings.xml | 1 + 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt index 04100225..ad08cca2 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt @@ -10,7 +10,10 @@ import kotlinx.serialization.Serializable @Serializable object RouteFriendProfile -fun NavController.navigateToFriendProfile(navOptions: NavOptions) { +fun NavController.navigateToFriendProfile( + friendId: String, + navOptions: NavOptions? = null, +) { navigate(RouteFriendProfile, navOptions) } 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 979f9330..254cc867 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 @@ -1,5 +1,6 @@ package com.alarmy.near.presentation.feature.home +import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -22,11 +23,14 @@ import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +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.clip @@ -67,13 +71,15 @@ internal fun HomeRoute( onContactClick: (String) -> Unit = {}, onAlarmClick: () -> Unit = {}, onMyPageClick: () -> Unit = {}, + onAddContactClick: () -> Unit = {}, ) { val friends = viewModel.friendsFlow.collectAsStateWithLifecycle() val monthlyFriends = viewModel.monthlyFriendFlow.collectAsStateWithLifecycle() HomeScreen( - onContactClick = {}, - onAlarmClick = {}, - onMyPageClick = {}, + onContactClick = onContactClick, + onAlarmClick = onAlarmClick, + onMyPageClick = onMyPageClick, + onAddContactClick = onAddContactClick, contacts = friends.value, monthlyFriends = monthlyFriends.value, ) @@ -86,6 +92,7 @@ internal fun HomeScreen( onContactClick: (String) -> Unit = { _ -> }, onMyPageClick: () -> Unit = {}, onAlarmClick: () -> Unit = {}, + onAddContactClick: () -> Unit = {}, contacts: List, monthlyFriends: List, ) { @@ -99,6 +106,7 @@ internal fun HomeScreen( contactsWithPage.count() + if (contactsWithPage.lastOrNull()?.count() == 5) 1 else 0 }, ) + val dropdownState = remember { mutableStateOf(false) } Surface(modifier = modifier) { Column( @@ -253,6 +261,7 @@ internal fun HomeScreen( } Spacer(modifier = Modifier.height(24.dp)) + Box( modifier = Modifier @@ -262,6 +271,15 @@ internal fun HomeScreen( shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp), ), ) { + MyContacts( + modifier = Modifier.align(Alignment.TopCenter), + contactsWithPage = contactsWithPage, + pagerState = pagerState, + onContactClick = onContactClick, + onAddContactClick = { + onAddContactClick() + }, + ) Row( modifier = Modifier @@ -276,21 +294,38 @@ internal fun HomeScreen( style = NearTheme.typography.H2_18_BOLD, color = NearTheme.colors.BLACK_1A1A1A, ) - Icon( - painterResource(R.drawable.ic_32_menu), - contentDescription = stringResource(R.string.home_my_people_setting), - ) + Column { + Image( + modifier = + Modifier.onNoRippleClick(onClick = { + Log.d("covy", "onClick") + dropdownState.value = true + }), + painter = painterResource(R.drawable.ic_32_menu), + contentDescription = stringResource(R.string.home_my_people_setting), + ) + DropdownMenu( + modifier = Modifier.background(color = NearTheme.colors.WHITE_FFFFFF), + expanded = dropdownState.value, + shape = RoundedCornerShape(12.dp), + onDismissRequest = { dropdownState.value = false }, + ) { + DropdownMenuItem( + onClick = { + // TODO 연락처 화면 이동 + dropdownState.value = false + }, + text = { + Text( + stringResource(R.string.home_menu_text_add_friend), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.BLACK_1A1A1A, + ) + }, + ) + } + } } - Spacer(modifier = Modifier.height(16.dp)) - MyContacts( - modifier = Modifier.align(Alignment.TopCenter), - contactsWithPage = contactsWithPage, - pagerState = pagerState, - onContactClick = onContactClick, - onAddContactClick = { - // TODO Contact 클릭 이벤트 구현 - }, - ) if (contactsWithPage.size >= MINIMUM_PAGE_COUNT_TO_SHOW_UI) { Column(modifier = Modifier.align(Alignment.BottomCenter)) { 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 4569fd79..804ed944 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 @@ -22,6 +22,7 @@ fun NavGraphBuilder.homeNavGraph( onContactClick: (String) -> Unit = {}, onAlarmClick: () -> Unit = {}, onMyPageClick: () -> Unit = {}, + onAddContactClick: () -> Unit = {}, ) { composable { backStackEntry -> HomeRoute( @@ -29,6 +30,7 @@ fun NavGraphBuilder.homeNavGraph( onContactClick = onContactClick, onAlarmClick = onAlarmClick, onMyPageClick = onMyPageClick, + onAddContactClick = onAddContactClick, ) } } 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 78d50a02..94f9e995 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 @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import com.alarmy.near.presentation.feature.friendprofile.navigation.friendProfileNavGraph +import com.alarmy.near.presentation.feature.friendprofile.navigation.navigateToFriendProfile import com.alarmy.near.presentation.feature.friendprofileedittor.navigation.friendProfileEditorNavGraph import com.alarmy.near.presentation.feature.home.navigation.RouteHome import com.alarmy.near.presentation.feature.home.navigation.homeNavGraph @@ -32,10 +33,11 @@ internal fun NearNavHost( homeNavGraph( onShowErrorSnackBar = onShowSnackbar, onContactClick = { contactId -> - // 예시: navController.navigate(RouteContact(it)) + navController.navigateToFriendProfile(friendId = contactId) }, onMyPageClick = {}, onAlarmClick = {}, + onAddContactClick = {}, ) } } diff --git a/Near/app/src/main/res/values/strings.xml b/Near/app/src/main/res/values/strings.xml index 3d71e0ec..3606877d 100644 --- a/Near/app/src/main/res/values/strings.xml +++ b/Near/app/src/main/res/values/strings.xml @@ -37,4 +37,5 @@ 2주 매달 6개월 + 사람 추가 From 550230eec0e08ca830b983a70a2ef96496edf9f0 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 16:52:03 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20Companion=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../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 370e983d..ac4fbb1b 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 @@ -26,7 +26,7 @@ data class MonthlyFriend( return ChronoUnit.DAYS.between(today, targetDate) } - companion object Companion { + companion object { private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") } } From dd5f4015f0a293a9b940a111ef3e21f579518660 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 17:00:20 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20home=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../near/presentation/feature/home/HomeScreen.kt | 9 +++++++++ .../near/presentation/feature/home/HomeViewModel.kt | 13 +++++++++++-- 2 files changed, 20 insertions(+), 2 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 254cc867..b09b243f 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 @@ -29,6 +29,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Surface 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 @@ -60,6 +61,7 @@ import com.alarmy.near.presentation.feature.home.component.MyContacts import com.alarmy.near.presentation.ui.extension.dropShadow import com.alarmy.near.presentation.ui.extension.onNoRippleClick import com.alarmy.near.presentation.ui.theme.NearTheme +import kotlinx.coroutines.launch import java.time.LocalDate private const val MINIMUM_PAGE_COUNT_TO_SHOW_UI = 2 @@ -73,6 +75,13 @@ internal fun HomeRoute( onMyPageClick: () -> Unit = {}, onAddContactClick: () -> Unit = {}, ) { + LaunchedEffect(Unit) { + launch { + viewModel.errorEvent.collect { + onShowErrorSnackBar(it) + } + } + } val friends = viewModel.friendsFlow.collectAsStateWithLifecycle() val monthlyFriends = viewModel.monthlyFriendFlow.collectAsStateWithLifecycle() HomeScreen( diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt index c2902221..899c1503 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt @@ -6,9 +6,11 @@ import com.alarmy.near.data.repository.FriendRepository import com.alarmy.near.model.FriendSummary import com.alarmy.near.model.monthly.MonthlyFriend import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @@ -18,10 +20,15 @@ class HomeViewModel constructor( friendRepository: FriendRepository, ) : ViewModel() { + private val _errorEvent = Channel() + val errorEvent = _errorEvent.receiveAsFlow() + val friendsFlow: StateFlow> = friendRepository .fetchFriends() - .catch { } + .catch { + _errorEvent.send(it) + } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), @@ -32,7 +39,9 @@ class HomeViewModel StateFlow> = friendRepository .fetchMonthlyFriends() - .catch { } + .catch { + _errorEvent.send(it) + } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), From eb8a39eab08a4dd2facb08edc5d44f5cd1fefac7 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 17:03:12 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20navigation=20=EC=9D=B8=EC=9E=90=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friendprofile/navigation/FriendProfileNavigation.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt index ad08cca2..c3b4608a 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/navigation/FriendProfileNavigation.kt @@ -8,13 +8,15 @@ import com.alarmy.near.presentation.feature.friendprofile.FriendProfileRoute import kotlinx.serialization.Serializable @Serializable -object RouteFriendProfile +data class RouteFriendProfile( + val friendId: String, +) fun NavController.navigateToFriendProfile( friendId: String, navOptions: NavOptions? = null, ) { - navigate(RouteFriendProfile, navOptions) + navigate(RouteFriendProfile(friendId), navOptions) } fun NavGraphBuilder.friendProfileNavGraph( From aef7ab2aa1d3730deab843b55f24f8a1bba31848 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 17:12:19 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20API=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/alarmy/near/network/service/FriendService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b9243592..0beca5dc 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 @@ -8,6 +8,6 @@ interface FriendService { @GET("/friend/list") suspend fun fetchFriends(): List - @GET("friend/monthly") + @GET("/friend/monthly") suspend fun fetchMonthlyFriends(): List } From b2ec675c4d569fd3015836c43768fdd02a2ea660 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 17:12:37 +0900 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alarmy/near/presentation/feature/home/HomeScreen.kt | 2 -- 1 file changed, 2 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 b09b243f..61612787 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 @@ -1,6 +1,5 @@ package com.alarmy.near.presentation.feature.home -import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -307,7 +306,6 @@ internal fun HomeScreen( Image( modifier = Modifier.onNoRippleClick(onClick = { - Log.d("covy", "onClick") dropdownState.value = true }), painter = painterResource(R.drawable.ic_32_menu), From 6028ad915d2e16f499062c434c14507bc7863e07 Mon Sep 17 00:00:00 2001 From: kwakjoohyeong Date: Sun, 24 Aug 2025 17:24:20 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=EC=B1=99=EA=B9=80=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=A7=A4=ED=95=91=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alarmy/near/model/monthly/MonthlyFriendType.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt index dedd3645..553d6939 100644 --- a/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt +++ b/Near/app/src/main/java/com/alarmy/near/model/monthly/MonthlyFriendType.kt @@ -15,9 +15,8 @@ enum class MonthlyFriendType( private const val ERROR_MESSAGE_NOT_FOUND_MONTHLY_TYPE = "일치하는 타입이 없습니다" fun from(value: String): MonthlyFriendType = - entries.firstOrNull { it.name == value } - ?: throw IllegalArgumentException( - ERROR_MESSAGE_NOT_FOUND_MONTHLY_TYPE, - ) + runCatching { valueOf(value.uppercase()) }.getOrNull() ?: throw IllegalStateException( + ERROR_MESSAGE_NOT_FOUND_MONTHLY_TYPE, + ) } }