diff --git a/data/src/main/kotlin/com/signal/data/api/FeedApi.kt b/data/src/main/kotlin/com/signal/data/api/FeedApi.kt index 66450279..5ae18f88 100644 --- a/data/src/main/kotlin/com/signal/data/api/FeedApi.kt +++ b/data/src/main/kotlin/com/signal/data/api/FeedApi.kt @@ -1,7 +1,8 @@ package com.signal.data.api -import com.signal.data.model.feed.request.PostRequest -import com.signal.data.model.feed.response.FetchPostCommentsResponse +import com.signal.data.model.feed.request.CreateCommentRequest +import com.signal.data.model.feed.request.CreatePostRequest +import com.signal.data.model.feed.response.FetchCommentsResponse import com.signal.data.model.feed.response.FetchPostDetailsResponse import com.signal.data.model.feed.response.FetchPostsResponse import com.signal.domain.enums.Tag @@ -21,7 +22,7 @@ interface FeedApi { @POST(SignalUrl.Feed.CreatePost) suspend fun createPost( - @Body postRequest: PostRequest, + @Body createPostRequest: CreatePostRequest, ) @GET(SignalUrl.Feed.Details) @@ -29,8 +30,14 @@ interface FeedApi { @Path("feed_id") feedId: Long, ): FetchPostDetailsResponse - @GET(SignalUrl.Feed.Comment) + @GET(SignalUrl.Feed.Comments) suspend fun fetchPostComments( @Path("feed_id") feedId: Long, - ): FetchPostCommentsResponse + ): FetchCommentsResponse + + @POST(SignalUrl.Feed.CreateComment) + suspend fun createComment( + @Path("feed_id") feedId: Long, + @Body createCommentRequest: CreateCommentRequest, + ) } diff --git a/data/src/main/kotlin/com/signal/data/api/SignalUrl.kt b/data/src/main/kotlin/com/signal/data/api/SignalUrl.kt index d6ecbda4..fd750249 100644 --- a/data/src/main/kotlin/com/signal/data/api/SignalUrl.kt +++ b/data/src/main/kotlin/com/signal/data/api/SignalUrl.kt @@ -16,7 +16,8 @@ object SignalUrl { const val CreatePost = "$feed/user" const val List = "$feed/list" const val Details = "$feed/{feed_id}" - const val Comment = "$feed/comment" + const val Comments = "$feed/comment/{feed_id}" + const val CreateComment = "$feed/{feed_id}" } object Attachment { diff --git a/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSource.kt b/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSource.kt index 86cf89f7..f755db8e 100644 --- a/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSource.kt +++ b/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSource.kt @@ -1,7 +1,8 @@ package com.signal.data.datasource.feed -import com.signal.data.model.feed.request.PostRequest -import com.signal.data.model.feed.response.FetchPostCommentsResponse +import com.signal.data.model.feed.request.CreateCommentRequest +import com.signal.data.model.feed.request.CreatePostRequest +import com.signal.data.model.feed.response.FetchCommentsResponse import com.signal.data.model.feed.response.FetchPostDetailsResponse import com.signal.data.model.feed.response.FetchPostsResponse import com.signal.domain.enums.Tag @@ -13,9 +14,14 @@ interface FeedDataSource { size: Long, ): FetchPostsResponse - suspend fun createPost(postRequest: PostRequest) + suspend fun createPost(createPostRequest: CreatePostRequest) suspend fun fetchPostDetails(feedId: Long): FetchPostDetailsResponse - suspend fun fetchPostComments(feedId: Long): FetchPostCommentsResponse + suspend fun fetchPostComments(feedId: Long): FetchCommentsResponse + + suspend fun createComment( + feedId: Long, + createCommentRequest: CreateCommentRequest, + ) } diff --git a/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSourceImpl.kt b/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSourceImpl.kt index 8b1c90ec..92f74241 100644 --- a/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSourceImpl.kt +++ b/data/src/main/kotlin/com/signal/data/datasource/feed/FeedDataSourceImpl.kt @@ -1,8 +1,9 @@ package com.signal.data.datasource.feed import com.signal.data.api.FeedApi -import com.signal.data.model.feed.request.PostRequest -import com.signal.data.model.feed.response.FetchPostCommentsResponse +import com.signal.data.model.feed.request.CreateCommentRequest +import com.signal.data.model.feed.request.CreatePostRequest +import com.signal.data.model.feed.response.FetchCommentsResponse import com.signal.data.model.feed.response.FetchPostDetailsResponse import com.signal.data.model.feed.response.FetchPostsResponse import com.signal.data.util.ExceptionHandler @@ -23,9 +24,9 @@ class FeedDataSourceImpl( ) }.sendRequest() - override suspend fun createPost(postRequest: PostRequest) = + override suspend fun createPost(createPostRequest: CreatePostRequest) = ExceptionHandler().httpRequest { - feedApi.createPost(postRequest = postRequest) + feedApi.createPost(createPostRequest = createPostRequest) }.sendRequest() override suspend fun fetchPostDetails(feedId: Long) = @@ -34,7 +35,17 @@ class FeedDataSourceImpl( }.sendRequest() override suspend fun fetchPostComments(feedId: Long) = - ExceptionHandler().httpRequest { + ExceptionHandler().httpRequest { feedApi.fetchPostComments(feedId = feedId) }.sendRequest() + + override suspend fun createComment( + feedId: Long, + createCommentRequest: CreateCommentRequest, + ) = ExceptionHandler().httpRequest { + feedApi.createComment( + feedId = feedId, + createCommentRequest = createCommentRequest, + ) + }.sendRequest() } diff --git a/data/src/main/kotlin/com/signal/data/model/feed/request/CreateCommentRequest.kt b/data/src/main/kotlin/com/signal/data/model/feed/request/CreateCommentRequest.kt new file mode 100644 index 00000000..546735ba --- /dev/null +++ b/data/src/main/kotlin/com/signal/data/model/feed/request/CreateCommentRequest.kt @@ -0,0 +1,7 @@ +package com.signal.data.model.feed.request + +import com.google.gson.annotations.SerializedName + +data class CreateCommentRequest( + @SerializedName("content") val content: String, +) diff --git a/data/src/main/kotlin/com/signal/data/model/feed/request/PostRequest.kt b/data/src/main/kotlin/com/signal/data/model/feed/request/CreatePostRequest.kt similarity index 91% rename from data/src/main/kotlin/com/signal/data/model/feed/request/PostRequest.kt rename to data/src/main/kotlin/com/signal/data/model/feed/request/CreatePostRequest.kt index 2c5c1d29..a24dfebc 100644 --- a/data/src/main/kotlin/com/signal/data/model/feed/request/PostRequest.kt +++ b/data/src/main/kotlin/com/signal/data/model/feed/request/CreatePostRequest.kt @@ -3,7 +3,7 @@ package com.signal.data.model.feed.request import com.google.gson.annotations.SerializedName import com.signal.domain.enums.Tag -data class PostRequest( +data class CreatePostRequest( @SerializedName("title") val title: String, @SerializedName("content") val content: String, @SerializedName("image") val image: String?, diff --git a/data/src/main/kotlin/com/signal/data/model/feed/response/FetchPostCommentsResponse.kt b/data/src/main/kotlin/com/signal/data/model/feed/response/FetchCommentsResponse.kt similarity index 72% rename from data/src/main/kotlin/com/signal/data/model/feed/response/FetchPostCommentsResponse.kt rename to data/src/main/kotlin/com/signal/data/model/feed/response/FetchCommentsResponse.kt index ff3591a6..f0ebe747 100644 --- a/data/src/main/kotlin/com/signal/data/model/feed/response/FetchPostCommentsResponse.kt +++ b/data/src/main/kotlin/com/signal/data/model/feed/response/FetchCommentsResponse.kt @@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName import com.signal.domain.entity.PostCommentsEntity import java.time.LocalDateTime -data class FetchPostCommentsResponse( +data class FetchCommentsResponse( @SerializedName("comment") val comments: List, ) { data class Comment( @@ -12,16 +12,18 @@ data class FetchPostCommentsResponse( @SerializedName("content") val content: String, @SerializedName("is_mine") val isMine: Boolean, @SerializedName("date_time") val dateTime: LocalDateTime, + @SerializedName("profile") val profile: String, ) } -fun FetchPostCommentsResponse.toEntity() = PostCommentsEntity( +fun FetchCommentsResponse.toEntity() = PostCommentsEntity( comments = this.comments.map { it.toEntity() }, ) -fun FetchPostCommentsResponse.Comment.toEntity() = PostCommentsEntity.CommentEntity( +fun FetchCommentsResponse.Comment.toEntity() = PostCommentsEntity.CommentEntity( writer = this.writer, content = this.content, isMine = this.isMine, dateTime = this.dateTime, + profile = this.profile, ) diff --git a/data/src/main/kotlin/com/signal/data/repository/FeedRepositoryImpl.kt b/data/src/main/kotlin/com/signal/data/repository/FeedRepositoryImpl.kt index 946b6504..e29ccc37 100644 --- a/data/src/main/kotlin/com/signal/data/repository/FeedRepositoryImpl.kt +++ b/data/src/main/kotlin/com/signal/data/repository/FeedRepositoryImpl.kt @@ -1,7 +1,8 @@ package com.signal.data.repository import com.signal.data.datasource.feed.FeedDataSource -import com.signal.data.model.feed.request.PostRequest +import com.signal.data.model.feed.request.CreateCommentRequest +import com.signal.data.model.feed.request.CreatePostRequest import com.signal.data.model.feed.response.toEntity import com.signal.domain.PostsEntity import com.signal.domain.enums.Tag @@ -27,7 +28,7 @@ class FeedRepositoryImpl( tag: Tag, ) = runCatching { feedDataSource.createPost( - PostRequest( + CreatePostRequest( title = title, content = content, image = image, @@ -43,4 +44,14 @@ class FeedRepositoryImpl( override suspend fun fetchPostComments(feedId: Long) = runCatching { feedDataSource.fetchPostComments(feedId = feedId).toEntity() } + + override suspend fun createComment( + feedId: Long, + content: String, + ) = kotlin.runCatching { + feedDataSource.createComment( + feedId = feedId, + createCommentRequest = CreateCommentRequest(content = content), + ) + } } diff --git a/domain/src/main/kotlin/com/signal/domain/entity/PostCommentsEntity.kt b/domain/src/main/kotlin/com/signal/domain/entity/PostCommentsEntity.kt index 72ba9261..ee9334ec 100644 --- a/domain/src/main/kotlin/com/signal/domain/entity/PostCommentsEntity.kt +++ b/domain/src/main/kotlin/com/signal/domain/entity/PostCommentsEntity.kt @@ -10,5 +10,6 @@ data class PostCommentsEntity( val content: String, val isMine: Boolean, val dateTime: LocalDateTime, + val profile: String, ) } diff --git a/domain/src/main/kotlin/com/signal/domain/repository/FeedRepository.kt b/domain/src/main/kotlin/com/signal/domain/repository/FeedRepository.kt index 5733387f..f383832d 100644 --- a/domain/src/main/kotlin/com/signal/domain/repository/FeedRepository.kt +++ b/domain/src/main/kotlin/com/signal/domain/repository/FeedRepository.kt @@ -22,4 +22,9 @@ interface FeedRepository { suspend fun fetchPostDetails(feedId: Long): Result suspend fun fetchPostComments(feedId: Long): Result + + suspend fun createComment( + feedId: Long, + content: String, + ): Result } diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/CommentDialog.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/CommentDialog.kt index 3793e100..b0bdfbb7 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/CommentDialog.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/CommentDialog.kt @@ -20,9 +20,12 @@ import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -35,14 +38,23 @@ import com.signal.signal_android.designsystem.foundation.Body2 import com.signal.signal_android.designsystem.foundation.BodyLarge2 import com.signal.signal_android.designsystem.foundation.SignalColor import com.signal.signal_android.designsystem.textfield.SignalTextField +import kotlinx.coroutines.flow.SharedFlow @Composable internal fun CommentDialog( - state: FeedState, - fetchPostComments: suspend () -> Unit, + feedViewModel: FeedViewModel, ) { + val state by feedViewModel.state.collectAsState() + + val focusManager = LocalFocusManager.current + LaunchedEffect(Unit) { - fetchPostComments() + feedViewModel.sideEffect.collect { + when (it) { + is FeedSideEffect.ClearFocus -> focusManager.clearFocus() + else -> {} + } + } } Box(contentAlignment = Alignment.BottomCenter) { @@ -81,9 +93,9 @@ internal fun CommentDialog( .imePadding(), ) { Input( - comment = { "comment" }, - onCommentChange = { }, - onClick = {}, + comment = { state.comment }, + onCommentChange = feedViewModel::setComment, + onClick = feedViewModel::createComment, ) } } @@ -101,7 +113,7 @@ private fun Input( ) { SignalTextField( modifier = Modifier.fillMaxWidth(0.8f), - value = "", + value = comment(), onValueChange = onCommentChange, hint = stringResource(id = R.string.comment_dialog_input_comment), ) @@ -110,6 +122,7 @@ private fun Input( modifier = Modifier.weight(0.2f), text = stringResource(id = R.string.my_page_secession_check), onClick = onClick, + enabled = comment().isNotBlank(), ) } } @@ -125,7 +138,7 @@ private fun Comments( ) { items(commentEntities) { Comment( - profileImageUrl = "", + profileImageUrl = it.profile, writer = it.writer, time = it.dateTime.toString(), content = it.content, diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/Feed.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/Feed.kt index af425f54..a9711a6c 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/Feed.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/Feed.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -73,7 +74,7 @@ internal fun Feed( ) LaunchedEffect(Unit) { - if(state.isPostsEmpty) { + if (state.isPostsEmpty) { feedViewModel.fetchPosts() } } @@ -124,6 +125,7 @@ internal fun Feed( onDismissRequest = { expanded = -1 }, onDelete = { showDialog = true }, ) + Column( modifier = Modifier .fillMaxWidth() @@ -144,7 +146,10 @@ internal fun Feed( text = stringResource(id = R.string.feed_posts_is_empty), ) Body( - modifier = Modifier.signalClickable(onClick = moveToCreatePost), + modifier = Modifier.signalClickable( + onClick = moveToCreatePost, + enabled = state.isPostsEmpty + ), text = stringResource(id = R.string.feed_posts_add), color = SignalColor.Primary100, ) @@ -292,11 +297,16 @@ internal fun Post( .padding(12.dp), verticalAlignment = Alignment.CenterVertically, ) { - AsyncImage( - modifier = Modifier.size(48.dp), - model = imageUrl, - contentDescription = stringResource(id = R.string.feed_image), - ) + imageUrl?.run { + AsyncImage( + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(4.dp)), + model = this, + contentDescription = stringResource(id = R.string.feed_image), + contentScale = ContentScale.Crop, + ) + } Spacer(modifier = Modifier.width(8.dp)) Column(verticalArrangement = Arrangement.Center) { Row( @@ -316,7 +326,7 @@ internal fun Post( FeedDropDownMenu( expanded = expanded, onDismissRequest = onDismissRequest, - isMine = false, + isMine = true, onEdit = onEdit, onDelete = onDelete, onReport = onReport, diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedDetails.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedDetails.kt index 8965cd5a..a2dc1cc5 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedDetails.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedDetails.kt @@ -91,10 +91,7 @@ internal fun FeedDetails( ModalBottomSheetLayout( sheetContent = { - CommentDialog( - state = state, - fetchPostComments = feedViewModel::fetchPostComments, - ) + CommentDialog(feedViewModel = feedViewModel) }, sheetState = sheetState, sheetShape = RoundedCornerShape( @@ -146,6 +143,7 @@ internal fun FeedDetails( .signalClickable( onClick = { coroutineScope.launch { + feedViewModel.fetchPostComments() sheetState.show() } }, diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedSideEffect.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedSideEffect.kt index af7fe8f6..777fea0b 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedSideEffect.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedSideEffect.kt @@ -2,4 +2,5 @@ package com.signal.signal_android.feature.main.feed sealed interface FeedSideEffect { object PostSuccess : FeedSideEffect + object ClearFocus: FeedSideEffect } diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedState.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedState.kt index b23c8355..dc600771 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedState.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedState.kt @@ -17,12 +17,13 @@ data class FeedState( val feedId: Long, val image: String, val comments: List, + val comment: String, ) { companion object { fun getDefaultState() = FeedState( posts = listOf(), tag = Tag.GENERAL, - page = 1, + page = 0, size = 8, isPostsEmpty = true, title = "", @@ -39,6 +40,7 @@ data class FeedState( feedId = 0L, image = "", comments = listOf(), + comment = "" ) } } diff --git a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedViewModel.kt b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedViewModel.kt index 187b0a03..de87b963 100644 --- a/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedViewModel.kt +++ b/presentation/src/main/java/com/signal/signal_android/feature/main/feed/FeedViewModel.kt @@ -81,6 +81,7 @@ internal class FeedViewModel( with(state.value) { viewModelScope.launch(Dispatchers.IO) { feedRepository.fetchPostComments(feedId).onSuccess { + _comments.clear() _comments.addAll(it.comments) setState(copy(comments = _comments)) } @@ -88,6 +89,22 @@ internal class FeedViewModel( } } + internal fun createComment() { + with(state.value) { + viewModelScope.launch(Dispatchers.IO) { + feedRepository.createComment( + feedId = feedId, + content = comment, + ).onSuccess { + postSideEffect(FeedSideEffect.ClearFocus) + fetchPostComments() + }.onFailure { + + } + } + } + } + internal fun setTitle(title: String) { setState(state.value.copy(title = title)) } @@ -107,4 +124,8 @@ internal class FeedViewModel( fetchPosts() } } + + internal fun setComment(comment: String) { + setState(state.value.copy(comment = comment)) + } }