From f6a696d539dcb67169059fed26434d4732a0ff91 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:00:17 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[refactor]:=20roomId=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=EC=98=81=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/navigator/navigations/GroupNavigation.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt index 8470680c..8c913a27 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/GroupNavigation.kt @@ -206,8 +206,7 @@ fun NavGraphBuilder.groupNavigation( val roomId = route.roomId GroupRoomScreen( -// roomId = roomId, - roomId = 1, + roomId = roomId, onBackClick = { navigateBack() }, @@ -226,8 +225,7 @@ fun NavGraphBuilder.groupNavigation( val roomId = route.roomId GroupRoomMatesScreen( -// roomId = roomId, - roomId = 1, + roomId = roomId, onBackClick = { navigateBack() }, @@ -250,8 +248,7 @@ fun NavGraphBuilder.groupNavigation( val uiState by viewModel.uiState.collectAsStateWithLifecycle() GroupNoteScreen( -// roomId = roomId, - roomId = 1, + roomId = roomId, resultTabIndex = result, initialPage = page, initialIsOverview = isOverview, @@ -286,7 +283,7 @@ fun NavGraphBuilder.groupNavigation( val roomId = route.roomId GroupNoteCreateScreen( - roomId = 1, + roomId = roomId, recentPage = route.recentBookPage, totalPage = route.totalBookPage, isOverviewPossible = route.isOverviewPossible, @@ -307,8 +304,7 @@ fun NavGraphBuilder.groupNavigation( val roomId = route.roomId GroupVoteCreateScreen( -// roomId = roomId, - roomId = 1, + roomId = roomId, recentPage = route.recentPage, totalPage = route.totalPage, isOverviewPossible = route.isOverviewPossible, From e55f373fa1d1ad4119ad5a739c7aceb9631ffc89 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:30:30 +0900 Subject: [PATCH 02/18] =?UTF-8?q?[feat]:=20comment=20response=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comments/response/CommentsResponse.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt new file mode 100644 index 00000000..0db6c4d7 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsResponse.kt @@ -0,0 +1,41 @@ +package com.texthip.thip.data.model.comments.response + +import kotlinx.serialization.Serializable + +@Serializable +data class CommentsResponse( + val commentList: List, + val nextCursor: String?, + val isLast: Boolean, +) + +@Serializable +data class CommentList( + val commentId: Int, + val creatorId: Int, + val creatorProfileImageUrl: String, + val creatorNickname: String, + val aliasName: String, + val aliasColor: String, + val postDate: String, + val content: String, + val likeCount: Int, + val isDeleted: Boolean, + val isLike: Boolean, + val replyList: List, +) + +@Serializable +data class ReplyList( + val commentId: Int, + val parentCommentCreatorNickname: String, + val creatorId: Int, + val creatorProfileImageUrl: String, + val creatorNickname: String, + val aliasName: String, + val aliasColor: String, + val postDate: String, + val content: String, + val likeCount: Int, + val isLike: Boolean, +) \ No newline at end of file From b142d1403e76d2752d2850a2985d79abb5dba0a8 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:30:38 +0900 Subject: [PATCH 03/18] =?UTF-8?q?[feat]:=20comment=20service=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../texthip/thip/data/service/CommentsService.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/service/CommentsService.kt diff --git a/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt new file mode 100644 index 00000000..8f37aa3b --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt @@ -0,0 +1,16 @@ +package com.texthip.thip.data.service + +import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.comments.response.CommentsResponse +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface CommentsService { + @GET("comments/{postId}") + suspend fun getComments( + @Path("postId") postId: Long, + @Query("postType") postType: String = "RECORD", + @Query("cursor") cursor: String? = null, + ): BaseResponse +} \ No newline at end of file From 4633a2a7e1633c1fe49d670b1a1c6f2e27dada9e Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:30:47 +0900 Subject: [PATCH 04/18] =?UTF-8?q?[feat]:=20comment=20repository=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/di/ServiceModule.kt | 6 +++++ .../data/repository/CommentsRepository.kt | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt diff --git a/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt b/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt index bf7eafad..8f34f17f 100644 --- a/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt +++ b/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt @@ -1,6 +1,7 @@ package com.texthip.thip.data.di import com.texthip.thip.data.service.BookService +import com.texthip.thip.data.service.CommentsService import com.texthip.thip.data.service.GroupService import com.texthip.thip.data.service.RoomsService import dagger.Module @@ -29,4 +30,9 @@ object ServiceModule { @Singleton fun providesRoomsService(retrofit: Retrofit): RoomsService = retrofit.create(RoomsService::class.java) + + @Provides + @Singleton + fun providesCommentsService(retrofit: Retrofit): CommentsService = + retrofit.create(CommentsService::class.java) } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt new file mode 100644 index 00000000..8990465c --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt @@ -0,0 +1,23 @@ +package com.texthip.thip.data.repository + +import com.texthip.thip.data.model.base.handleBaseResponse +import com.texthip.thip.data.service.CommentsService +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CommentsRepository @Inject constructor( + private val commentsService: CommentsService, +) { + suspend fun getComments( + postId: Long, + postType: String = "RECORD", + cursor: String? = null, + ) = runCatching { + commentsService.getComments( + postId = postId, + postType = postType, + cursor = cursor + ).handleBaseResponse().getOrThrow() + } +} \ No newline at end of file From 3cfb1037c66e8b87e055934c5762dc30077f679c Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:33:28 +0900 Subject: [PATCH 05/18] =?UTF-8?q?[ui]:=20profile=20bar=20string=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/ui/common/header/ProfileBarFeed.kt | 9 ++++----- .../thip/ui/feed/screen/FeedCommentScreen.kt | 14 +++++--------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt b/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt index 03690bd2..bc5f842f 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBarFeed.kt @@ -1,6 +1,5 @@ package com.texthip.thip.ui.common.header -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -18,16 +17,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun ProfileBarFeed( - profileImage: Painter?, + profileImage: String?, nickname: String, genreName: String, genreColor: Color = colors.NeonGreen, @@ -41,8 +40,8 @@ fun ProfileBarFeed( ) { Row { if (profileImage != null) { - Image( - painter = profileImage, + AsyncImage( + model = profileImage, contentDescription = "프로필 이미지", modifier = Modifier .size(24.dp) diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt index 77b256c8..4df9988c 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedCommentScreen.kt @@ -48,12 +48,8 @@ import com.texthip.thip.ui.common.forms.CommentTextField import com.texthip.thip.ui.common.header.ProfileBar import com.texthip.thip.ui.common.modal.DialogPopup import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar -import com.texthip.thip.ui.group.note.component.CommentItem -import com.texthip.thip.ui.group.note.component.ReplyItem import com.texthip.thip.ui.feed.component.ImageViewerModal import com.texthip.thip.ui.feed.mock.FeedItemType -import com.texthip.thip.ui.group.note.component.CommentItem -import com.texthip.thip.ui.group.note.component.ReplyItem import com.texthip.thip.ui.group.note.mock.mockCommentList import com.texthip.thip.ui.group.room.mock.MenuBottomSheetItem import com.texthip.thip.ui.mypage.mock.FeedItem @@ -241,10 +237,10 @@ fun FeedCommentScreen( ) } ) { - CommentItem( - data = commentItem, - onReplyClick = { replyTo.value = it } - ) +// CommentItem( +// data = commentItem, +// onReplyClick = { replyTo.value = it } +// ) } if (selectedComment == commentItem) { Row( @@ -304,7 +300,7 @@ fun FeedCommentScreen( ) } ) { - ReplyItem(data = reply, onReplyClick = { replyTo.value = it }) +// ReplyItem(data = reply, onReplyClick = { replyTo.value = it }) } if (selectedReply == reply) { From 95a9c00bca930e4c6f9966121875bf622fb2b75a Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:34:12 +0900 Subject: [PATCH 06/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20viewmodel=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20screen?= =?UTF-8?q?=EC=97=90=20=EC=97=B0=EA=B2=B0=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../note/component/CommentBottomSheet.kt | 173 ++++++++++++++---- .../ui/group/note/component/CommentItem.kt | 82 +++++---- .../ui/group/note/component/CommentSection.kt | 53 +++--- .../thip/ui/group/note/component/ReplyItem.kt | 50 ++--- .../ui/group/note/screen/GroupNoteScreen.kt | 27 ++- .../group/note/viewmodel/CommentsViewModel.kt | 85 +++++++++ .../com/texthip/thip/utils/toComposeColor.kt | 12 ++ 7 files changed, 344 insertions(+), 138 deletions(-) create mode 100644 app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt create mode 100644 app/src/main/java/com/texthip/thip/utils/toComposeColor.kt diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt index 4bd21837..26e9da87 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt @@ -3,11 +3,18 @@ package com.texthip.thip.ui.group.note.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -18,23 +25,25 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.texthip.thip.R +import com.texthip.thip.data.model.comments.response.CommentList import com.texthip.thip.ui.common.bottomsheet.CustomBottomSheet import com.texthip.thip.ui.common.forms.CommentTextField -import com.texthip.thip.ui.group.note.mock.CommentItem -import com.texthip.thip.ui.group.note.mock.ReplyItem -import com.texthip.thip.ui.group.note.mock.mockComment +import com.texthip.thip.ui.group.note.viewmodel.CommentsEvent +import com.texthip.thip.ui.group.note.viewmodel.CommentsUiState import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun CommentBottomSheet( - commentResponse: List, + uiState: CommentsUiState, + onEvent: (CommentsEvent) -> Unit, onDismiss: () -> Unit, - onSendReply: (String, Int?, String?) -> Unit + onSendReply: (text: String, parentCommentId: Int?, replyToNickname: String?) -> Unit ) { var inputText by remember { mutableStateOf("") } - var replyingTo by remember { mutableStateOf(null) } + var replyingToCommentId by remember { mutableStateOf(null) } + var replyingToNickname by remember { mutableStateOf(null) } CustomBottomSheet(onDismiss = onDismiss) { Column( @@ -54,35 +63,25 @@ fun CommentBottomSheet( modifier = Modifier.padding(start = 20.dp, top = 20.dp, end = 20.dp) ) - if (commentResponse.isEmpty()) { + if (uiState.isLoading) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 210.dp), // TODO: 유동적으로 수정 가능할수도 - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - text = stringResource(R.string.no_comments_yet), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White - ) - Text( - text = stringResource(R.string.no_comment_subtext), - style = typography.copy_r400_s14, - color = colors.Grey, - modifier = Modifier.padding(top = 4.dp) - ) + CircularProgressIndicator() } + } else if (uiState.comments.isEmpty()) { + EmptyCommentView() } else { - CommentList( - commentList = commentResponse, - onSendReply = { replyText, commentId, replyTo -> - onSendReply(replyText, commentId, replyTo) - inputText = "" - }, - onReplyClick = { replyItem -> - replyingTo = replyItem + CommentLazyList( + commentList = uiState.comments, + isLoadingMore = uiState.isLoadingMore, + isLastPage = uiState.isLast, + onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, + onReplyClick = { commentId, nickname -> + replyingToCommentId = commentId + replyingToNickname = nickname } ) } @@ -96,19 +95,98 @@ fun CommentBottomSheet( onSendClick = { onSendReply( inputText, - replyingTo?.replyId, - replyingTo?.nickName + replyingToCommentId, + replyingToNickname ) inputText = "" - replyingTo = null + replyingToCommentId = null + replyingToNickname = null }, - replyTo = replyingTo?.nickName, - onCancelReply = { replyingTo = null } + replyTo = replyingToNickname, + onCancelReply = { + replyingToCommentId = null + replyingToNickname = null + } ) } } } +@Composable +private fun CommentLazyList( + commentList: List, + isLoadingMore: Boolean, + isLastPage: Boolean, + onLoadMore: () -> Unit, + onReplyClick: (commentId: Int, nickname: String) -> Unit +) { + val lazyListState = rememberLazyListState() + + val isScrolledToEnd by remember { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + if (layoutInfo.totalItemsCount == 0) return@derivedStateOf false + val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 + lastVisibleItemIndex >= layoutInfo.totalItemsCount - 1 + } + } + + LaunchedEffect(isScrolledToEnd) { + if (isScrolledToEnd && !isLoadingMore && !isLastPage) { + onLoadMore() + } + } + + LazyColumn(state = lazyListState) { + items( + items = commentList, + key = { it.commentId } + ) { comment -> + CommentSection( + commentItem = comment, + onReplyClick = onReplyClick + ) + } + + if (isLoadingMore) { + item { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator() + } + } + } + } +} + + +@Composable +private fun EmptyCommentView() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 60.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.no_comments_yet), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + Text( + text = stringResource(R.string.no_comment_subtext), + style = typography.copy_r400_s14, + color = colors.Grey, + modifier = Modifier.padding(top = 4.dp) + ) + } +} + @Preview @Composable private fun CommentBottomSheetPreview() { @@ -116,7 +194,28 @@ private fun CommentBottomSheetPreview() { var showSheet by remember { mutableStateOf(true) } if (showSheet) { CommentBottomSheet( - commentResponse = listOf(mockComment, mockComment, mockComment), + uiState = CommentsUiState( + comments = listOf( + CommentList( + commentId = 1, + creatorId = 1, + creatorNickname = "User1", + content = "This is a comment.", + postDate = "2023-10-01", + likeCount = 5, + creatorProfileImageUrl = "https://example.com/image1.jpg", + aliasName = "칭호칭호", + aliasColor = "#A0F8E8", + isDeleted = false, + isLike = false, + replyList = emptyList() + ) + ), + isLoading = false, + isLoadingMore = false, + isLast = false + ), + onEvent = {}, onDismiss = { showSheet = false }, onSendReply = { _, _, _ -> } ) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt index 8a5de977..b38b7ed7 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt @@ -20,30 +20,30 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.texthip.thip.R +import com.texthip.thip.data.model.comments.response.CommentList import com.texthip.thip.ui.common.header.ProfileBarFeed -import com.texthip.thip.ui.group.note.mock.CommentItem import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.toComposeColor @Composable fun CommentItem( modifier: Modifier = Modifier, - data: CommentItem, + data: CommentList, onReplyClick: (String) -> Unit = { } ) { - var isLiked by remember { mutableStateOf(data.isLiked) } + var isLiked by remember { mutableStateOf(data.isLike) } Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(12.dp) ) { ProfileBarFeed( -// profileImage = data.profileImageUrl, - profileImage = painterResource(R.drawable.character_literature), - nickname = data.nickName, - genreName = data.genreName, - genreColor = colors.SocialScience, + profileImage = data.creatorProfileImageUrl, + nickname = data.creatorNickname, + genreName = data.aliasName, + genreColor = data.aliasColor.toComposeColor(), // todo: 추후 다른 함수로 바꾸기 date = data.postDate ) @@ -61,7 +61,7 @@ fun CommentItem( style = typography.feedcopy_r400_s14_h20, ) Text( - modifier = Modifier.clickable(onClick = { onReplyClick(data.nickName) }), + modifier = Modifier.clickable(onClick = { onReplyClick(data.creatorNickname) }), text = stringResource(R.string.write_reply), style = typography.menu_sb600_s12, color = colors.Grey02, @@ -100,47 +100,53 @@ private fun CommentItemPreview() { verticalArrangement = Arrangement.spacedBy(12.dp) ) { CommentItem( - data = CommentItem( + data = CommentList( commentId = 1, - userId = 1, - nickName = "user.01", - genreName = "칭호칭호", - profileImageUrl = "https://example.com/profile.jpg", - content = "입력하세요. 댓글 내용을 입력하세요오. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. ", - postDate = "2025.01.12", - isWriter = false, - isLiked = true, + creatorId = 1, + creatorNickname = "User1", + creatorProfileImageUrl = "https://example.com/image1.jpg", + aliasName= "칭호칭호", + aliasColor = "#FF5733", + content = "This is a comment.", + postDate = "2023-10-01T12:00:00Z", + isLike = false, likeCount = 10, + isDeleted = false, + replyList = emptyList() ) ) CommentItem( - data = CommentItem( + data = CommentList( commentId = 1, - userId = 1, - nickName = "user.01", - genreName = "칭호칭호", - profileImageUrl = "https://example.com/profile.jpg", - content = "입력하세요. 댓글 내용을 입력하세요오. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요.", - postDate = "12시간 전", - isWriter = false, - isLiked = true, - likeCount = 10 + creatorId = 1, + creatorNickname = "User1", + creatorProfileImageUrl = "https://example.com/image1.jpg", + aliasName= "칭호칭호", + aliasColor = "#FF5733", + content = "This is a comment.", + postDate = "2023-10-01T12:00:00Z", + isLike = false, + likeCount = 10, + isDeleted = false, + replyList = emptyList() ) ) CommentItem( - data = CommentItem( + data = CommentList( commentId = 1, - userId = 1, - nickName = "user.01", - genreName = "칭호칭호", - profileImageUrl = "https://example.com/profile.jpg", - content = "입력하세요.", - postDate = "12시간 전", - isWriter = false, - isLiked = true, - likeCount = 10 + creatorId = 1, + creatorNickname = "User1", + creatorProfileImageUrl = "https://example.com/image1.jpg", + aliasName= "칭호칭호", + aliasColor = "#FF5733", + content = "This is a comment.", + postDate = "2023-10-01T12:00:00Z", + isLike = false, + likeCount = 10, + isDeleted = false, + replyList = emptyList() ) ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt index 273b8530..09aaac0f 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt @@ -14,18 +14,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.texthip.thip.data.model.comments.response.CommentList import com.texthip.thip.ui.common.modal.drawVerticalScrollbar -import com.texthip.thip.ui.group.note.mock.CommentItem -import com.texthip.thip.ui.group.note.mock.ReplyItem -import com.texthip.thip.ui.group.note.mock.mockComment import com.texthip.thip.ui.theme.ThipTheme @Composable fun CommentList( modifier: Modifier = Modifier, - commentList: List, + commentList: List, onSendReply: (String, Int?, String?) -> Unit, - onReplyClick: (ReplyItem) -> Unit + onReplyClick: (commentId: Int, nickname: String) -> Unit ) { val scrollState = rememberScrollState() @@ -53,9 +51,9 @@ fun CommentList( @Composable fun CommentSection( - commentItem: CommentItem, - onSendReply: (String, Int?, String?) -> Unit, - onReplyClick: (ReplyItem) -> Unit + commentItem: CommentList, + onSendReply: (String, Int?, String?) -> Unit = { _, _, _ -> }, + onReplyClick: (commentId: Int, nickname: String) -> Unit ) { Box { Column( @@ -67,31 +65,13 @@ fun CommentSection( ) { CommentItem( data = commentItem, - onReplyClick = { - onReplyClick( - ReplyItem( - replyId = commentItem.commentId, - userId = commentItem.userId, - nickName = commentItem.nickName, - parentNickname = "", // 댓글에는 parentNickname이 필요 없음 - genreName = commentItem.genreName, - profileImageUrl = commentItem.profileImageUrl, - content = commentItem.content, - postDate = commentItem.postDate, - isWriter = commentItem.isWriter, - isLiked = commentItem.isLiked, - likeCount = commentItem.likeCount - ) - ) - } + onReplyClick = { onReplyClick(commentItem.commentId, commentItem.creatorNickname) } ) commentItem.replyList.forEach { reply -> ReplyItem( data = reply, - onReplyClick = { - onReplyClick(reply) - } + onReplyClick = { onReplyClick(commentItem.commentId, reply.creatorNickname) } ) } } @@ -105,10 +85,23 @@ fun CommentSectionPreview() { Column { CommentList( commentList = listOf( - mockComment, mockComment, mockComment + CommentList( + commentId = 1, + creatorId = 1, + creatorNickname = "User1", + creatorProfileImageUrl = "https://example.com/image1.jpg", + aliasName= "칭호칭호", + aliasColor = "#A0F8E8", + content = "This is a comment.", + postDate = "2023-10-01", + isLike = false, + likeCount = 10, + isDeleted = false, + replyList = emptyList() + ) ), onSendReply = { _, _, _ -> }, - onReplyClick = { replyItem -> + onReplyClick = { commentId, nickname -> // Handle reply click } ) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt index 29b5ec69..aaaa5a69 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt @@ -22,19 +22,20 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.texthip.thip.R +import com.texthip.thip.data.model.comments.response.ReplyList import com.texthip.thip.ui.common.header.ProfileBarFeed -import com.texthip.thip.ui.group.note.mock.ReplyItem import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.toComposeColor @Composable fun ReplyItem( modifier: Modifier = Modifier, - data: ReplyItem, - onReplyClick: (String) -> Unit = { } + data: ReplyList, + onReplyClick: () -> Unit = { } ) { - var isLiked by remember { mutableStateOf(data.isLiked) } + var isLiked by remember { mutableStateOf(data.isLike) } Row( horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -50,13 +51,12 @@ fun ReplyItem( verticalArrangement = Arrangement.spacedBy(12.dp) ) { ProfileBarFeed( -// profileImage = data.profileImageUrl, - profileImage = painterResource(R.drawable.character_literature), - nickname = data.nickName, - genreName = data.genreName, - genreColor = colors.SocialScience, - date = data.postDate - ) + profileImage = data.creatorProfileImageUrl, + nickname = data.creatorNickname, + genreName = data.aliasName, + genreColor = data.aliasColor.toComposeColor(), // todo: 추후 다른 함수로 바꾸기 + date = data.postDate + ) Row( horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -72,7 +72,7 @@ fun ReplyItem( style = typography.copy_m500_s14_h20.copy(color = colors.White) .toSpanStyle() ) { - append(stringResource(R.string.annotation) + data.parentNickname + stringResource(R.string.space_bar)) + append(stringResource(R.string.annotation) + data.parentCommentCreatorNickname + stringResource(R.string.space_bar)) } append(data.content) }, @@ -80,7 +80,7 @@ fun ReplyItem( style = typography.feedcopy_r400_s14_h20, ) Text( - modifier = Modifier.clickable(onClick = { onReplyClick(data.nickName) }), + modifier = Modifier.clickable(onClick = onReplyClick), text = stringResource(R.string.write_reply), style = typography.menu_sb600_s12, color = colors.Grey02, @@ -120,18 +120,18 @@ private fun ReplyItemPreview() { verticalArrangement = Arrangement.spacedBy(12.dp) ) { ReplyItem( - data = ReplyItem( - replyId = 1, - userId = 1, - nickName = "user.01", - parentNickname = "사용자태그", - genreName = "칭호칭호", - profileImageUrl = "https://example.com/profile.jpg", - content = "입력하세요. 댓글 내용을 입력하세요오. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. 댓글 내용을 입력하세요. ", - postDate = "12시간 전", - isWriter = false, - isLiked = true, - likeCount = 10, + data = ReplyList( + commentId = 1, + parentCommentCreatorNickname = "User1", + creatorId = 2, + creatorNickname = "User1", + aliasName= "칭호칭호", + aliasColor = "#FF5733", + creatorProfileImageUrl = "https://example.com/image2.jpg", + content = "This is a reply.", + postDate = "2023-10-01T12:05:00Z", + isLike = false, + likeCount = 5 ) ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt index 709e058d..fdf6f93a 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt @@ -56,7 +56,7 @@ import com.texthip.thip.ui.group.note.component.CommentBottomSheet import com.texthip.thip.ui.group.note.component.FilterHeaderSection import com.texthip.thip.ui.group.note.component.TextCommentCard import com.texthip.thip.ui.group.note.component.VoteCommentCard -import com.texthip.thip.ui.group.note.mock.mockComment +import com.texthip.thip.ui.group.note.viewmodel.CommentsViewModel import com.texthip.thip.ui.group.note.viewmodel.GroupNoteEvent import com.texthip.thip.ui.group.note.viewmodel.GroupNoteUiState import com.texthip.thip.ui.group.note.viewmodel.GroupNoteViewModel @@ -161,6 +161,9 @@ fun GroupNoteContent( var isPinDialogVisible by remember { mutableStateOf(false) } var showToast by remember { mutableStateOf(false) } + val commentsViewModel: CommentsViewModel = hiltViewModel() + val commentsUiState by commentsViewModel.uiState.collectAsStateWithLifecycle() + LaunchedEffect(showToast) { if (showToast) { delay(3000) @@ -359,7 +362,10 @@ fun GroupNoteContent( "RECORD" -> TextCommentCard( data = post, modifier = itemModifier, - onCommentClick = { isCommentBottomSheetVisible = true }, + onCommentClick = { + selectedPostForComment = post + isCommentBottomSheetVisible = true + }, onLongPress = { selectedPostForMenu = post }, onPinClick = { isPinDialogVisible = true }, onLikeClick = { postId, postType -> @@ -370,7 +376,10 @@ fun GroupNoteContent( "VOTE" -> VoteCommentCard( data = post, modifier = itemModifier, - onCommentClick = { isCommentBottomSheetVisible = true }, + onCommentClick = { + selectedPostForComment = post + isCommentBottomSheetVisible = true + }, onLongPress = { selectedPostForMenu = post }, onPinClick = { isPinDialogVisible = true }, onVote = { postId, voteItemId, type -> @@ -460,17 +469,19 @@ fun GroupNoteContent( } if (isCommentBottomSheetVisible && selectedPostForComment != null) { + LaunchedEffect(selectedPostForComment) { + commentsViewModel.initialize(postId = selectedPostForComment!!.postId.toLong()) + } + CommentBottomSheet( - commentResponse = listOf(mockComment, mockComment, mockComment), -// commentResponse = emptyList(), + uiState = commentsUiState, + onEvent = commentsViewModel::onEvent, onDismiss = { isCommentBottomSheetVisible = false -// selectedNoteRecord = null -// selectedNoteVote = null selectedPostForComment = null }, onSendReply = { replyText, commentId, replyTo -> - // 댓글 전송 로직 구현 + // TODO: 댓글 전송 로직 구현 } ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt new file mode 100644 index 00000000..5df10ccc --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt @@ -0,0 +1,85 @@ +package com.texthip.thip.ui.group.note.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.model.comments.response.CommentList +import com.texthip.thip.data.repository.CommentsRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class CommentsUiState( + val isLoading: Boolean = false, + val isLoadingMore: Boolean = false, + val error: String? = null, + val isLast: Boolean = false, + val comments: List = emptyList() +) + +sealed interface CommentsEvent { + data object LoadMoreComments : CommentsEvent +} + +@HiltViewModel +class CommentsViewModel @Inject constructor( + private val commentsRepository: CommentsRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(CommentsUiState()) + val uiState = _uiState.asStateFlow() + + private var nextCursor: String? = null + private var currentPostId: Long = -1L + + fun initialize(postId: Long) { + if (currentPostId == postId) return + this.currentPostId = postId + fetchComments(isRefresh = true) + } + + fun onEvent(event: CommentsEvent) { + when (event) { + is CommentsEvent.LoadMoreComments -> fetchComments(isRefresh = false) + } + } + + private fun fetchComments(isRefresh: Boolean) { + val currentState = _uiState.value + if (currentState.isLoading || currentState.isLoadingMore || (currentState.isLast && !isRefresh)) { + return + } + + viewModelScope.launch { + _uiState.update { + if (isRefresh) it.copy(isLoading = true, comments = emptyList(), isLast = false) + else it.copy(isLoadingMore = true) + } + + val cursorToFetch = if (isRefresh) null else nextCursor + + commentsRepository.getComments(postId = currentPostId, cursor = cursorToFetch) + .onSuccess { response -> + if (response != null) { + nextCursor = response.nextCursor + _uiState.update { + it.copy( + isLoading = false, + isLoadingMore = false, + // isRefresh일 경우 새 목록으로, 아닐 경우 기존 목록에 추가 + comments = if (isRefresh) response.commentList else it.comments + response.commentList, + isLast = response.isLast + ) + } + } + } + .onFailure { throwable -> + _uiState.update { + it.copy(isLoading = false, isLoadingMore = false, error = throwable.message) + } + } + } + } +} diff --git a/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt b/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt new file mode 100644 index 00000000..2e357035 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt @@ -0,0 +1,12 @@ +package com.texthip.thip.utils + +import androidx.compose.ui.graphics.Color +import androidx.core.graphics.toColorInt + +fun String.toComposeColor(defaultColor: Color = Color.Gray): Color { + return try { + Color(this.toColorInt()) + } catch (e: IllegalArgumentException) { + defaultColor + } +} \ No newline at end of file From 86946bd16da7a5f2e99bec3c6769a319bfab1316 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:01:03 +0900 Subject: [PATCH 07/18] =?UTF-8?q?[refactor]:=20color=20util=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=EB=B2=84=EC=A0=84=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt b/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt index 381ef36f..aa005a48 100644 --- a/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt +++ b/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt @@ -1,10 +1,11 @@ package com.texthip.thip.utils.color import androidx.compose.ui.graphics.Color +import androidx.core.graphics.toColorInt fun hexToColor(hex: String): Color { return try { - Color(android.graphics.Color.parseColor(hex)) + Color(hex.toColorInt()) } catch (e: IllegalArgumentException) { //잘못된 형식이면 기본 색 Color.White From 26f2079f190c61b47eaa36d8f16e4191eb3eb181 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:02:50 +0900 Subject: [PATCH 08/18] =?UTF-8?q?[refactor]:=20color=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/group/note/component/CommentItem.kt | 4 ++-- .../thip/ui/group/note/component/ReplyItem.kt | 22 +++++++++++-------- .../com/texthip/thip/utils/toComposeColor.kt | 12 ---------- 3 files changed, 15 insertions(+), 23 deletions(-) delete mode 100644 app/src/main/java/com/texthip/thip/utils/toComposeColor.kt diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt index b38b7ed7..bda431fa 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt @@ -25,7 +25,7 @@ import com.texthip.thip.ui.common.header.ProfileBarFeed import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography -import com.texthip.thip.utils.toComposeColor +import com.texthip.thip.utils.color.hexToColor @Composable fun CommentItem( @@ -43,7 +43,7 @@ fun CommentItem( profileImage = data.creatorProfileImageUrl, nickname = data.creatorNickname, genreName = data.aliasName, - genreColor = data.aliasColor.toComposeColor(), // todo: 추후 다른 함수로 바꾸기 + genreColor = hexToColor(data.aliasColor), date = data.postDate ) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt index aaaa5a69..30609440 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt @@ -27,7 +27,7 @@ import com.texthip.thip.ui.common.header.ProfileBarFeed import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography -import com.texthip.thip.utils.toComposeColor +import com.texthip.thip.utils.color.hexToColor @Composable fun ReplyItem( @@ -51,12 +51,12 @@ fun ReplyItem( verticalArrangement = Arrangement.spacedBy(12.dp) ) { ProfileBarFeed( - profileImage = data.creatorProfileImageUrl, - nickname = data.creatorNickname, - genreName = data.aliasName, - genreColor = data.aliasColor.toComposeColor(), // todo: 추후 다른 함수로 바꾸기 - date = data.postDate - ) + profileImage = data.creatorProfileImageUrl, + nickname = data.creatorNickname, + genreName = data.aliasName, + genreColor = hexToColor(data.aliasColor), + date = data.postDate + ) Row( horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -72,7 +72,11 @@ fun ReplyItem( style = typography.copy_m500_s14_h20.copy(color = colors.White) .toSpanStyle() ) { - append(stringResource(R.string.annotation) + data.parentCommentCreatorNickname + stringResource(R.string.space_bar)) + append( + stringResource(R.string.annotation) + data.parentCommentCreatorNickname + stringResource( + R.string.space_bar + ) + ) } append(data.content) }, @@ -125,7 +129,7 @@ private fun ReplyItemPreview() { parentCommentCreatorNickname = "User1", creatorId = 2, creatorNickname = "User1", - aliasName= "칭호칭호", + aliasName = "칭호칭호", aliasColor = "#FF5733", creatorProfileImageUrl = "https://example.com/image2.jpg", content = "This is a reply.", diff --git a/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt b/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt deleted file mode 100644 index 2e357035..00000000 --- a/app/src/main/java/com/texthip/thip/utils/toComposeColor.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.texthip.thip.utils - -import androidx.compose.ui.graphics.Color -import androidx.core.graphics.toColorInt - -fun String.toComposeColor(defaultColor: Color = Color.Gray): Color { - return try { - Color(this.toColorInt()) - } catch (e: IllegalArgumentException) { - defaultColor - } -} \ No newline at end of file From 4529f43310d3e8d2f865cd7a77ac503e4a197fa1 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:39:59 +0900 Subject: [PATCH 09/18] =?UTF-8?q?[fix]:=20=EB=8C=93=EA=B8=80=20bottom=20sh?= =?UTF-8?q?eet=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=EC=98=AC=EB=9D=BC=EA=B0=94?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EA=B0=99=EC=9D=B4=20=EC=98=AC=EB=9D=BC?= =?UTF-8?q?=EA=B0=80=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../note/component/CommentBottomSheet.kt | 45 ++++++++++--------- .../thip/utils/rooms/AdvancedImePadding.kt | 28 ++++++++++++ 3 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 79bce4b1..82f9c596 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt index 26e9da87..137b1cbb 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt @@ -1,6 +1,7 @@ package com.texthip.thip.ui.group.note.component import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -33,6 +34,7 @@ import com.texthip.thip.ui.group.note.viewmodel.CommentsUiState import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.rooms.advancedImePadding @Composable fun CommentBottomSheet( @@ -50,6 +52,7 @@ fun CommentBottomSheet( modifier = Modifier .fillMaxWidth() .height(600.dp) + .advancedImePadding() ) { Column( modifier = Modifier @@ -63,27 +66,29 @@ fun CommentBottomSheet( modifier = Modifier.padding(start = 20.dp, top = 20.dp, end = 20.dp) ) - if (uiState.isLoading) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - CircularProgressIndicator() - } - } else if (uiState.comments.isEmpty()) { - EmptyCommentView() - } else { - CommentLazyList( - commentList = uiState.comments, - isLoadingMore = uiState.isLoadingMore, - isLastPage = uiState.isLast, - onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, - onReplyClick = { commentId, nickname -> - replyingToCommentId = commentId - replyingToNickname = nickname + Box(modifier = Modifier.weight(1f)) { + if (uiState.isLoading) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator() } - ) + } else if (uiState.comments.isEmpty()) { + EmptyCommentView() + } else { + CommentLazyList( + commentList = uiState.comments, + isLoadingMore = uiState.isLoadingMore, + isLastPage = uiState.isLast, + onLoadMore = { onEvent(CommentsEvent.LoadMoreComments) }, + onReplyClick = { commentId, nickname -> + replyingToCommentId = commentId + replyingToNickname = nickname + } + ) + } } } diff --git a/app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt b/app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt new file mode 100644 index 00000000..9e963723 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/utils/rooms/AdvancedImePadding.kt @@ -0,0 +1,28 @@ +package com.texthip.thip.utils.rooms + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.imePadding +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.layout.findRootCoordinates +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp + +fun Modifier.advancedImePadding() = composed { + var consumePadding by remember { mutableStateOf(0) } + onGloballyPositioned { coordinates -> + val rootCoordinate = coordinates.findRootCoordinates() + val bottom = coordinates.positionInWindow().y + coordinates.size.height + + consumePadding = (rootCoordinate.size.height - bottom).toInt().coerceAtLeast(0) + } + .consumeWindowInsets(PaddingValues(bottom = (consumePadding / LocalDensity.current.density).dp)) + .imePadding() +} \ No newline at end of file From 45822bfc1c6b6a19477aaf1e33258a3d2601cea2 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:56:18 +0900 Subject: [PATCH 10/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20request,=20response=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/comments/request/CommentsLikesRequest.kt | 8 ++++++++ .../model/comments/response/CommentsLikesResponse.kt | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt create mode 100644 app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt b/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt new file mode 100644 index 00000000..ec0867b6 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsLikesRequest.kt @@ -0,0 +1,8 @@ +package com.texthip.thip.data.model.comments.request + +import kotlinx.serialization.Serializable + +@Serializable +data class CommentsLikesRequest( + val type: Boolean +) diff --git a/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt new file mode 100644 index 00000000..4f420f90 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsLikesResponse.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.comments.response + +import kotlinx.serialization.Serializable + +@Serializable +data class CommentsLikesResponse( + val commentId: Int, + val isLiked: Boolean, +) From 763c4e4561da5780ddd1deeb840586cdd494f71f Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:56:57 +0900 Subject: [PATCH 11/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20service=20=EC=83=9D=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/texthip/thip/data/service/CommentsService.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt index 8f37aa3b..a11b7210 100644 --- a/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt @@ -1,8 +1,12 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.comments.request.CommentsLikesRequest +import com.texthip.thip.data.model.comments.response.CommentsLikesResponse import com.texthip.thip.data.model.comments.response.CommentsResponse +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query @@ -13,4 +17,10 @@ interface CommentsService { @Query("postType") postType: String = "RECORD", @Query("cursor") cursor: String? = null, ): BaseResponse + + @POST("comments/{commentId}/likes") + suspend fun likeComment( + @Path("commentId") commentId: Long, + @Body response: CommentsLikesRequest + ): BaseResponse } \ No newline at end of file From 768afc3683f3ca19b70e5f6ae807678d03a60d33 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:57:03 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20repository=20=EC=83=9D=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/repository/CommentsRepository.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt index 8990465c..7fd8cf53 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt @@ -1,6 +1,7 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse +import com.texthip.thip.data.model.comments.request.CommentsLikesRequest import com.texthip.thip.data.service.CommentsService import javax.inject.Inject import javax.inject.Singleton @@ -20,4 +21,14 @@ class CommentsRepository @Inject constructor( cursor = cursor ).handleBaseResponse().getOrThrow() } + + suspend fun likeComment( + commentId: Long, + type: Boolean + ) = runCatching { + commentsService.likeComment( + commentId = commentId, + response = CommentsLikesRequest(type) + ).handleBaseResponse().getOrThrow() + } } \ No newline at end of file From bed9e297d5abca3fa5f158314aa11c08c292730a Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:57:22 +0900 Subject: [PATCH 13/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20viewmodel=20=EC=83=9D=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=97=90=20=EC=97=B0=EA=B2=B0=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../note/component/CommentBottomSheet.kt | 9 ++- .../ui/group/note/component/CommentItem.kt | 13 ++-- .../ui/group/note/component/CommentSection.kt | 64 ++++++------------ .../thip/ui/group/note/component/ReplyItem.kt | 13 ++-- .../group/note/viewmodel/CommentsViewModel.kt | 66 +++++++++++++++++++ 5 files changed, 100 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt index 137b1cbb..84038c4c 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentBottomSheet.kt @@ -86,7 +86,8 @@ fun CommentBottomSheet( onReplyClick = { commentId, nickname -> replyingToCommentId = commentId replyingToNickname = nickname - } + }, + onEvent = onEvent ) } } @@ -123,7 +124,8 @@ private fun CommentLazyList( isLoadingMore: Boolean, isLastPage: Boolean, onLoadMore: () -> Unit, - onReplyClick: (commentId: Int, nickname: String) -> Unit + onReplyClick: (commentId: Int, nickname: String) -> Unit, + onEvent: (CommentsEvent) -> Unit ) { val lazyListState = rememberLazyListState() @@ -149,7 +151,8 @@ private fun CommentLazyList( ) { comment -> CommentSection( commentItem = comment, - onReplyClick = onReplyClick + onReplyClick = onReplyClick, + onEvent = onEvent ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt index bda431fa..af7a5ad5 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentItem.kt @@ -8,10 +8,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -31,10 +27,9 @@ import com.texthip.thip.utils.color.hexToColor fun CommentItem( modifier: Modifier = Modifier, data: CommentList, - onReplyClick: (String) -> Unit = { } + onReplyClick: (String) -> Unit = { }, + onLikeClick: () -> Unit = {} ) { - var isLiked by remember { mutableStateOf(data.isLike) } - Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(12.dp) @@ -69,12 +64,12 @@ fun CommentItem( } Column( - modifier = Modifier.clickable(onClick = { isLiked = !isLiked }), + modifier = Modifier.clickable(onClick = onLikeClick), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(2.dp), ) { Icon( - painter = painterResource(if (isLiked) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), + painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), contentDescription = null, tint = Color.Unspecified ) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt index 09aaac0f..fcc2ea4d 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/CommentSection.kt @@ -4,56 +4,22 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.texthip.thip.data.model.comments.response.CommentList -import com.texthip.thip.ui.common.modal.drawVerticalScrollbar +import com.texthip.thip.ui.group.note.viewmodel.CommentsEvent import com.texthip.thip.ui.theme.ThipTheme -@Composable -fun CommentList( - modifier: Modifier = Modifier, - commentList: List, - onSendReply: (String, Int?, String?) -> Unit, - onReplyClick: (commentId: Int, nickname: String) -> Unit -) { - val scrollState = rememberScrollState() - - Box( - modifier = modifier - .fillMaxWidth() - .height(482.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(scrollState) - .drawVerticalScrollbar(scrollState), - ) { - commentList.forEach { commentItem -> - CommentSection( - commentItem = commentItem, - onSendReply = onSendReply, - onReplyClick = onReplyClick - ) - } - } - } -} - @Composable fun CommentSection( commentItem: CommentList, onSendReply: (String, Int?, String?) -> Unit = { _, _, _ -> }, - onReplyClick: (commentId: Int, nickname: String) -> Unit + onReplyClick: (commentId: Int, nickname: String) -> Unit, + onEvent: (CommentsEvent) -> Unit = { _ -> } ) { Box { Column( @@ -65,13 +31,23 @@ fun CommentSection( ) { CommentItem( data = commentItem, - onReplyClick = { onReplyClick(commentItem.commentId, commentItem.creatorNickname) } + onReplyClick = { onReplyClick(commentItem.commentId, commentItem.creatorNickname) }, + onLikeClick = { onEvent(CommentsEvent.LikeComment(commentItem.commentId)) } ) commentItem.replyList.forEach { reply -> ReplyItem( data = reply, - onReplyClick = { onReplyClick(commentItem.commentId, reply.creatorNickname) } + onReplyClick = { onReplyClick(commentItem.commentId, reply.creatorNickname) }, + onLikeClick = { + onEvent( + CommentsEvent.LikeReply( + commentItem.commentId, + reply.commentId + ) + ) + } + ) } } @@ -83,14 +59,14 @@ fun CommentSection( fun CommentSectionPreview() { ThipTheme { Column { - CommentList( - commentList = listOf( + CommentSection( + commentItem = CommentList( commentId = 1, creatorId = 1, creatorNickname = "User1", creatorProfileImageUrl = "https://example.com/image1.jpg", - aliasName= "칭호칭호", + aliasName = "칭호칭호", aliasColor = "#A0F8E8", content = "This is a comment.", postDate = "2023-10-01", @@ -98,8 +74,8 @@ fun CommentSectionPreview() { likeCount = 10, isDeleted = false, replyList = emptyList() - ) - ), + + ), onSendReply = { _, _, _ -> }, onReplyClick = { commentId, nickname -> // Handle reply click diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt index 30609440..d0729746 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/ReplyItem.kt @@ -8,10 +8,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -33,10 +29,9 @@ import com.texthip.thip.utils.color.hexToColor fun ReplyItem( modifier: Modifier = Modifier, data: ReplyList, - onReplyClick: () -> Unit = { } + onReplyClick: () -> Unit = { }, + onLikeClick: () -> Unit = {} ) { - var isLiked by remember { mutableStateOf(data.isLike) } - Row( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -92,12 +87,12 @@ fun ReplyItem( } Column( - modifier = Modifier.clickable(onClick = { isLiked = !isLiked }), + modifier = Modifier.clickable(onClick = onLikeClick), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(2.dp), ) { Icon( - painter = painterResource(if (isLiked) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), + painter = painterResource(if (data.isLike) R.drawable.ic_heart_center_filled else R.drawable.ic_heart_center), contentDescription = null, tint = Color.Unspecified ) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt index 5df10ccc..9c2508c9 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt @@ -21,6 +21,8 @@ data class CommentsUiState( sealed interface CommentsEvent { data object LoadMoreComments : CommentsEvent + data class LikeComment(val commentId: Int) : CommentsEvent // 댓글 좋아요 이벤트 + data class LikeReply(val parentCommentId: Int, val replyId: Int) : CommentsEvent // 대댓글 좋아요 이벤트 } @HiltViewModel @@ -43,6 +45,70 @@ class CommentsViewModel @Inject constructor( fun onEvent(event: CommentsEvent) { when (event) { is CommentsEvent.LoadMoreComments -> fetchComments(isRefresh = false) + is CommentsEvent.LikeComment -> toggleCommentLike(event.commentId) + is CommentsEvent.LikeReply -> toggleReplyLike(event.parentCommentId, event.replyId) + } + } + + private fun toggleCommentLike(commentId: Int) { + // 클릭한 댓글 찾기 + val comments = _uiState.value.comments + val commentIndex = comments.indexOfFirst { it.commentId == commentId } + if (commentIndex == -1) return + + val comment = comments[commentIndex] + val currentIsLiked = comment.isLike + val newLikeCount = if (currentIsLiked) comment.likeCount - 1 else comment.likeCount + 1 + + // 즉시 UI 업데이트 + val updatedComment = comment.copy(isLike = !currentIsLiked, likeCount = newLikeCount) + val newComments = comments.toMutableList().apply { set(commentIndex, updatedComment) } + _uiState.update { it.copy(comments = newComments) } + + viewModelScope.launch { + commentsRepository.likeComment(commentId.toLong(), !currentIsLiked) + .onFailure { + _uiState.update { + val originalComments = it.comments.toMutableList() + originalComments[commentIndex] = comment + it.copy(comments = originalComments) + } + } + } + } + + private fun toggleReplyLike(parentCommentId: Int, replyId: Int) { + // 부모 댓글 및 대댓글 찾기 + val comments = _uiState.value.comments + val parentCommentIndex = comments.indexOfFirst { it.commentId == parentCommentId } + if (parentCommentIndex == -1) return + + val parentComment = comments[parentCommentIndex] + val replyIndex = parentComment.replyList.indexOfFirst { it.commentId == replyId } + if (replyIndex == -1) return + + val reply = parentComment.replyList[replyIndex] + val currentIsLiked = reply.isLike + val newLikeCount = if (currentIsLiked) reply.likeCount - 1 else reply.likeCount + 1 + + // 즉시 UI 업데이트 + val updatedReply = reply.copy(isLike = !currentIsLiked, likeCount = newLikeCount) + val newReplyList = + parentComment.replyList.toMutableList().apply { set(replyIndex, updatedReply) } + val updatedParentComment = parentComment.copy(replyList = newReplyList) + val newComments = + comments.toMutableList().apply { set(parentCommentIndex, updatedParentComment) } + _uiState.update { it.copy(comments = newComments) } + + viewModelScope.launch { + commentsRepository.likeComment(replyId.toLong(), !currentIsLiked) + .onFailure { + _uiState.update { + val originalComments = it.comments.toMutableList() + originalComments[parentCommentIndex] = parentComment + it.copy(comments = originalComments) + } + } } } From 44ab3e02a0c7d90f08b2106feebad1d34167d826 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:06:39 +0900 Subject: [PATCH 14/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20request,=20response=20=EC=83=9D=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/comments/request/CommentsCreateRequest.kt | 11 +++++++++++ .../model/comments/response/CommentsCreateResponse.kt | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt create mode 100644 app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt diff --git a/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt b/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt new file mode 100644 index 00000000..da8d8438 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/comments/request/CommentsCreateRequest.kt @@ -0,0 +1,11 @@ +package com.texthip.thip.data.model.comments.request + +import kotlinx.serialization.Serializable + +@Serializable +data class CommentsCreateRequest( + val content: String, + val isReplyRequest: Boolean, + val parentId: Int? = null, + val postType: String, +) diff --git a/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt new file mode 100644 index 00000000..362a6df6 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/comments/response/CommentsCreateResponse.kt @@ -0,0 +1,8 @@ +package com.texthip.thip.data.model.comments.response + +import kotlinx.serialization.Serializable + +@Serializable +data class CommentsCreateResponse( + val commentId: Int, +) \ No newline at end of file From 387cdd098fcbb01dfe03e2dd9d044d0540781be6 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:06:47 +0900 Subject: [PATCH 15/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20service=20=EC=9E=91=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/texthip/thip/data/service/CommentsService.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt index a11b7210..4d50efdc 100644 --- a/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/CommentsService.kt @@ -1,7 +1,9 @@ package com.texthip.thip.data.service import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.comments.request.CommentsCreateRequest import com.texthip.thip.data.model.comments.request.CommentsLikesRequest +import com.texthip.thip.data.model.comments.response.CommentsCreateResponse import com.texthip.thip.data.model.comments.response.CommentsLikesResponse import com.texthip.thip.data.model.comments.response.CommentsResponse import retrofit2.http.Body @@ -23,4 +25,10 @@ interface CommentsService { @Path("commentId") commentId: Long, @Body response: CommentsLikesRequest ): BaseResponse + + @POST("comments/{postId}") + suspend fun createComment( + @Path("postId") postId: Long, + @Body request: CommentsCreateRequest + ): BaseResponse } \ No newline at end of file From 219cc80e7d32898f0a0aa84fa7467fcf699cef47 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:06:55 +0900 Subject: [PATCH 16/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20repository=20=EC=9E=91=EC=84=B1=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/CommentsRepository.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt index 7fd8cf53..90e618ee 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/CommentsRepository.kt @@ -1,6 +1,7 @@ package com.texthip.thip.data.repository import com.texthip.thip.data.model.base.handleBaseResponse +import com.texthip.thip.data.model.comments.request.CommentsCreateRequest import com.texthip.thip.data.model.comments.request.CommentsLikesRequest import com.texthip.thip.data.service.CommentsService import javax.inject.Inject @@ -31,4 +32,22 @@ class CommentsRepository @Inject constructor( response = CommentsLikesRequest(type) ).handleBaseResponse().getOrThrow() } + + suspend fun createComment( + postId: Long, + content: String, + isReplyRequest: Boolean, + parentId: Int? = null, + postType: String = "RECORD", + ) = runCatching { + commentsService.createComment( + postId = postId, + request = CommentsCreateRequest( + content = content, + isReplyRequest = isReplyRequest, + parentId = parentId, + postType = postType + ) + ).handleBaseResponse().getOrThrow() + } } \ No newline at end of file From e48a3f186c1153993e456802bc6925b2bd00c291 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:11:51 +0900 Subject: [PATCH 17/18] =?UTF-8?q?[feat]:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20viewmodel=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20screen?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/group/note/screen/GroupNoteScreen.kt | 17 +++++++++-- .../group/note/viewmodel/CommentsViewModel.kt | 29 ++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt index fdf6f93a..2c2c8944 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteScreen.kt @@ -56,6 +56,7 @@ import com.texthip.thip.ui.group.note.component.CommentBottomSheet import com.texthip.thip.ui.group.note.component.FilterHeaderSection import com.texthip.thip.ui.group.note.component.TextCommentCard import com.texthip.thip.ui.group.note.component.VoteCommentCard +import com.texthip.thip.ui.group.note.viewmodel.CommentsEvent import com.texthip.thip.ui.group.note.viewmodel.CommentsViewModel import com.texthip.thip.ui.group.note.viewmodel.GroupNoteEvent import com.texthip.thip.ui.group.note.viewmodel.GroupNoteUiState @@ -470,7 +471,10 @@ fun GroupNoteContent( if (isCommentBottomSheetVisible && selectedPostForComment != null) { LaunchedEffect(selectedPostForComment) { - commentsViewModel.initialize(postId = selectedPostForComment!!.postId.toLong()) + commentsViewModel.initialize( + postId = selectedPostForComment!!.postId.toLong(), + postType = selectedPostForComment!!.postType + ) } CommentBottomSheet( @@ -480,8 +484,15 @@ fun GroupNoteContent( isCommentBottomSheetVisible = false selectedPostForComment = null }, - onSendReply = { replyText, commentId, replyTo -> - // TODO: 댓글 전송 로직 구현 + onSendReply = { text, parentId, _ -> + if (text.isNotBlank()) { + commentsViewModel.onEvent( + CommentsEvent.CreateComment( + content = text, + parentId = parentId + ) + ) + } } ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt index 9c2508c9..4247ab32 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/CommentsViewModel.kt @@ -23,6 +23,7 @@ sealed interface CommentsEvent { data object LoadMoreComments : CommentsEvent data class LikeComment(val commentId: Int) : CommentsEvent // 댓글 좋아요 이벤트 data class LikeReply(val parentCommentId: Int, val replyId: Int) : CommentsEvent // 대댓글 좋아요 이벤트 + data class CreateComment(val content: String, val parentId: Int?) : CommentsEvent } @HiltViewModel @@ -35,10 +36,12 @@ class CommentsViewModel @Inject constructor( private var nextCursor: String? = null private var currentPostId: Long = -1L + private var currentPostType: String = "RECORD" - fun initialize(postId: Long) { + fun initialize(postId: Long, postType: String) { if (currentPostId == postId) return this.currentPostId = postId + this.currentPostType = postType fetchComments(isRefresh = true) } @@ -47,6 +50,30 @@ class CommentsViewModel @Inject constructor( is CommentsEvent.LoadMoreComments -> fetchComments(isRefresh = false) is CommentsEvent.LikeComment -> toggleCommentLike(event.commentId) is CommentsEvent.LikeReply -> toggleReplyLike(event.parentCommentId, event.replyId) + is CommentsEvent.CreateComment -> createComment( + content = event.content, + parentId = event.parentId + ) + } + } + + private fun createComment(content: String, parentId: Int?) { + if (content.isBlank()) return + + viewModelScope.launch { + val isReply = parentId != null + + commentsRepository.createComment( + postId = currentPostId, + content = content, + isReplyRequest = isReply, + parentId = parentId, + postType = currentPostType + ).onSuccess { + fetchComments(isRefresh = true) + }.onFailure { throwable -> + _uiState.update { it.copy(error = "댓글 작성 실패: ${throwable.message}") } + } } } From e8cc4e48d4dcb9f31e26ec0f6675e1b9616722e9 Mon Sep 17 00:00:00 2001 From: Naeun Kim <102296721+Nico1eKim@users.noreply.github.com> Date: Thu, 14 Aug 2025 02:35:39 +0900 Subject: [PATCH 18/18] =?UTF-8?q?[refactor]:=20alias=20color,=20category?= =?UTF-8?q?=20color=20=EC=88=98=EC=A0=95=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/model/rooms/response/RoomsPlayingResponse.kt | 1 + .../thip/data/model/rooms/response/RoomsUsersResponse.kt | 1 + .../java/com/texthip/thip/ui/common/header/ProfileBar.kt | 2 +- .../thip/ui/group/room/component/GroupRoomHeader.kt | 9 +++------ .../thip/ui/group/room/component/GroupRoomMatesList.kt | 6 ++++-- .../thip/ui/group/room/screen/GroupRoomMatesScreen.kt | 2 ++ .../texthip/thip/ui/group/room/screen/GroupRoomScreen.kt | 4 +++- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt b/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt index 978c97fc..f05496c6 100644 --- a/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPlayingResponse.kt @@ -12,6 +12,7 @@ data class RoomsPlayingResponse( val progressStartDate: String, val progressEndDate: String, val category: String, + val categoryColor: String, val roomDescription: String, val memberCount: Int, val recruitCount: Int, diff --git a/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt b/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt index b1c3e442..92d6a0d1 100644 --- a/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt +++ b/app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt @@ -12,6 +12,7 @@ data class UserList( val userId: Int, val nickname: String, val imageUrl: String, + val aliasColor: String, val aliasName: String, val followerCount: Int, ) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt b/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt index e0d8efd6..ee49681d 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/header/ProfileBar.kt @@ -32,7 +32,7 @@ fun ProfileBar( profileImage: String, topText: String, bottomText: String, - bottomTextColor: Color = colors.NeonGreen, // todo: 서버에서 색 보내주는걸로 받기? + bottomTextColor: Color = colors.NeonGreen, showSubscriberInfo: Boolean, subscriberCount: Int = 0, hoursAgo: String = "", diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt b/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt index faed3109..195ff46a 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomHeader.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import com.texthip.thip.R import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography -import com.texthip.thip.utils.type.GenreColor +import com.texthip.thip.utils.color.hexToColor @Composable fun GroupRoomHeader( @@ -37,12 +37,9 @@ fun GroupRoomHeader( progressEndDate: String, memberCount: Int, category: String, - color: String = "RED", // TODO: 서버에서 색상 추가해주면 수정, + categoryColor: String = "#A0F8E8", onNavigateToMates: () -> Unit = { } ) { - val categoryColorEnum = GenreColor.fromString(color) - val categoryColor = categoryColorEnum.colorProvider() - Column( modifier = Modifier.padding(horizontal = 20.dp) ) { @@ -176,7 +173,7 @@ fun GroupRoomHeader( Text( text = category, style = typography.info_m500_s12, - color = categoryColor + color = hexToColor(categoryColor) ) } } diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt b/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt index af83361a..158b31b6 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/component/GroupRoomMatesList.kt @@ -13,6 +13,7 @@ import com.texthip.thip.data.model.rooms.response.RoomsUsersResponse import com.texthip.thip.data.model.rooms.response.UserList import com.texthip.thip.ui.common.header.ProfileBar import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.utils.color.hexToColor @Composable fun GroupRoomMatesList( @@ -31,8 +32,7 @@ fun GroupRoomMatesList( profileImage = member.imageUrl, topText = member.nickname, bottomText = member.aliasName, -// bottomTextColor = member.aliasColor, - bottomTextColor = colors.ScienceIt, // TODO: 서버에서 보내주는 색상으로 수정 + bottomTextColor = hexToColor(member.aliasColor), showSubscriberInfo = true, subscriberCount = member.followerCount ) { onUserClick(member.userId) } @@ -58,6 +58,7 @@ private fun GroupRoomMatesListPreview() { userId = 1, nickname = "김희용", aliasName = "문학가", + aliasColor= "#A0F8E8", imageUrl = "https://example.com/image1.jpg", followerCount = 100 ), @@ -65,6 +66,7 @@ private fun GroupRoomMatesListPreview() { userId = 2, nickname = "노성준", aliasName = "문학가", + aliasColor= "#A0F8E8", imageUrl = "https://example.com/image1.jpg", followerCount = 100 ), diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt index ce2dfa52..3748aff7 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomMatesScreen.kt @@ -104,6 +104,7 @@ private fun GroupRoomMatesScreenPreview() { userId = 1, nickname = "김희용", aliasName = "문학가", + aliasColor = "#A0F8E8", imageUrl = "https://example.com/image1.jpg", followerCount = 100 ), @@ -111,6 +112,7 @@ private fun GroupRoomMatesScreenPreview() { userId = 2, nickname = "노성준", aliasName = "문학가", + aliasColor = "#A0F8E8", imageUrl = "https://example.com/image1.jpg", followerCount = 100 ), diff --git a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt index 9ac9b3b8..19ccfba6 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/room/screen/GroupRoomScreen.kt @@ -162,7 +162,8 @@ fun GroupRoomContent( progressStartDate = roomDetails.progressStartDate, progressEndDate = roomDetails.progressEndDate, memberCount = roomDetails.memberCount, - category = roomDetails.category + category = roomDetails.category, + categoryColor = roomDetails.categoryColor, ) { onNavigateToMates() } @@ -282,6 +283,7 @@ private fun GroupRoomScreenPreview() { progressStartDate = "2023.10.01", progressEndDate = "2023.10.31", category = "문학", + categoryColor = "#A0F8E8", roomDescription = "‘시집만 읽는 사람들’ 3월 모임입니다.", memberCount = 22, recruitCount = 30,