From d32f4860c5b183b3107b7dabd42cc358fde23db7 Mon Sep 17 00:00:00 2001 From: kimjw Date: Wed, 4 Feb 2026 20:49:46 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[Feat]=20comment=20dto=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/todomain/comment/CommentMapper.kt | 32 ++++++++++++ .../mapper/todto/comment/CommentMapper.kt | 21 ++++++++ .../datasource/CommentRemoteDataSource.kt | 14 ++++++ .../CommentRemoteDataSourceImpl.kt | 26 ++++++++++ .../comment/CommentListPageableRequestDto.kt | 14 ++++++ .../comment/RegisterCommentRequestDto.kt | 12 +++++ .../comment/GetCommentListResponseDto.kt | 42 ++++++++++++++++ .../data/remote/service/CommentService.kt | 30 ++++++++++++ .../repositoryimpl/CommentRepositoryImpl.kt | 49 +++++++++++++++++++ .../java/org/sopt/certi/di/ServiceModule.kt | 6 +++ .../certi/domain/model/comment/CommentData.kt | 4 +- .../comment/CommentListPageableRequest.kt | 7 +++ .../model/comment/RegisterCommentRequest.kt | 6 +++ .../domain/repository/CommentRepository.kt | 12 +++++ .../certi/domain/type/CertAcquireStateType.kt | 6 --- .../usecase/comment/DeleteCommentUseCase.kt | 12 +++++ .../usecase/comment/GetCommentListUseCase.kt | 14 ++++++ .../usecase/comment/LikeCommentUseCase.kt | 12 +++++ .../usecase/comment/RegisterCommentUseCase.kt | 13 +++++ .../ui/certdetail/CertDetailViewModel.kt | 10 +++- .../component/comment/CommentItem.kt | 12 +++-- .../screen/CertDetailCommentScreen.kt | 22 ++++----- 22 files changed, 352 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt create mode 100644 app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/CommentListPageableRequestDto.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt create mode 100644 app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt delete mode 100644 app/src/main/java/org/sopt/certi/domain/type/CertAcquireStateType.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt new file mode 100644 index 00000000..40bc4291 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt @@ -0,0 +1,32 @@ +package org.sopt.certi.data.mapper.todomain.comment + +import org.sopt.certi.data.remote.dto.response.comment.CommentItemResponseDto +import org.sopt.certi.data.remote.dto.response.comment.GetCommentListResponseDto +import org.sopt.certi.domain.model.comment.CommentData +import org.sopt.certi.domain.model.comment.CommentItemData +import org.sopt.certi.domain.type.CertStateType + +fun GetCommentListResponseDto.toDomain() : CommentData { + return CommentData( + content = content.map { it.toDomain() }, + totalPages = totalPages, + totalElements = totalElements, + isLast = isLast + ) +} + +fun CommentItemResponseDto.toDomain() : CommentItemData { + return CommentItemData( + commentId = commentId, + userId = userId, + nickName = nickName, + content = content, + userMajor = userMajor, + userJob = userJob, + state = CertStateType.valueOf(state), + createdTime = createdTime, + lastModifiedTime = lastModifiedTime, + isLike = isLike, + likeCount = likeCount + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt new file mode 100644 index 00000000..f9442820 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt @@ -0,0 +1,21 @@ +package org.sopt.certi.data.mapper.todto.comment + +import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto +import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto +import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import org.sopt.certi.domain.model.comment.RegisterCommentRequest + +fun RegisterCommentRequest.toDto() : RegisterCommentRequestDto { + return RegisterCommentRequestDto( + content = content, + certificationId = certificationId + ) +} + +fun CommentListPageableRequest.toDto() : CommentListPageableRequestDto { + return CommentListPageableRequestDto( + page = page, + size = size, + sort = sort + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt b/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt new file mode 100644 index 00000000..d99125a0 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt @@ -0,0 +1,14 @@ +package org.sopt.certi.data.remote.datasource + +import org.sopt.certi.data.remote.dto.base.ApiResponse +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto +import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto +import org.sopt.certi.data.remote.dto.response.comment.GetCommentListResponseDto + +interface CommentRemoteDataSource { + suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequestDto): ApiResponse + suspend fun registerComment(registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse + suspend fun likeComment(commentId: Long): NullableApiResponse + suspend fun deleteComment(commentId: Long): NullableApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt new file mode 100644 index 00000000..74f3374c --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt @@ -0,0 +1,26 @@ +package org.sopt.certi.data.remote.datasourceimpl + +import org.sopt.certi.data.remote.datasource.CommentRemoteDataSource +import org.sopt.certi.data.remote.dto.base.ApiResponse +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto +import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto +import org.sopt.certi.data.remote.dto.response.comment.GetCommentListResponseDto +import org.sopt.certi.data.remote.service.CommentService +import javax.inject.Inject + +class CommentRemoteDataSourceImpl @Inject constructor( + private val commentService: CommentService +) : CommentRemoteDataSource { + override suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequestDto): ApiResponse = + commentService.getCommentList(certificationId, pageable) + + override suspend fun registerComment(registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse = + commentService.registerComment(registerCommentRequest) + + override suspend fun likeComment(commentId: Long): NullableApiResponse = + commentService.likeComment(commentId) + + override suspend fun deleteComment(commentId: Long): NullableApiResponse = + commentService.deleteComment(commentId) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/CommentListPageableRequestDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/CommentListPageableRequestDto.kt new file mode 100644 index 00000000..9d2070bd --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/CommentListPageableRequestDto.kt @@ -0,0 +1,14 @@ +package org.sopt.certi.data.remote.dto.request.comment + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CommentListPageableRequestDto( + @SerialName("page") + val page: Int, + @SerialName("size") + val size: Int, + @SerialName("sort") + val sort: List +) diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt new file mode 100644 index 00000000..cf83318c --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.data.remote.dto.request.comment + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RegisterCommentRequestDto( + @SerialName("content") + val content: String, + @SerialName("certificationId") + val certificationId: Long +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt new file mode 100644 index 00000000..cc57ee15 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt @@ -0,0 +1,42 @@ +package org.sopt.certi.data.remote.dto.response.comment + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetCommentListResponseDto( + @SerialName("content") + val content: List, + @SerialName("totalPages") + val totalPages: Int, + @SerialName("totalElements") + val totalElements: Int, + @SerialName("isLast") + val isLast: Boolean +) + +@Serializable +data class CommentItemResponseDto( + @SerialName("commentId") + val commentId: Long, + @SerialName("userId") + val userId: Long, + @SerialName("nickName") + val nickName: String, + @SerialName("content") + val content: String, + @SerialName("userMajor") + val userMajor: String, + @SerialName("userJob") + val userJob: String, + @SerialName("state") + val state: String, + @SerialName("likeCount") + val likeCount: Int, + @SerialName("createdTime") + val createdTime: String, + @SerialName("lastModifiedTime") + val lastModifiedTime: String, + @SerialName("isLike") + val isLike: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt new file mode 100644 index 00000000..2a8063b8 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt @@ -0,0 +1,30 @@ +package org.sopt.certi.data.remote.service + +import org.sopt.certi.data.remote.dto.base.ApiResponse +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto +import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto +import org.sopt.certi.data.remote.dto.response.comment.GetCommentListResponseDto +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface CommentService { + @GET("api/v1/comments") + suspend fun getCommentList( + @Query("certificationId") certificationId: Long, + @Query("pageable") pageable: CommentListPageableRequestDto + ) : ApiResponse + + @POST("api/v1/comments") + suspend fun registerComment(@Body registerCommentRequest: RegisterCommentRequestDto) : NullableApiResponse + + @POST("api/v1/comments/{commentId}/like") + suspend fun likeComment(@Path("commentId") commentId: Long) : NullableApiResponse + + @DELETE("api/v1/comments/{commentId}") + suspend fun deleteComment(@Path("commentId") commentId: Long) : NullableApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt new file mode 100644 index 00000000..60f835fb --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -0,0 +1,49 @@ +package org.sopt.certi.data.repositoryimpl + +import org.sopt.certi.data.mapper.todomain.comment.toDomain +import org.sopt.certi.data.mapper.todto.comment.toDto +import org.sopt.certi.data.remote.datasource.CommentRemoteDataSource +import org.sopt.certi.data.remote.util.HttpResponseHandler.handleApiResponse +import org.sopt.certi.data.remote.util.HttpResponseHandler.handleNullableApiResponse +import org.sopt.certi.domain.model.comment.CommentData +import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import org.sopt.certi.domain.model.comment.RegisterCommentRequest +import org.sopt.certi.domain.repository.CommentRepository +import javax.inject.Inject + +class CommentRepositoryImpl @Inject constructor( + private val commentRemoteDataSource: CommentRemoteDataSource +) : CommentRepository { + override suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequest): Result { + return runCatching { + commentRemoteDataSource.getCommentList(certificationId, pageable.toDto()) + .handleApiResponse() + .getOrThrow() + .toDomain() + } + } + + override suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result { + return runCatching { + commentRemoteDataSource.registerComment(registerCommentRequest.toDto()) + .handleNullableApiResponse() + .getOrThrow() + } + } + + override suspend fun likeComment(commentId: Long): Result { + return runCatching { + commentRemoteDataSource.likeComment(commentId) + .handleNullableApiResponse() + .getOrThrow() + } + } + + override suspend fun deleteComment(commentId: Long): Result { + return runCatching { + commentRemoteDataSource.deleteComment(commentId) + .handleNullableApiResponse() + .getOrThrow() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/di/ServiceModule.kt b/app/src/main/java/org/sopt/certi/di/ServiceModule.kt index 4b121c4f..10f0af37 100644 --- a/app/src/main/java/org/sopt/certi/di/ServiceModule.kt +++ b/app/src/main/java/org/sopt/certi/di/ServiceModule.kt @@ -9,6 +9,7 @@ import org.sopt.certi.data.remote.service.AcquisitionService import org.sopt.certi.data.remote.service.AuthService import org.sopt.certi.data.remote.service.CareerService import org.sopt.certi.data.remote.service.CertService +import org.sopt.certi.data.remote.service.CommentService import javax.inject.Singleton import org.sopt.certi.data.remote.service.DummyService import org.sopt.certi.data.remote.service.HomeService @@ -77,4 +78,9 @@ object ServiceModule { fun provideS3Service(@Named("S3Retrofit") retrofit: Retrofit): S3Service { return retrofit.create(S3Service::class.java) } + + @Provides + @Singleton + fun provideCommentService(retrofit: Retrofit): CommentService = + retrofit.create(CommentService::class.java) } diff --git a/app/src/main/java/org/sopt/certi/domain/model/comment/CommentData.kt b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentData.kt index 4bf0beb3..a6806400 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/comment/CommentData.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentData.kt @@ -1,6 +1,6 @@ package org.sopt.certi.domain.model.comment -import org.sopt.certi.domain.type.CertAcquireStateType +import org.sopt.certi.domain.type.CertStateType data class CommentData( val content: List, @@ -16,7 +16,7 @@ data class CommentItemData( val content: String, // 댓글 내용 val userMajor: String, // 사용자 전공 val userJob: String, // 사용자 직무 정보(현재 123순위 기능 구현 안해서, 그냥 직무 정보중 첫번째로 가져옴) - val state: CertAcquireStateType, // 취득 예정인지, 취득인지 + val state: CertStateType, // 취득 예정인지, 취득인지 val createdTime: String, // 생성일자 val lastModifiedTime: String, // 수정일자 val isLike: Boolean, // 댓글을 조회하는 사용자가 해당 댓글에 좋아요를 눌렀는지 diff --git a/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt new file mode 100644 index 00000000..c926e16a --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt @@ -0,0 +1,7 @@ +package org.sopt.certi.domain.model.comment + +data class CommentListPageableRequest( + val page: Int, + val size: Int, + val sort: List +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt new file mode 100644 index 00000000..e51e9557 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt @@ -0,0 +1,6 @@ +package org.sopt.certi.domain.model.comment + +data class RegisterCommentRequest( + val content: String, + val certificationId: Long +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt new file mode 100644 index 00000000..bd411cd1 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.domain.repository + +import org.sopt.certi.domain.model.comment.CommentData +import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import org.sopt.certi.domain.model.comment.RegisterCommentRequest + +interface CommentRepository { + suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequest): Result + suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result + suspend fun likeComment(commentId: Long): Result + suspend fun deleteComment(commentId: Long): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/type/CertAcquireStateType.kt b/app/src/main/java/org/sopt/certi/domain/type/CertAcquireStateType.kt deleted file mode 100644 index a5ba6dc4..00000000 --- a/app/src/main/java/org/sopt/certi/domain/type/CertAcquireStateType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.certi.domain.type - -enum class CertAcquireStateType { - ACQUIRED, - PRE -} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt new file mode 100644 index 00000000..53f2bdc7 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.domain.usecase.comment + +import org.sopt.certi.domain.repository.CommentRepository +import javax.inject.Inject + +class DeleteCommentUseCase @Inject constructor( + private val commentRepository: CommentRepository +) { + suspend operator fun invoke(commentId: Long): Result { + return commentRepository.deleteComment(commentId) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt new file mode 100644 index 00000000..def7d33c --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -0,0 +1,14 @@ +package org.sopt.certi.domain.usecase.comment + +import org.sopt.certi.domain.model.comment.CommentData +import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import org.sopt.certi.domain.repository.CommentRepository +import javax.inject.Inject + +class GetCommentListUseCase @Inject constructor( + private val commentRepository: CommentRepository +) { + suspend operator fun invoke(certificationId: Long, pageable: CommentListPageableRequest): Result { + return commentRepository.getCommentList(certificationId, pageable) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt new file mode 100644 index 00000000..0a802841 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.domain.usecase.comment + +import org.sopt.certi.domain.repository.CommentRepository +import javax.inject.Inject + +class LikeCommentUseCase @Inject constructor( + private val commentRepository: CommentRepository +) { + suspend operator fun invoke(commentId: Long): Result { + return commentRepository.likeComment(commentId) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt new file mode 100644 index 00000000..fc1e06a8 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt @@ -0,0 +1,13 @@ +package org.sopt.certi.domain.usecase.comment + +import org.sopt.certi.domain.model.comment.RegisterCommentRequest +import org.sopt.certi.domain.repository.CommentRepository +import javax.inject.Inject + +class RegisterCommentUseCase @Inject constructor( + private val commentRepository: CommentRepository +) { + suspend operator fun invoke(registerCommentRequest: RegisterCommentRequest): Result { + return commentRepository.registerComment(registerCommentRequest) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index ee26591a..9d24ab12 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -15,6 +15,10 @@ import org.sopt.certi.core.state.UiState import org.sopt.certi.domain.model.certification.CertificationData import org.sopt.certi.domain.usecase.acquisition.AcquiredCertUseCase import org.sopt.certi.domain.usecase.certification.GetCertInfoUseCase +import org.sopt.certi.domain.usecase.comment.DeleteCommentUseCase +import org.sopt.certi.domain.usecase.comment.GetCommentListUseCase +import org.sopt.certi.domain.usecase.comment.LikeCommentUseCase +import org.sopt.certi.domain.usecase.comment.RegisterCommentUseCase import org.sopt.certi.domain.usecase.precert.AcquireExpectCertUseCase import org.sopt.certi.presentation.ui.certdetail.sideeffect.DetailSideEffect import org.sopt.certi.presentation.ui.certdetail.state.DetailUiState @@ -24,7 +28,11 @@ import javax.inject.Inject class CertDetailViewModel @Inject constructor( private val getCertInfoUseCase: GetCertInfoUseCase, private val acquiredCertUseCase: AcquiredCertUseCase, - private val acquireExpectCertUseCase: AcquireExpectCertUseCase + private val acquireExpectCertUseCase: AcquireExpectCertUseCase, + private val getCommentListUseCase: GetCommentListUseCase, + private val registerCommentUseCase: RegisterCommentUseCase, + private val likeCommentUseCase: LikeCommentUseCase, + private val deleteCommentUseCase: DeleteCommentUseCase, ) : ViewModel() { private val _certDetailInfo = MutableStateFlow>(UiState.Init) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt index 69001c02..1e06cd4f 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt @@ -34,7 +34,7 @@ import org.sopt.certi.core.util.screenHeightDp import org.sopt.certi.core.util.screenWidthDp import org.sopt.certi.core.util.widthForScreenPercentage import org.sopt.certi.domain.model.comment.CommentItemData -import org.sopt.certi.domain.type.CertAcquireStateType +import org.sopt.certi.domain.type.CertStateType import org.sopt.certi.ui.theme.CertiTheme @Composable @@ -53,14 +53,18 @@ fun CommentItem( var likeCountStatus by remember { mutableStateOf(commentData.likeCount) } when (commentData.state) { - CertAcquireStateType.ACQUIRED -> { + CertStateType.ANTICIPATED, -> { acquireStateText = stringResource(R.string.comment_state_acquired) acquireStateTextColor = CertiTheme.colors.purpleBlue } - CertAcquireStateType.PRE -> { + CertStateType.ACQUISITION -> { acquireStateText = stringResource(R.string.comment_state_pre) acquireStateTextColor = CertiTheme.colors.gray300 } + CertStateType.NORMAL -> { + acquireStateText = "ERROR" + acquireStateTextColor = CertiTheme.colors.error + } } Column( @@ -203,7 +207,7 @@ fun CommentItemPreview() { content = "이 자격증 너무 좋은 것 같아요! 다들 꼭 따세요~이 자격증 너무 좋은 것 같아요! 다들 꼭 따세요~이 자격증 너무 좋은 것 같아요! 다들 꼭 따세요~", userMajor = "컴퓨터공학과", userJob = "개발자", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, createdTime = "2024.07.21", lastModifiedTime = "2024.07.21", isLike = true, diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index e394a1ea..143cb1c7 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -50,7 +50,7 @@ import org.sopt.certi.core.util.screenWidthDp import org.sopt.certi.core.util.widthForScreenPercentage import org.sopt.certi.domain.model.comment.CommentData import org.sopt.certi.domain.model.comment.CommentItemData -import org.sopt.certi.domain.type.CertAcquireStateType +import org.sopt.certi.domain.type.CertStateType import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButton import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButtonType import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentItem @@ -67,7 +67,7 @@ fun CertDetailCommentRoute() { content = "댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -80,7 +80,7 @@ fun CertDetailCommentRoute() { content = "댓글입니다.2댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -93,7 +93,7 @@ fun CertDetailCommentRoute() { content = "댓글입니다.3댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -106,7 +106,7 @@ fun CertDetailCommentRoute() { content = "댓글입니다.4댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -119,7 +119,7 @@ fun CertDetailCommentRoute() { content = "댓글입니다.5댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -313,7 +313,7 @@ private fun PreviewCertDetailCommentScreen() { content = "댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ACQUISITION, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -326,7 +326,7 @@ private fun PreviewCertDetailCommentScreen() { content = "댓글입니다.2댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ACQUISITION, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -339,7 +339,7 @@ private fun PreviewCertDetailCommentScreen() { content = "댓글입니다.3댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -352,7 +352,7 @@ private fun PreviewCertDetailCommentScreen() { content = "댓글입니다.4댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", @@ -365,7 +365,7 @@ private fun PreviewCertDetailCommentScreen() { content = "댓글입니다.5댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", userMajor = "전산학/컴퓨터공학", userJob = "IT/인터넷", - state = CertAcquireStateType.ACQUIRED, + state = CertStateType.ANTICIPATED, likeCount = 3, createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", From 461ac346f277182824b7fe0f3d222fb6020d884c Mon Sep 17 00:00:00 2001 From: kimjw Date: Wed, 4 Feb 2026 21:14:55 +0900 Subject: [PATCH 02/17] [Feat] empty comment view --- .../org/sopt/certi/di/DataSourceModule.kt | 6 ++ .../org/sopt/certi/di/RepositoryModule.kt | 6 ++ .../java/org/sopt/certi/di/UseCaseModule.kt | 29 +++++ .../ui/certdetail/CertDetailScreen.kt | 2 +- .../component/comment/CommentEmptyView.kt | 54 ++++++++++ .../screen/CertDetailCommentScreen.kt | 100 +++++------------- app/src/main/res/values/strings.xml | 1 + 7 files changed, 126 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt diff --git a/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt b/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt index 7585d394..baa83cec 100644 --- a/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt @@ -9,6 +9,7 @@ import org.sopt.certi.data.remote.datasource.ActivityRemoteDataSource import org.sopt.certi.data.remote.datasource.AuthRemoteDataSource import org.sopt.certi.data.remote.datasource.CareerRemoteDataSource import org.sopt.certi.data.remote.datasource.CertRemoteDataSource +import org.sopt.certi.data.remote.datasource.CommentRemoteDataSource import javax.inject.Singleton import org.sopt.certi.data.remote.datasource.DummyRemoteDataSource import org.sopt.certi.data.remote.datasource.HomeRemoteDataSource @@ -21,6 +22,7 @@ import org.sopt.certi.data.remote.datasourceimpl.ActivityRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.AuthRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.CareerRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.CertRemoteDataSourceImpl +import org.sopt.certi.data.remote.datasourceimpl.CommentRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.DummyRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.HomeRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.PreCertEditRemoteDataSourceImpl @@ -74,4 +76,8 @@ abstract class DataSourceModule { @Binds @Singleton abstract fun bindsS3DataSource(s3DataSourceImpl: S3DataSourceImpl): S3DataSource + + @Binds + @Singleton + abstract fun bindsCommentDataSource(commentRemoteDataSourceImpl: CommentRemoteDataSourceImpl): CommentRemoteDataSource } diff --git a/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt b/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt index 25b7b9dd..3be97291 100644 --- a/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt @@ -9,6 +9,7 @@ import org.sopt.certi.data.repositoryimpl.AcquisitionRepositoryImpl import org.sopt.certi.data.repositoryimpl.AuthRepositoryImpl import org.sopt.certi.data.repositoryimpl.CareerRepositoryImpl import org.sopt.certi.data.repositoryimpl.CertRepositoryImpl +import org.sopt.certi.data.repositoryimpl.CommentRepositoryImpl import org.sopt.certi.data.repositoryimpl.DummyRepositoryImpl import org.sopt.certi.data.repositoryimpl.HomeRepositoryImpl import org.sopt.certi.data.repositoryimpl.PreCertEditRepositoryImpl @@ -20,6 +21,7 @@ import org.sopt.certi.domain.repository.ActivityRepository import org.sopt.certi.domain.repository.AuthRepository import org.sopt.certi.domain.repository.CareerRepository import org.sopt.certi.domain.repository.CertRepository +import org.sopt.certi.domain.repository.CommentRepository import org.sopt.certi.domain.repository.DummyRepository import org.sopt.certi.domain.repository.HomeRepository import org.sopt.certi.domain.repository.PreCertEditRepository @@ -73,4 +75,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindS3Repository(s3RepositoryImpl: S3RepositoryImpl): S3Repository + + @Binds + @Singleton + abstract fun bindCommentRepository(commentRepositoryImpl: CommentRepositoryImpl): CommentRepository } diff --git a/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt b/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt index b697ad33..1e432b26 100644 --- a/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt +++ b/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt @@ -9,6 +9,7 @@ import org.sopt.certi.domain.repository.AcquisitionRepository import org.sopt.certi.domain.repository.AuthRepository import org.sopt.certi.domain.repository.CareerRepository import org.sopt.certi.domain.repository.CertRepository +import org.sopt.certi.domain.repository.CommentRepository import org.sopt.certi.domain.repository.DummyRepository import org.sopt.certi.domain.repository.HomeRepository import org.sopt.certi.domain.repository.PreCertEditRepository @@ -42,6 +43,10 @@ import org.sopt.certi.domain.usecase.certification.GetRecommendCertListUseCase import org.sopt.certi.domain.usecase.certification.SearchCertListUseCase import org.sopt.certi.domain.usecase.certification.Top3JobCertListUseCase import org.sopt.certi.domain.usecase.certification.Top3TrackCertListUseCase +import org.sopt.certi.domain.usecase.comment.DeleteCommentUseCase +import org.sopt.certi.domain.usecase.comment.GetCommentListUseCase +import org.sopt.certi.domain.usecase.comment.LikeCommentUseCase +import org.sopt.certi.domain.usecase.comment.RegisterCommentUseCase import org.sopt.certi.domain.usecase.precert.AcquireExpectCertUseCase import org.sopt.certi.domain.usecase.user.GetInterestedJobListUseCase import org.sopt.certi.domain.usecase.user.ModifyInterestedJobListUseCase @@ -231,4 +236,28 @@ object UseCaseModule { fun provideTop3JobCertListUseCase( certRepository: CertRepository ): Top3JobCertListUseCase = Top3JobCertListUseCase(certRepository) + + @Provides + @Singleton + fun provideGetCommentListUseCase( + commentRepository: CommentRepository + ): GetCommentListUseCase = GetCommentListUseCase(commentRepository) + + @Provides + @Singleton + fun provideRegisterCommentUseCase( + commentRepository: CommentRepository + ): RegisterCommentUseCase = RegisterCommentUseCase(commentRepository) + + @Provides + @Singleton + fun provideLikeCommentUseCase( + commentRepository: CommentRepository + ): LikeCommentUseCase = LikeCommentUseCase(commentRepository) + + @Provides + @Singleton + fun provideDeleteCommentUseCase( + commentRepository: CommentRepository + ): DeleteCommentUseCase = DeleteCommentUseCase(commentRepository) } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt index 321f78f9..3f2807e0 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt @@ -243,7 +243,7 @@ fun CertDetailScreen( ) } DetailTabType.Comment -> { - CertDetailCommentRoute() + CertDetailCommentRoute(certData.certificationId) } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt new file mode 100644 index 00000000..8dfed8d3 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt @@ -0,0 +1,54 @@ +package org.sopt.certi.presentation.ui.certdetail.component.comment + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.certi.R +import org.sopt.certi.core.util.heightForScreenPercentage +import org.sopt.certi.ui.theme.CertiTheme + +@Composable +fun CommentEmptyView( + modifier : Modifier = Modifier +) { + Column ( + modifier = modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.img_empty), + contentDescription = null, + ) + + Spacer(Modifier.heightForScreenPercentage(20.dp)) + + Text( + text = stringResource(R.string.comment_empty), + style = CertiTheme.typography.caption.regular_14, + color = CertiTheme.colors.gray400, + textAlign = TextAlign.Center + ) + } +} + +@Preview +@Composable +private fun PreviewCommentEmptyView() { + CommentEmptyView() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 143cb1c7..62877c23 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import org.sopt.certi.R import org.sopt.certi.core.util.heightForScreenPercentage import org.sopt.certi.core.util.noRippleClickable @@ -51,13 +52,18 @@ import org.sopt.certi.core.util.widthForScreenPercentage import org.sopt.certi.domain.model.comment.CommentData import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.type.CertStateType +import org.sopt.certi.presentation.ui.certdetail.CertDetailViewModel import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButton import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButtonType +import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentEmptyView import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentItem import org.sopt.certi.ui.theme.CertiTheme @Composable -fun CertDetailCommentRoute() { +fun CertDetailCommentRoute( + certificationId: Long, + viewModel: CertDetailViewModel = hiltViewModel() +) { val dummyCommentData = CommentData( content = listOf( CommentItemData( @@ -72,58 +78,6 @@ fun CertDetailCommentRoute() { createdTime = "2025-11-15T23:00:38.042089", lastModifiedTime = "2025-11-15T23:00:38.042089", isLike = false - ), - CommentItemData( - commentId = 58, - userId = 1, - nickName = "이성민2", - content = "댓글입니다.2댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 59, - userId = 1, - nickName = "이성민3", - content = "댓글입니다.3댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 60, - userId = 1, - nickName = "이성민4", - content = "댓글입니다.4댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 61, - userId = 1, - nickName = "이성민5", - content = "댓글입니다.5댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false ) ), totalPages = 1, @@ -203,24 +157,28 @@ fun CertDetailCommentScreen( ) } - LazyColumn( - contentPadding = PaddingValues(top = screenHeightDp(12.dp)), - verticalArrangement = Arrangement.spacedBy(screenHeightDp(12.dp)) - ) { - itemsIndexed(commentData.content) { _, item -> - CommentItem( - commentData = item, - myUserId = myUserId, - likeOnClick = { like -> - likeOnClick(like, item.commentId) - }, - reportOnClick = { - reportOnClick(item.commentId) - }, - deleteOnClick = { - deleteOnClick(item.commentId) - } - ) + if(commentData.content.isEmpty()) { + CommentEmptyView() + } else { + LazyColumn( + contentPadding = PaddingValues(top = screenHeightDp(12.dp)), + verticalArrangement = Arrangement.spacedBy(screenHeightDp(12.dp)) + ) { + itemsIndexed(commentData.content) { _, item -> + CommentItem( + commentData = item, + myUserId = myUserId, + likeOnClick = { like -> + likeOnClick(like, item.commentId) + }, + reportOnClick = { + reportOnClick(item.commentId) + }, + deleteOnClick = { + deleteOnClick(item.commentId) + } + ) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db3f5195..93156960 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -286,6 +286,7 @@ 최신순 댓글(%s) 댓글을 입력하세요 + 아직 댓글이 없습니다.\n가장 먼저 댓글을 작성해보세요. \ No newline at end of file From 84622f4b5e04a8ee6d4f45a2c226b68025431682 Mon Sep 17 00:00:00 2001 From: kimjw Date: Wed, 4 Feb 2026 21:35:50 +0900 Subject: [PATCH 03/17] [Feat] paging setting --- app/build.gradle.kts | 3 ++ .../certi/data/pagingsource/CertiPaging.kt | 53 +++++++++++++++++++ .../repositoryimpl/CommentRepositoryImpl.kt | 29 +++++++--- .../domain/repository/CommentRepository.kt | 7 +-- .../usecase/comment/GetCommentListUseCase.kt | 9 ++-- gradle/libs.versions.toml | 6 +++ 6 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7d342e89..4e0f63e5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -125,6 +125,9 @@ dependencies { // calendar implementation(libs.kizitonwose.calendar) + + // Paging + implementation(libs.androidx.room.paging) } ktlint { diff --git a/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt new file mode 100644 index 00000000..4e06d724 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt @@ -0,0 +1,53 @@ +package org.sopt.certi.data.pagingsource + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState + +class CertiPagingSource( + private val getList: suspend (Int) -> List, +) : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + val anchor = state.anchorPosition ?: return null + val page = state.closestPageToPosition(anchor) ?: return null + return page.prevKey?.plus(1) ?: page.nextKey?.minus(1) + } + + override suspend fun load(params: LoadParams): LoadResult { + val currentPage = params.key ?: 0 + return try { + val list = getList(currentPage) + LoadResult.Page( + data = list, + prevKey = if (currentPage == 0) null else currentPage - 1, + nextKey = if (list.isEmpty()) null else currentPage + 1 + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } +} + +fun createPager( + limit: Int = 10, + initialLoadSize: Int = 20, + q: List? = null, + startPage: Int? = null, + pagingSourceFactory: suspend (page: Int, limit: Int, sort: List?) -> List +) : Pager { + return Pager( + config = PagingConfig( + pageSize = limit, + initialLoadSize = initialLoadSize, + prefetchDistance = 1, + enablePlaceholders = false + ), + initialKey = startPage ?: 0, + pagingSourceFactory = { + CertiPagingSource { page -> + pagingSourceFactory(page, limit, q) + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt index 60f835fb..b8788afd 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -1,12 +1,15 @@ package org.sopt.certi.data.repositoryimpl +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow import org.sopt.certi.data.mapper.todomain.comment.toDomain import org.sopt.certi.data.mapper.todto.comment.toDto +import org.sopt.certi.data.pagingsource.createPager import org.sopt.certi.data.remote.datasource.CommentRemoteDataSource +import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto import org.sopt.certi.data.remote.util.HttpResponseHandler.handleApiResponse import org.sopt.certi.data.remote.util.HttpResponseHandler.handleNullableApiResponse -import org.sopt.certi.domain.model.comment.CommentData -import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest import org.sopt.certi.domain.repository.CommentRepository import javax.inject.Inject @@ -14,13 +17,25 @@ import javax.inject.Inject class CommentRepositoryImpl @Inject constructor( private val commentRemoteDataSource: CommentRemoteDataSource ) : CommentRepository { - override suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequest): Result { - return runCatching { - commentRemoteDataSource.getCommentList(certificationId, pageable.toDto()) + override suspend fun getCommentList(certificationId: Long, sort: List): Flow> { + return createPager( + limit = 12, + initialLoadSize = 12, + q = sort + ) { page, limit, sortParam -> + commentRemoteDataSource.getCommentList( + certificationId, + CommentListPageableRequestDto( + page = page, + size = limit, + sort = sortParam ?: listOf("likeCount,desc") + ) + ) .handleApiResponse() .getOrThrow() - .toDomain() - } + .content + .map { it.toDomain() } + }.flow } override suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result { diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt index bd411cd1..e503957c 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -1,11 +1,12 @@ package org.sopt.certi.domain.repository -import org.sopt.certi.domain.model.comment.CommentData -import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest interface CommentRepository { - suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequest): Result + suspend fun getCommentList(certificationId: Long, sort: List = listOf("createdTime,desc")): Flow> suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result suspend fun likeComment(commentId: Long): Result suspend fun deleteComment(commentId: Long): Result diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt index def7d33c..65f10ede 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -1,14 +1,15 @@ package org.sopt.certi.domain.usecase.comment -import org.sopt.certi.domain.model.comment.CommentData -import org.sopt.certi.domain.model.comment.CommentListPageableRequest +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.repository.CommentRepository import javax.inject.Inject class GetCommentListUseCase @Inject constructor( private val commentRepository: CommentRepository ) { - suspend operator fun invoke(certificationId: Long, pageable: CommentListPageableRequest): Result { - return commentRepository.getCommentList(certificationId, pageable) + suspend operator fun invoke(certificationId: Long, sort: List = listOf("createdTime,desc")): Flow> { + return commentRepository.getCommentList(certificationId, sort) } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f07b4054..96027533 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,6 +52,9 @@ firebaseCrashlyticsBuildtools = "3.0.4" # Calendar calendar = "2.6.0" +# Paging +roomRuntime = "2.8.2" + [libraries] # Core androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -107,6 +110,9 @@ firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "fireb # Calendar kizitonwose-calendar = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendar" } +# Paging +androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "roomRuntime" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From 0d01ec6d220beb57857ec3751c7e88140d5bb0a0 Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 5 Feb 2026 03:35:21 +0900 Subject: [PATCH 04/17] [Feat] comment viewModel --- app/build.gradle.kts | 2 + .../CommentRemoteDataSourceImpl.kt | 2 +- .../data/remote/service/CommentService.kt | 2 +- .../repositoryimpl/CommentRepositoryImpl.kt | 43 ++-- .../domain/repository/CommentRepository.kt | 2 +- .../usecase/comment/GetCommentListUseCase.kt | 2 +- .../presentation/type/CommentSortType.kt | 5 + .../ui/certdetail/CertDetailViewModel.kt | 26 ++- .../component/chip/CommentArrayButton.kt | 19 +- .../screen/CertDetailCommentScreen.kt | 204 ++++++++---------- gradle/libs.versions.toml | 4 + 11 files changed, 162 insertions(+), 149 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e0f63e5..b8d88413 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,8 @@ dependencies { // Paging implementation(libs.androidx.room.paging) + implementation(libs.androidx.paging.runtime.android) + implementation(libs.androidx.paging.runtime) } ktlint { diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt index 74f3374c..1aec937e 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt @@ -13,7 +13,7 @@ class CommentRemoteDataSourceImpl @Inject constructor( private val commentService: CommentService ) : CommentRemoteDataSource { override suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequestDto): ApiResponse = - commentService.getCommentList(certificationId, pageable) + commentService.getCommentList(certificationId, "{\"page\": ${pageable.page}, \"size\": ${pageable.size}, \"sort\": ${pageable.sort}}") override suspend fun registerComment(registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse = commentService.registerComment(registerCommentRequest) diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt index 2a8063b8..fd175d6f 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt @@ -16,7 +16,7 @@ interface CommentService { @GET("api/v1/comments") suspend fun getCommentList( @Query("certificationId") certificationId: Long, - @Query("pageable") pageable: CommentListPageableRequestDto + @Query("pageable") pageable: String ) : ApiResponse @POST("api/v1/comments") diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt index b8788afd..f0d9ec1e 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -17,25 +17,42 @@ import javax.inject.Inject class CommentRepositoryImpl @Inject constructor( private val commentRemoteDataSource: CommentRemoteDataSource ) : CommentRepository { - override suspend fun getCommentList(certificationId: Long, sort: List): Flow> { - return createPager( + override suspend fun getCommentList(certificationId: Long, sort: List): Pair>, Int> { + val initialResponse = commentRemoteDataSource.getCommentList( + certificationId, + CommentListPageableRequestDto( + page = 0, + size = 12, + sort = sort.ifEmpty { listOf("likeCount,desc") } + ) + ).handleApiResponse().getOrThrow() + + val totalElements = initialResponse.totalElements + + val pagingFlow = createPager( limit = 12, initialLoadSize = 12, q = sort ) { page, limit, sortParam -> - commentRemoteDataSource.getCommentList( - certificationId, - CommentListPageableRequestDto( - page = page, - size = limit, - sort = sortParam ?: listOf("likeCount,desc") + if (page == 0) { + initialResponse.content.map { it.toDomain() } + } else { + commentRemoteDataSource.getCommentList( + certificationId, + CommentListPageableRequestDto( + page = page, + size = limit, + sort = sortParam ?: listOf("likeCount,desc") + ) ) - ) - .handleApiResponse() - .getOrThrow() - .content - .map { it.toDomain() } + .handleApiResponse() + .getOrThrow() + .content + .map { it.toDomain() } + } }.flow + + return Pair(pagingFlow, totalElements) } override suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result { diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt index e503957c..41b648a9 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -6,7 +6,7 @@ import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest interface CommentRepository { - suspend fun getCommentList(certificationId: Long, sort: List = listOf("createdTime,desc")): Flow> + suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Pair>, Int> suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result suspend fun likeComment(commentId: Long): Result suspend fun deleteComment(commentId: Long): Result diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt index 65f10ede..db88b7db 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class GetCommentListUseCase @Inject constructor( private val commentRepository: CommentRepository ) { - suspend operator fun invoke(certificationId: Long, sort: List = listOf("createdTime,desc")): Flow> { + suspend operator fun invoke(certificationId: Long, sort: List = listOf("likeCount", "desc")): Pair>, Int> { return commentRepository.getCommentList(certificationId, sort) } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt b/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt new file mode 100644 index 00000000..58093625 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt @@ -0,0 +1,5 @@ +package org.sopt.certi.presentation.type + +enum class CommentSortType { + Recent, Famous +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 9d24ab12..e9799559 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -2,6 +2,8 @@ package org.sopt.certi.presentation.ui.certdetail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -11,6 +13,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.core.state.UiState import org.sopt.certi.domain.model.certification.CertificationData import org.sopt.certi.domain.usecase.acquisition.AcquiredCertUseCase @@ -20,6 +23,7 @@ import org.sopt.certi.domain.usecase.comment.GetCommentListUseCase import org.sopt.certi.domain.usecase.comment.LikeCommentUseCase import org.sopt.certi.domain.usecase.comment.RegisterCommentUseCase import org.sopt.certi.domain.usecase.precert.AcquireExpectCertUseCase +import org.sopt.certi.presentation.type.CommentSortType import org.sopt.certi.presentation.ui.certdetail.sideeffect.DetailSideEffect import org.sopt.certi.presentation.ui.certdetail.state.DetailUiState import javax.inject.Inject @@ -37,6 +41,12 @@ class CertDetailViewModel @Inject constructor( private val _certDetailInfo = MutableStateFlow>(UiState.Init) + private val _commentPagingData = MutableStateFlow>(PagingData.empty()) + val commentPagingData: StateFlow> = _commentPagingData + + private val _totalCommentCount = MutableStateFlow(0) + val totalCommentCount: StateFlow = _totalCommentCount + val detailUiState: StateFlow = combine( _certDetailInfo @@ -96,6 +106,20 @@ class CertDetailViewModel @Inject constructor( ) } - fun getCommentList() = viewModelScope.launch { + fun getCommentList(certId: Long, commentSortType: CommentSortType) = viewModelScope.launch { + val sortValue = if (commentSortType == CommentSortType.Famous) { + listOf("likeCount, desc") + } else { + listOf() + } + + val (pagingFlow, totalElements) = getCommentListUseCase(certId, sortValue) + _totalCommentCount.value = totalElements + + pagingFlow + .cachedIn(viewModelScope) + .collect { pagingData -> + _commentPagingData.value = pagingData + } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/chip/CommentArrayButton.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/chip/CommentArrayButton.kt index da31a533..0c7a9cf8 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/chip/CommentArrayButton.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/chip/CommentArrayButton.kt @@ -13,22 +13,19 @@ import org.sopt.certi.core.util.noRippleClickable import org.sopt.certi.core.util.roundedBackgroundWithBorder import org.sopt.certi.core.util.screenHeightDp import org.sopt.certi.core.util.screenWidthDp +import org.sopt.certi.presentation.type.CommentSortType import org.sopt.certi.ui.theme.CertiTheme -enum class CommentArrayButtonType { - Famous, Recent -} - @Composable fun CommentArrayButton( - commentArrayButtonType: CommentArrayButtonType, + commentSortType: CommentSortType, isSelected: Boolean, - selectOnClick: (CommentArrayButtonType) -> Unit, + selectOnClick: (CommentSortType) -> Unit, modifier: Modifier = Modifier ) { - val label = when (commentArrayButtonType) { - CommentArrayButtonType.Famous -> stringResource(R.string.comment_label_famous) - CommentArrayButtonType.Recent -> stringResource(R.string.comment_label_recent) + val label = when (commentSortType) { + CommentSortType.Famous -> stringResource(R.string.comment_label_famous) + CommentSortType.Recent -> stringResource(R.string.comment_label_recent) } Box( @@ -40,7 +37,7 @@ fun CommentArrayButton( backgroundColor = CertiTheme.colors.white ) .noRippleClickable { - selectOnClick(commentArrayButtonType) + selectOnClick(commentSortType) } ) { Text( @@ -56,7 +53,7 @@ fun CommentArrayButton( @Composable private fun PreviewCommentArrayButton() { CommentArrayButton( - commentArrayButtonType = CommentArrayButtonType.Recent, + commentSortType = CommentSortType.Recent, isSelected = true, selectOnClick = {} ) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 62877c23..c4b79551 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -19,12 +19,12 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -43,18 +43,23 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemKey +import kotlinx.coroutines.flow.flowOf import org.sopt.certi.R import org.sopt.certi.core.util.heightForScreenPercentage import org.sopt.certi.core.util.noRippleClickable import org.sopt.certi.core.util.screenHeightDp import org.sopt.certi.core.util.screenWidthDp import org.sopt.certi.core.util.widthForScreenPercentage -import org.sopt.certi.domain.model.comment.CommentData import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.type.CertStateType +import org.sopt.certi.presentation.type.CommentSortType import org.sopt.certi.presentation.ui.certdetail.CertDetailViewModel import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButton -import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButtonType import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentEmptyView import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentItem import org.sopt.certi.ui.theme.CertiTheme @@ -64,41 +69,49 @@ fun CertDetailCommentRoute( certificationId: Long, viewModel: CertDetailViewModel = hiltViewModel() ) { - val dummyCommentData = CommentData( - content = listOf( - CommentItemData( - commentId = 57, - userId = 1, - nickName = "이성민", - content = "댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ) - ), - totalPages = 1, - totalElements = 5, - isLast = false - ) + var commentSortType by remember { mutableStateOf(CommentSortType.Famous) } + val commentList = viewModel.commentPagingData.collectAsLazyPagingItems() + val totalCommentCount by viewModel.totalCommentCount.collectAsStateWithLifecycle() + + LaunchedEffect(commentSortType) { + viewModel.getCommentList(certificationId, commentSortType) + } + + CertDetailCommentScreen( + commentData = commentList, + totalCommentCount = totalCommentCount, + myUserId = 0, + changeSortType = { changedSortType -> + commentSortType = changedSortType + }, + writeComment = { content -> + + }, + likeOnClick = { like, commentId -> - CertDetailCommentScreen(commentData = dummyCommentData, myUserId = 0) + }, + reportOnClick = { commentId -> + + }, + deleteOnClick = { commentId -> + + } + ) } @OptIn(ExperimentalLayoutApi::class) @Composable fun CertDetailCommentScreen( - commentData: CommentData, + commentData: LazyPagingItems, + totalCommentCount: Int, myUserId: Long, + changeSortType: (CommentSortType) -> Unit = {}, writeComment: (content: String) -> Unit = {}, likeOnClick: (like: Boolean, commentId: Long) -> Unit = { _, _ -> }, reportOnClick: (commentId: Long) -> Unit = {}, deleteOnClick: (commentId: Long) -> Unit = {} ) { - var commentSortType by remember { mutableStateOf(CommentArrayButtonType.Famous) } + var commentSortType by remember { mutableStateOf(CommentSortType.Famous) } var commentText by remember { mutableStateOf("") } @@ -131,53 +144,60 @@ fun CertDetailCommentScreen( verticalAlignment = Alignment.CenterVertically ) { CommentArrayButton( - commentArrayButtonType = CommentArrayButtonType.Famous, - isSelected = commentSortType == CommentArrayButtonType.Famous, + commentSortType = CommentSortType.Famous, + isSelected = commentSortType == CommentSortType.Famous, selectOnClick = { - commentSortType = CommentArrayButtonType.Famous + commentSortType = CommentSortType.Famous + changeSortType(commentSortType) } ) Spacer(Modifier.widthForScreenPercentage(8.dp)) CommentArrayButton( - commentArrayButtonType = CommentArrayButtonType.Recent, - isSelected = commentSortType == CommentArrayButtonType.Recent, + commentSortType = CommentSortType.Recent, + isSelected = commentSortType == CommentSortType.Recent, selectOnClick = { - commentSortType = CommentArrayButtonType.Recent + commentSortType = CommentSortType.Recent + changeSortType(commentSortType) } ) Spacer(Modifier.weight(1f)) Text( - text = stringResource(R.string.comment_count, commentData.totalElements), + text = stringResource(R.string.comment_count, totalCommentCount), style = CertiTheme.typography.caption.regular_14, color = CertiTheme.colors.gray400 ) } - if(commentData.content.isEmpty()) { + if(totalCommentCount == 0) { CommentEmptyView() } else { LazyColumn( contentPadding = PaddingValues(top = screenHeightDp(12.dp)), verticalArrangement = Arrangement.spacedBy(screenHeightDp(12.dp)) ) { - itemsIndexed(commentData.content) { _, item -> - CommentItem( - commentData = item, - myUserId = myUserId, - likeOnClick = { like -> - likeOnClick(like, item.commentId) - }, - reportOnClick = { - reportOnClick(item.commentId) - }, - deleteOnClick = { - deleteOnClick(item.commentId) - } - ) + items( + count = commentData.itemCount, + key = commentData.itemKey { it.commentId } + ) { index -> + commentData[index]?.let { comment -> + CommentItem( + commentData = comment, + myUserId = myUserId, + likeOnClick = { like -> + likeOnClick(like, comment.commentId) + }, + reportOnClick = { + reportOnClick(comment.commentId) + }, + deleteOnClick = { + deleteOnClick(comment.commentId) + } + ) + } } } } @@ -262,78 +282,22 @@ fun CertDetailCommentScreen( @Preview(showBackground = true) @Composable private fun PreviewCertDetailCommentScreen() { - val dummyCommentData = CommentData( - content = listOf( - CommentItemData( - commentId = 57, - userId = 1, - nickName = "이성민", - content = "댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ACQUISITION, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 58, - userId = 1, - nickName = "이성민2", - content = "댓글입니다.2댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ACQUISITION, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 59, - userId = 1, - nickName = "이성민3", - content = "댓글입니다.3댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 60, - userId = 1, - nickName = "이성민4", - content = "댓글입니다.4댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ), - CommentItemData( - commentId = 61, - userId = 1, - nickName = "이성민5", - content = "댓글입니다.5댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.댓글입니다.", - userMajor = "전산학/컴퓨터공학", - userJob = "IT/인터넷", - state = CertStateType.ANTICIPATED, - likeCount = 3, - createdTime = "2025-11-15T23:00:38.042089", - lastModifiedTime = "2025-11-15T23:00:38.042089", - isLike = false - ) - ), - totalPages = 1, - totalElements = 4, - isLast = false + val dummyItem = CommentItemData( + commentId = 1L, + userId = 1L, + nickName = "홍길동", + content = "이 자격증 정말 추천합니다! 취업에 많은 도움이 되었어요.", + userMajor = "컴퓨터공학", + userJob = "백엔드 개발자", + state = CertStateType.ACQUISITION, + createdTime = "2024-01-15", + lastModifiedTime = "2024-01-15", + isLike = false, + likeCount = 5 ) + val dummyPagingData = PagingData.from(listOf(dummyItem)) + val dummyFlow = flowOf(dummyPagingData) + val dummyList = dummyFlow.collectAsLazyPagingItems() - CertDetailCommentScreen(commentData = dummyCommentData, myUserId = 0) + CertDetailCommentScreen(commentData = dummyList, totalCommentCount = 1, myUserId = 0) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96027533..f63ef0df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,7 @@ calendar = "2.6.0" # Paging roomRuntime = "2.8.2" +pagingCommonAndroid = "3.3.6" [libraries] # Core @@ -112,6 +113,9 @@ kizitonwose-calendar = { group = "com.kizitonwose.calendar", name = "compose", v # Paging androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "roomRuntime" } +androidx-paging-runtime-android = { module = "androidx.paging:paging-runtime", version.ref = "pagingCommonAndroid" } +androidx-paging-runtime = { module = "androidx.paging:paging-compose", version.ref = "pagingCommonAndroid" } +androidx-paging-common = { module = "androidx.paging:paging-common-ktx", version.ref = "pagingCommonAndroid"} [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From fb7ef7806ad1374d77a9c6cf7ac2b2d6bd34e910 Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 5 Feb 2026 04:53:40 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[Feat]=20=EB=94=B0=EB=A1=9C=20=EB=B9=BC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/todomain/comment/CommentMapper.kt | 2 +- .../repositoryimpl/CommentRepositoryImpl.kt | 48 ++++++++----------- .../domain/repository/CommentRepository.kt | 4 +- .../sopt/certi/domain/type/CertStateType.kt | 15 ++++-- .../usecase/comment/GetCommentListUseCase.kt | 6 ++- .../ui/certdetail/CertDetailViewModel.kt | 24 ++++++++-- .../screen/CertDetailCommentScreen.kt | 3 +- 7 files changed, 62 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt index 40bc4291..9a05a43e 100644 --- a/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt +++ b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt @@ -23,7 +23,7 @@ fun CommentItemResponseDto.toDomain() : CommentItemData { content = content, userMajor = userMajor, userJob = userJob, - state = CertStateType.valueOf(state), + state = CertStateType.fromStateName(state), createdTime = createdTime, lastModifiedTime = lastModifiedTime, isLike = isLike, diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt index f0d9ec1e..7a9c6843 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -2,6 +2,7 @@ package org.sopt.certi.data.repositoryimpl import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import org.sopt.certi.data.mapper.todomain.comment.toDomain import org.sopt.certi.data.mapper.todto.comment.toDto import org.sopt.certi.data.pagingsource.createPager @@ -17,42 +18,33 @@ import javax.inject.Inject class CommentRepositoryImpl @Inject constructor( private val commentRemoteDataSource: CommentRemoteDataSource ) : CommentRepository { - override suspend fun getCommentList(certificationId: Long, sort: List): Pair>, Int> { - val initialResponse = commentRemoteDataSource.getCommentList( - certificationId, - CommentListPageableRequestDto( - page = 0, - size = 12, - sort = sort.ifEmpty { listOf("likeCount,desc") } - ) - ).handleApiResponse().getOrThrow() - val totalElements = initialResponse.totalElements + private var _totalCommentCount = MutableStateFlow(0) - val pagingFlow = createPager( + override suspend fun getCommentList(certificationId: Long, sort: List): Flow> { + val sortList = sort.ifEmpty { listOf("likeCount", "desc") } + + return createPager( limit = 12, initialLoadSize = 12, - q = sort + q = sortList ) { page, limit, sortParam -> - if (page == 0) { - initialResponse.content.map { it.toDomain() } - } else { - commentRemoteDataSource.getCommentList( - certificationId, - CommentListPageableRequestDto( - page = page, - size = limit, - sort = sortParam ?: listOf("likeCount,desc") - ) + val response = commentRemoteDataSource.getCommentList( + certificationId, + CommentListPageableRequestDto( + page = page, + size = limit, + sort = sortParam ?: sortList ) - .handleApiResponse() - .getOrThrow() - .content - .map { it.toDomain() } - } + ).data.toDomain() + + _totalCommentCount.emit(response.totalElements) + response.content }.flow + } - return Pair(pagingFlow, totalElements) + override fun getTotalCommentCount(): Flow { + return _totalCommentCount } override suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result { diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt index 41b648a9..3effaaa2 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -6,8 +6,10 @@ import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest interface CommentRepository { - suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Pair>, Int> + suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Flow> suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result suspend fun likeComment(commentId: Long): Result suspend fun deleteComment(commentId: Long): Result + + fun getTotalCommentCount() : Flow } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/type/CertStateType.kt b/app/src/main/java/org/sopt/certi/domain/type/CertStateType.kt index c62f0e5a..e22aeb4c 100644 --- a/app/src/main/java/org/sopt/certi/domain/type/CertStateType.kt +++ b/app/src/main/java/org/sopt/certi/domain/type/CertStateType.kt @@ -1,7 +1,14 @@ package org.sopt.certi.domain.type -enum class CertStateType { - NORMAL, // 아무것도 아님 - ANTICIPATED, // 취득 예정 - ACQUISITION // 취득 완료 +enum class CertStateType(val stateName: String) { + NORMAL("아무것도 아님"), // 아무것도 아님 + ANTICIPATED("취득 예정"), // 취득 예정 + ACQUISITION("취득 완료"); // 취득 완료 + + companion object { + fun fromStateName(stateName: String): CertStateType { + return entries.find { it.stateName == stateName } + ?: NORMAL + } + } } diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt index db88b7db..0b0b5deb 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -9,7 +9,11 @@ import javax.inject.Inject class GetCommentListUseCase @Inject constructor( private val commentRepository: CommentRepository ) { - suspend operator fun invoke(certificationId: Long, sort: List = listOf("likeCount", "desc")): Pair>, Int> { + suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Flow> { return commentRepository.getCommentList(certificationId, sort) } + + fun getTotalCommentCount(): Flow { + return commentRepository.getTotalCommentCount() + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index e9799559..2b5ea8b1 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -1,15 +1,19 @@ package org.sopt.certi.presentation.ui.certdetail +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn +import androidx.paging.map import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -108,16 +112,30 @@ class CertDetailViewModel @Inject constructor( fun getCommentList(certId: Long, commentSortType: CommentSortType) = viewModelScope.launch { val sortValue = if (commentSortType == CommentSortType.Famous) { - listOf("likeCount, desc") + listOf("likeCount", "desc") } else { listOf() } - val (pagingFlow, totalElements) = getCommentListUseCase(certId, sortValue) - _totalCommentCount.value = totalElements + val pagingFlow = getCommentListUseCase.getCommentList(certId, sortValue) + + getCommentListUseCase.getTotalCommentCount() + .collect { + _totalCommentCount.value = it + } pagingFlow + .distinctUntilChanged() .cachedIn(viewModelScope) + .map { pagingData -> + var count = 0 + pagingData.map { item -> + count++ + item + }.also { + Log.d("Logd", "Total items loaded: $count") + } + } .collect { pagingData -> _commentPagingData.value = pagingData } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index c4b79551..f5008919 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -180,8 +180,7 @@ fun CertDetailCommentScreen( verticalArrangement = Arrangement.spacedBy(screenHeightDp(12.dp)) ) { items( - count = commentData.itemCount, - key = commentData.itemKey { it.commentId } + count = commentData.itemCount ) { index -> commentData[index]?.let { comment -> CommentItem( From 1fdb01e895584a7a7e55bc9c5e64351060243d22 Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 5 Feb 2026 05:04:17 +0900 Subject: [PATCH 06/17] [Feat] call --- .../ui/certdetail/CertDetailViewModel.kt | 18 ++++++++++-------- .../screen/CertDetailCommentScreen.kt | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 2b5ea8b1..4ef92d7d 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -117,14 +117,7 @@ class CertDetailViewModel @Inject constructor( listOf() } - val pagingFlow = getCommentListUseCase.getCommentList(certId, sortValue) - - getCommentListUseCase.getTotalCommentCount() - .collect { - _totalCommentCount.value = it - } - - pagingFlow + getCommentListUseCase.getCommentList(certId, sortValue) .distinctUntilChanged() .cachedIn(viewModelScope) .map { pagingData -> @@ -138,6 +131,15 @@ class CertDetailViewModel @Inject constructor( } .collect { pagingData -> _commentPagingData.value = pagingData + + getTotalCommentCount() + } + } + + private fun getTotalCommentCount() = viewModelScope.launch { + getCommentListUseCase.getTotalCommentCount() + .collect { + _totalCommentCount.value = it } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index f5008919..33f92081 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -172,7 +172,7 @@ fun CertDetailCommentScreen( ) } - if(totalCommentCount == 0) { + if(commentData.itemCount == 0) { CommentEmptyView() } else { LazyColumn( From 0fed9e2fe9fcd376c5b7cc32bd8cf0ae3363fc01 Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 5 Feb 2026 05:14:15 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[Feat]=20paging=20=EB=81=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/certi/data/pagingsource/CertiPaging.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt index 4e06d724..c7cb1dbb 100644 --- a/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt +++ b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt @@ -6,6 +6,7 @@ import androidx.paging.PagingSource import androidx.paging.PagingState class CertiPagingSource( + private val pageSize: Int, private val getList: suspend (Int) -> List, ) : PagingSource() { override fun getRefreshKey(state: PagingState): Int? { @@ -21,7 +22,7 @@ class CertiPagingSource( LoadResult.Page( data = list, prevKey = if (currentPage == 0) null else currentPage - 1, - nextKey = if (list.isEmpty()) null else currentPage + 1 + nextKey = if (list.size < pageSize) null else currentPage + 1 ) } catch (e: Exception) { LoadResult.Error(e) @@ -45,7 +46,7 @@ fun createPager( ), initialKey = startPage ?: 0, pagingSourceFactory = { - CertiPagingSource { page -> + CertiPagingSource(pageSize = limit) { page -> pagingSourceFactory(page, limit, q) } } From c329a910b101f93403ac8e906b190c03638c266d Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 5 Feb 2026 05:37:09 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EB=B6=88=EA=B0=80=EC=9D=BC=EB=95=8C=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositoryimpl/CommentRepositoryImpl.kt | 6 +- .../domain/repository/CommentRepository.kt | 2 +- .../usecase/comment/GetCommentListUseCase.kt | 2 +- .../ui/certdetail/CertDetailScreen.kt | 2 +- .../ui/certdetail/CertDetailViewModel.kt | 9 -- .../screen/CertDetailCommentScreen.kt | 95 ++++++++++++------- app/src/main/res/drawable/ic_lock.xml | 12 +++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 app/src/main/res/drawable/ic_lock.xml diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt index 7a9c6843..1c82e677 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -22,19 +22,17 @@ class CommentRepositoryImpl @Inject constructor( private var _totalCommentCount = MutableStateFlow(0) override suspend fun getCommentList(certificationId: Long, sort: List): Flow> { - val sortList = sort.ifEmpty { listOf("likeCount", "desc") } - return createPager( limit = 12, initialLoadSize = 12, - q = sortList + q = sort ) { page, limit, sortParam -> val response = commentRemoteDataSource.getCommentList( certificationId, CommentListPageableRequestDto( page = page, size = limit, - sort = sortParam ?: sortList + sort = sortParam ?: sort ) ).data.toDomain() diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt index 3effaaa2..7e052d0c 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -6,7 +6,7 @@ import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest interface CommentRepository { - suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Flow> + suspend fun getCommentList(certificationId: Long, sort: List): Flow> suspend fun registerComment(registerCommentRequest: RegisterCommentRequest): Result suspend fun likeComment(commentId: Long): Result suspend fun deleteComment(commentId: Long): Result diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt index 0b0b5deb..5a66ca27 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class GetCommentListUseCase @Inject constructor( private val commentRepository: CommentRepository ) { - suspend fun getCommentList(certificationId: Long, sort: List = listOf("likeCount", "desc")): Flow> { + suspend fun getCommentList(certificationId: Long, sort: List): Flow> { return commentRepository.getCommentList(certificationId, sort) } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt index 3f2807e0..b4aeae67 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt @@ -243,7 +243,7 @@ fun CertDetailScreen( ) } DetailTabType.Comment -> { - CertDetailCommentRoute(certData.certificationId) + CertDetailCommentRoute(certificationId = certData.certificationId, certStateType = certState) } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 4ef92d7d..bc7d599a 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -120,15 +120,6 @@ class CertDetailViewModel @Inject constructor( getCommentListUseCase.getCommentList(certId, sortValue) .distinctUntilChanged() .cachedIn(viewModelScope) - .map { pagingData -> - var count = 0 - pagingData.map { item -> - count++ - item - }.also { - Log.d("Logd", "Total items loaded: $count") - } - } .collect { pagingData -> _commentPagingData.value = pagingData diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 33f92081..88aae1d0 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -67,6 +67,7 @@ import org.sopt.certi.ui.theme.CertiTheme @Composable fun CertDetailCommentRoute( certificationId: Long, + certStateType: CertStateType, viewModel: CertDetailViewModel = hiltViewModel() ) { var commentSortType by remember { mutableStateOf(CommentSortType.Famous) } @@ -81,6 +82,7 @@ fun CertDetailCommentRoute( commentData = commentList, totalCommentCount = totalCommentCount, myUserId = 0, + certStateType = certStateType, changeSortType = { changedSortType -> commentSortType = changedSortType }, @@ -105,6 +107,7 @@ fun CertDetailCommentScreen( commentData: LazyPagingItems, totalCommentCount: Int, myUserId: Long, + certStateType: CertStateType, changeSortType: (CommentSortType) -> Unit = {}, writeComment: (content: String) -> Unit = {}, likeOnClick: (like: Boolean, commentId: Long) -> Unit = { _, _ -> }, @@ -235,44 +238,66 @@ fun CertDetailCommentScreen( .padding(end = screenWidthDp(12.dp)), verticalAlignment = Alignment.CenterVertically ) { - BasicTextField( - value = commentText, - onValueChange = { commentText = it }, - singleLine = true, - maxLines = 1, - textStyle = CertiTheme.typography.caption.regular_14.copy( - color = CertiTheme.colors.black - ), - cursorBrush = SolidColor(CertiTheme.colors.black), - decorationBox = { innerTextField -> - if (commentText.isEmpty()) { - Text( - text = stringResource(R.string.comment_hint), - style = CertiTheme.typography.caption.semibold_14, - color = CertiTheme.colors.gray300 - ) - } - innerTextField() - }, - modifier = Modifier - .weight(1f) - .padding(horizontal = screenWidthDp(10.dp)) - ) + if(certStateType == CertStateType.NORMAL) { + Spacer(Modifier.widthForScreenPercentage(12.dp)) - Spacer(Modifier.widthForScreenPercentage(12.dp)) + Icon( + painter = painterResource(R.drawable.ic_lock), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .widthForScreenPercentage(20.dp) + .heightForScreenPercentage(20.dp) + ) + + Spacer(Modifier.widthForScreenPercentage(8.dp)) + + Text( + text = stringResource(R.string.comment_write_unavailable), + style = CertiTheme.typography.caption.semibold_14, + color = CertiTheme.colors.gray300 + ) + } else { + BasicTextField( + value = commentText, + onValueChange = { commentText = it }, + singleLine = true, + maxLines = 1, + textStyle = CertiTheme.typography.caption.regular_14.copy( + color = CertiTheme.colors.black + ), + cursorBrush = SolidColor(CertiTheme.colors.black), + decorationBox = { innerTextField -> + if (commentText.isEmpty()) { + Text( + text = stringResource(R.string.comment_hint), + style = CertiTheme.typography.caption.semibold_14, + color = CertiTheme.colors.gray300 + ) + } - Icon( - painter = painterResource(R.drawable.ic_send), - tint = if (commentText.isEmpty()) Color.Unspecified else CertiTheme.colors.purpleBlue, - modifier = Modifier - .widthForScreenPercentage(24.dp) - .heightForScreenPercentage(24.dp) - .noRippleClickable { - writeComment(commentText) + innerTextField() }, - contentDescription = null - ) + modifier = Modifier + .weight(1f) + .padding(horizontal = screenWidthDp(10.dp)) + ) + + Spacer(Modifier.widthForScreenPercentage(12.dp)) + + Icon( + painter = painterResource(R.drawable.ic_send), + tint = if (commentText.isEmpty()) Color.Unspecified else CertiTheme.colors.purpleBlue, + modifier = Modifier + .widthForScreenPercentage(24.dp) + .heightForScreenPercentage(24.dp) + .noRippleClickable { + writeComment(commentText) + }, + contentDescription = null + ) + } } } } @@ -298,5 +323,5 @@ private fun PreviewCertDetailCommentScreen() { val dummyFlow = flowOf(dummyPagingData) val dummyList = dummyFlow.collectAsLazyPagingItems() - CertDetailCommentScreen(commentData = dummyList, totalCommentCount = 1, myUserId = 0) + CertDetailCommentScreen(commentData = dummyList, totalCommentCount = 1, myUserId = 0, certStateType = CertStateType.NORMAL) } diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 00000000..c8bb32eb --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 93156960..d012d8f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ 댓글(%s) 댓글을 입력하세요 아직 댓글이 없습니다.\n가장 먼저 댓글을 작성해보세요. + 자격증 취득예정/완료 후 댓글 작성이 가능합니다. \ No newline at end of file From 04c78c5988fcae5b4e308978ff8f5ea9b11ae26a Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 8 Feb 2026 01:23:39 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/certi/core/network/TokenManager.kt | 8 +++++ .../user/UserInfoResponseDtoMapper.kt | 2 ++ .../dto/response/UserInfoResponseDto.kt | 4 +++ .../certi/domain/model/user/UserInfoData.kt | 19 ++++++++++- .../ui/certdetail/CertDetailViewModel.kt | 34 +++++++++++++++++-- .../component/comment/CommentItem.kt | 4 +-- .../screen/CertDetailCommentScreen.kt | 34 +++++++++++++++++-- .../presentation/ui/home/HomeViewModel.kt | 1 + 8 files changed, 98 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/core/network/TokenManager.kt b/app/src/main/java/org/sopt/certi/core/network/TokenManager.kt index 93531ee4..7d82b3f5 100644 --- a/app/src/main/java/org/sopt/certi/core/network/TokenManager.kt +++ b/app/src/main/java/org/sopt/certi/core/network/TokenManager.kt @@ -76,6 +76,14 @@ class TokenManager @Inject constructor( return sharedPreferences.getString("NICKNAME", "").orEmpty() } + fun saveUserId(userId: Long) { + sharedPreferences.edit().putLong("USERID", userId).apply() + } + + fun getUserId(): Long { + return sharedPreferences.getLong("USERID", 0L) + } + fun nicknameFlow(): Flow = callbackFlow { trySend(getNickName()) diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todomain/user/UserInfoResponseDtoMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todomain/user/UserInfoResponseDtoMapper.kt index 7aeb5c3b..6cc610ea 100644 --- a/app/src/main/java/org/sopt/certi/data/mapper/todomain/user/UserInfoResponseDtoMapper.kt +++ b/app/src/main/java/org/sopt/certi/data/mapper/todomain/user/UserInfoResponseDtoMapper.kt @@ -4,10 +4,12 @@ import org.sopt.certi.data.remote.dto.response.UserInfoResponseDto import org.sopt.certi.domain.model.user.UserInfoData fun UserInfoResponseDto.toDomain() = UserInfoData( + userId = userId, nickname = nickname, name = name, university = university, major = major, + job = job, profileImageUrl = profileImage, birthday = birthDate ) diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/response/UserInfoResponseDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/response/UserInfoResponseDto.kt index e6c77cd0..b07f674a 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/dto/response/UserInfoResponseDto.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/response/UserInfoResponseDto.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable @Serializable data class UserInfoResponseDto( + @SerialName("userId") + val userId: Long, @SerialName("nickname") val nickname: String, @SerialName("name") @@ -13,6 +15,8 @@ data class UserInfoResponseDto( val university: String, @SerialName("major") val major: String, + @SerialName("job") + val job: String, @SerialName("profileImage") val profileImage: String?, @SerialName("birthDate") diff --git a/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt b/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt index de855b6c..d8138fe4 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt @@ -1,12 +1,29 @@ package org.sopt.certi.domain.model.user data class UserInfoData( + val userId: Long = 0, val name: String, val nickname: String = "", val university: String = "", val major: String = "", val category: List = emptyList(), + val job: String = "", val track: String = "", val birthday: String? = null, val profileImageUrl: String? = null -) +) { + companion object { + fun defaultData() = UserInfoData( + userId = 0, + name = "", + nickname = "", + university = "", + major = "", + category = emptyList(), + job = "", + track = "", + birthday = null, + profileImageUrl = null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index bc7d599a..9a1f80e3 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -11,15 +11,20 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import org.sopt.certi.core.network.TokenManager import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.core.state.UiState import org.sopt.certi.domain.model.certification.CertificationData +import org.sopt.certi.domain.model.comment.RegisterCommentRequest +import org.sopt.certi.domain.model.user.UserInfoData +import org.sopt.certi.domain.usecase.UserInfoUseCase import org.sopt.certi.domain.usecase.acquisition.AcquiredCertUseCase import org.sopt.certi.domain.usecase.certification.GetCertInfoUseCase import org.sopt.certi.domain.usecase.comment.DeleteCommentUseCase @@ -41,15 +46,23 @@ class CertDetailViewModel @Inject constructor( private val registerCommentUseCase: RegisterCommentUseCase, private val likeCommentUseCase: LikeCommentUseCase, private val deleteCommentUseCase: DeleteCommentUseCase, + private val tokenManager: TokenManager ) : ViewModel() { private val _certDetailInfo = MutableStateFlow>(UiState.Init) private val _commentPagingData = MutableStateFlow>(PagingData.empty()) - val commentPagingData: StateFlow> = _commentPagingData + val commentPagingData: StateFlow> = _commentPagingData.asStateFlow() private val _totalCommentCount = MutableStateFlow(0) - val totalCommentCount: StateFlow = _totalCommentCount + val totalCommentCount: StateFlow = _totalCommentCount.asStateFlow() + + private val _myUserId = MutableStateFlow(0L) + val myUserId: StateFlow = _myUserId.asStateFlow() + + private val _registerCommentSuccess = MutableStateFlow>(UiState.Init) + val registerCommentSuccess: StateFlow> = _registerCommentSuccess.asStateFlow() + val detailUiState: StateFlow = combine( @@ -69,6 +82,7 @@ class CertDetailViewModel @Inject constructor( private val _sideEffect = Channel() val sideEffect = _sideEffect.receiveAsFlow() + fun getCertDetailInfo(certId: Long) = viewModelScope.launch { getCertInfoUseCase.invoke(certId).fold( onSuccess = { @@ -110,6 +124,10 @@ class CertDetailViewModel @Inject constructor( ) } + fun getMyUserId() = viewModelScope.launch { + _myUserId.value = tokenManager.getUserId() + } + fun getCommentList(certId: Long, commentSortType: CommentSortType) = viewModelScope.launch { val sortValue = if (commentSortType == CommentSortType.Famous) { listOf("likeCount", "desc") @@ -124,6 +142,7 @@ class CertDetailViewModel @Inject constructor( _commentPagingData.value = pagingData getTotalCommentCount() + _registerCommentSuccess.emit(UiState.Init) } } @@ -133,4 +152,15 @@ class CertDetailViewModel @Inject constructor( _totalCommentCount.value = it } } + + fun registerComment(certId: Long, content: String) = viewModelScope.launch { + registerCommentUseCase.invoke(registerCommentRequest = RegisterCommentRequest(content, certId)).fold( + onSuccess = { + _registerCommentSuccess.emit(UiState.Success(true)) + }, + onFailure = { + + } + ) + } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt index 1e06cd4f..23f7905a 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt @@ -156,7 +156,7 @@ fun CommentItem( Spacer(Modifier.widthForScreenPercentage(8.dp)) Text( - text = commentData.createdTime, + text = commentData.createdTime.split("T")[0].replace("-", "."), style = CertiTheme.typography.caption.semibold_12, color = CertiTheme.colors.gray400 ) @@ -208,7 +208,7 @@ fun CommentItemPreview() { userMajor = "컴퓨터공학과", userJob = "개발자", state = CertStateType.ANTICIPATED, - createdTime = "2024.07.21", + createdTime = "2026-02-05T03:25:01.699655", lastModifiedTime = "2024.07.21", isLike = true, likeCount = 15 diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 88aae1d0..c3c87884 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -19,6 +19,8 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon @@ -48,8 +50,10 @@ import androidx.paging.PagingData import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf import org.sopt.certi.R +import org.sopt.certi.core.state.UiState import org.sopt.certi.core.util.heightForScreenPercentage import org.sopt.certi.core.util.noRippleClickable import org.sopt.certi.core.util.screenHeightDp @@ -73,21 +77,40 @@ fun CertDetailCommentRoute( var commentSortType by remember { mutableStateOf(CommentSortType.Famous) } val commentList = viewModel.commentPagingData.collectAsLazyPagingItems() val totalCommentCount by viewModel.totalCommentCount.collectAsStateWithLifecycle() + val myUserId by viewModel.myUserId.collectAsStateWithLifecycle() + + val listState = rememberLazyListState() LaunchedEffect(commentSortType) { + viewModel.getMyUserId() viewModel.getCommentList(certificationId, commentSortType) } + LaunchedEffect(Unit) { + viewModel.registerCommentSuccess.collect { uiState -> + when(uiState) { + is UiState.Success -> { + commentList.refresh() + + delay(500) + listState.animateScrollToItem(commentList.itemCount) + } + else -> {} + } + } + } + CertDetailCommentScreen( commentData = commentList, totalCommentCount = totalCommentCount, - myUserId = 0, + myUserId = myUserId, certStateType = certStateType, + listState = listState, changeSortType = { changedSortType -> commentSortType = changedSortType }, writeComment = { content -> - + viewModel.registerComment(certId = certificationId, content = content) }, likeOnClick = { like, commentId -> @@ -108,6 +131,7 @@ fun CertDetailCommentScreen( totalCommentCount: Int, myUserId: Long, certStateType: CertStateType, + listState: LazyListState = rememberLazyListState(), changeSortType: (CommentSortType) -> Unit = {}, writeComment: (content: String) -> Unit = {}, likeOnClick: (like: Boolean, commentId: Long) -> Unit = { _, _ -> }, @@ -179,11 +203,13 @@ fun CertDetailCommentScreen( CommentEmptyView() } else { LazyColumn( + state = listState, contentPadding = PaddingValues(top = screenHeightDp(12.dp)), verticalArrangement = Arrangement.spacedBy(screenHeightDp(12.dp)) ) { items( - count = commentData.itemCount + count = commentData.itemCount, + key = commentData.itemKey { it.commentId } ) { index -> commentData[index]?.let { comment -> CommentItem( @@ -294,6 +320,8 @@ fun CertDetailCommentScreen( .heightForScreenPercentage(24.dp) .noRippleClickable { writeComment(commentText) + + commentText = "" }, contentDescription = null ) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/home/HomeViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/home/HomeViewModel.kt index 7f26844e..4fe2d548 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/home/HomeViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/home/HomeViewModel.kt @@ -75,6 +75,7 @@ class HomeViewModel @Inject constructor( .onSuccess { result -> _userInfoLoadState.value = UiState.Success(result) tokenManager.saveNickName(result.nickname) + tokenManager.saveUserId(result.userId) } .onFailure { _userInfoLoadState.value = UiState.Failure(it.toString()) From c94a34834af202c34cb443fb3d04c8666598d5fc Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 8 Feb 2026 01:37:49 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/certdetail/CertDetailScreen.kt | 4 ++-- .../ui/certdetail/CertDetailViewModel.kt | 20 ++++++++++++++++++- .../component/comment/CommentItem.kt | 4 ++-- .../screen/CertDetailCommentScreen.kt | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt index b4aeae67..a17682c5 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailScreen.kt @@ -191,13 +191,13 @@ fun CertDetailScreen( LaunchedEffect(acquireExpectSuccess) { if (acquireExpectSuccess) { - certState = CertStateType.ACQUISITION + certState = CertStateType.ANTICIPATED } } LaunchedEffect(acquireSuccess) { if (acquireSuccess) { - certState = CertStateType.ANTICIPATED + certState = CertStateType.ACQUISITION } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 9a1f80e3..1668206b 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -159,8 +159,26 @@ class CertDetailViewModel @Inject constructor( _registerCommentSuccess.emit(UiState.Success(true)) }, onFailure = { - + _registerCommentSuccess.emit(UiState.Failure(it.message.toString())) } ) } + + fun likeComment(commentId: Long) = viewModelScope.launch { + likeCommentUseCase.invoke(commentId).fold( + onSuccess = {}, + onFailure = {} + ) + } + + fun deleteComment(commentId: Long) = viewModelScope.launch { + deleteCommentUseCase.invoke(commentId).fold( + onSuccess = {}, + onFailure = {} + ) + } + + fun reportComment(commentId: Long) = viewModelScope.launch { + + } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt index 23f7905a..bcd091a3 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt @@ -54,11 +54,11 @@ fun CommentItem( when (commentData.state) { CertStateType.ANTICIPATED, -> { - acquireStateText = stringResource(R.string.comment_state_acquired) + acquireStateText = stringResource(R.string.comment_state_pre) acquireStateTextColor = CertiTheme.colors.purpleBlue } CertStateType.ACQUISITION -> { - acquireStateText = stringResource(R.string.comment_state_pre) + acquireStateText = stringResource(R.string.comment_state_acquired) acquireStateTextColor = CertiTheme.colors.gray300 } CertStateType.NORMAL -> { diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index c3c87884..9e54f965 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -113,7 +113,7 @@ fun CertDetailCommentRoute( viewModel.registerComment(certId = certificationId, content = content) }, likeOnClick = { like, commentId -> - + viewModel.likeComment(commentId) }, reportOnClick = { commentId -> From b33827ea4f153ced50a1f9643e5f47e54350eb15 Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 8 Feb 2026 20:29:27 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dialog/CertiContentDialog.kt | 100 ++++++++++++++++++ .../core/component/dialog/CertiDialog.kt | 11 ++ .../ui/certdetail/CertDetailViewModel.kt | 23 ++-- .../screen/CertDetailCommentScreen.kt | 27 ++++- .../sideeffect/CommentDialogState.kt | 6 ++ app/src/main/res/values/strings.xml | 2 + 6 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt create mode 100644 app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt diff --git a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt new file mode 100644 index 00000000..1103cd21 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt @@ -0,0 +1,100 @@ +package org.sopt.certi.core.component.dialog + + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import org.sopt.certi.R +import org.sopt.certi.core.util.heightForScreenPercentage +import org.sopt.certi.core.util.screenHeightDp +import org.sopt.certi.core.util.screenWidthDp +import org.sopt.certi.ui.theme.CertiTheme + +@Composable +fun CertiContentDialog( + titleText: String, + contentText: String, + onConfirmClick: () -> Unit, + onDismissClick: () -> Unit +) { + Dialog(onDismissRequest = onDismissClick) { + Column( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background(CertiTheme.colors.white) + .padding(top = screenHeightDp(30.dp)), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = titleText, + style = CertiTheme.typography.body.semibold_16, + color = CertiTheme.colors.gray600, + modifier = Modifier.padding(horizontal = screenWidthDp(16.dp)), + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.heightForScreenPercentage(16.dp)) + + Text( + text = contentText, + style = CertiTheme.typography.caption.regular_14, + color = CertiTheme.colors.gray600, + modifier = Modifier.padding(horizontal = screenWidthDp(16.dp)), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.heightForScreenPercentage(26.dp)) + + HorizontalDivider( + thickness = 1.dp, + color = CertiTheme.colors.gray100 + ) + + Row(modifier = Modifier.height(IntrinsicSize.Max)) { + DialogButton( + text = stringResource(R.string.delete_dialog_cancel), + textColor = CertiTheme.colors.black, + onClick = onDismissClick, + modifier = Modifier.weight(1f) + ) + VerticalDivider( + thickness = 1.dp, + color = CertiTheme.colors.gray100 + ) + DialogButton( + text = stringResource(R.string.delete_dialog_confirm), + textColor = CertiTheme.colors.purpleBlue, + onClick = onConfirmClick, + modifier = Modifier.weight(1f) + ) + } + } + } +} + +@Preview +@Composable +private fun PreviewCertiContentDialog() { + CertiContentDialog( + titleText = "프리뷰 프리뷰", + contentText = "콘텐트 콘텐트", + onConfirmClick = {}, + onDismissClick = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt index 64b41f73..920be779 100644 --- a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt +++ b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import org.sopt.certi.R @@ -74,3 +75,13 @@ fun CertiDialog( } } } + +@Preview +@Composable +private fun PreviewCertiDialog() { + CertiDialog( + text = "프리뷰 프리뷰", + onConfirmClick = {}, + onDismissClick = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 1668206b..e21e6a07 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -1,11 +1,9 @@ package org.sopt.certi.presentation.ui.certdetail -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn -import androidx.paging.map import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -14,7 +12,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -23,8 +20,6 @@ import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.core.state.UiState import org.sopt.certi.domain.model.certification.CertificationData import org.sopt.certi.domain.model.comment.RegisterCommentRequest -import org.sopt.certi.domain.model.user.UserInfoData -import org.sopt.certi.domain.usecase.UserInfoUseCase import org.sopt.certi.domain.usecase.acquisition.AcquiredCertUseCase import org.sopt.certi.domain.usecase.certification.GetCertInfoUseCase import org.sopt.certi.domain.usecase.comment.DeleteCommentUseCase @@ -60,8 +55,8 @@ class CertDetailViewModel @Inject constructor( private val _myUserId = MutableStateFlow(0L) val myUserId: StateFlow = _myUserId.asStateFlow() - private val _registerCommentSuccess = MutableStateFlow>(UiState.Init) - val registerCommentSuccess: StateFlow> = _registerCommentSuccess.asStateFlow() + private val _updateCommentSuccess = MutableStateFlow>(UiState.Init) + val updateCommentSuccess: StateFlow> = _updateCommentSuccess.asStateFlow() val detailUiState: StateFlow = @@ -142,7 +137,7 @@ class CertDetailViewModel @Inject constructor( _commentPagingData.value = pagingData getTotalCommentCount() - _registerCommentSuccess.emit(UiState.Init) + _updateCommentSuccess.emit(UiState.Init) } } @@ -156,10 +151,10 @@ class CertDetailViewModel @Inject constructor( fun registerComment(certId: Long, content: String) = viewModelScope.launch { registerCommentUseCase.invoke(registerCommentRequest = RegisterCommentRequest(content, certId)).fold( onSuccess = { - _registerCommentSuccess.emit(UiState.Success(true)) + _updateCommentSuccess.emit(UiState.Success(true)) }, onFailure = { - _registerCommentSuccess.emit(UiState.Failure(it.message.toString())) + _updateCommentSuccess.emit(UiState.Failure(it.message.toString())) } ) } @@ -173,8 +168,12 @@ class CertDetailViewModel @Inject constructor( fun deleteComment(commentId: Long) = viewModelScope.launch { deleteCommentUseCase.invoke(commentId).fold( - onSuccess = {}, - onFailure = {} + onSuccess = { + _updateCommentSuccess.emit(UiState.Success(true)) + }, + onFailure = { + _updateCommentSuccess.emit(UiState.Failure(it.message.toString())) + } ) } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 9e54f965..817efcb4 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -53,6 +53,7 @@ import androidx.paging.compose.itemKey import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf import org.sopt.certi.R +import org.sopt.certi.core.component.dialog.CertiContentDialog import org.sopt.certi.core.state.UiState import org.sopt.certi.core.util.heightForScreenPercentage import org.sopt.certi.core.util.noRippleClickable @@ -66,6 +67,7 @@ import org.sopt.certi.presentation.ui.certdetail.CertDetailViewModel import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButton import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentEmptyView import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentItem +import org.sopt.certi.presentation.ui.certdetail.sideeffect.CommentDialogState import org.sopt.certi.ui.theme.CertiTheme @Composable @@ -81,13 +83,17 @@ fun CertDetailCommentRoute( val listState = rememberLazyListState() + var commentDeleteDialogState by remember { mutableStateOf(CommentDialogState.Hidden) } + + + LaunchedEffect(commentSortType) { viewModel.getMyUserId() viewModel.getCommentList(certificationId, commentSortType) } LaunchedEffect(Unit) { - viewModel.registerCommentSuccess.collect { uiState -> + viewModel.updateCommentSuccess.collect { uiState -> when(uiState) { is UiState.Success -> { commentList.refresh() @@ -119,9 +125,26 @@ fun CertDetailCommentRoute( }, deleteOnClick = { commentId -> - + commentDeleteDialogState = CommentDialogState.ShowDeleteCommentDialog(commentId) } ) + + when (val state = commentDeleteDialogState) { + is CommentDialogState.Hidden -> { } + is CommentDialogState.ShowDeleteCommentDialog -> { + CertiContentDialog( + titleText = stringResource(R.string.dialog_comment_delete_title), + contentText = stringResource(R.string.dialog_comment_delete_content), + onConfirmClick = { + viewModel.deleteComment(state.commentId) + commentDeleteDialogState = CommentDialogState.Hidden + }, + onDismissClick = { + commentDeleteDialogState = CommentDialogState.Hidden + } + ) + } + } } @OptIn(ExperimentalLayoutApi::class) diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt new file mode 100644 index 00000000..00ef74c5 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt @@ -0,0 +1,6 @@ +package org.sopt.certi.presentation.ui.certdetail.sideeffect + +sealed interface CommentDialogState { + data object Hidden : CommentDialogState + data class ShowDeleteCommentDialog(val commentId: Long) : CommentDialogState +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b146b6e3..feaa1c43 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -290,6 +290,8 @@ 댓글을 입력하세요 아직 댓글이 없습니다.\n가장 먼저 댓글을 작성해보세요. 자격증 취득예정/완료 후 댓글 작성이 가능합니다. + 삭제하시겠습니까? + 삭제하게 되면 복구할 수 없어요. \ No newline at end of file From b611323c80a43b81a4a1bc68542cb087136334e3 Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 8 Feb 2026 21:30:45 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[Feat]=20=EB=8F=99=EC=8B=9C=20=EC=97=B4?= =?UTF-8?q?=EB=9E=8C=20=EB=B6=88=EA=B0=80=ED=95=98=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/bottomsheet/RegisterTestInfoBottomSheet.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt b/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt index 3c5607c8..8f756145 100644 --- a/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt +++ b/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt @@ -298,7 +298,9 @@ fun RegisterTestInfoBottomSheet( } .padding(horizontal = screenWidthDp(12.dp)) .noRippleClickable { - showPlaceP1List = true + if(!showPlaceP2List) { + showPlaceP1List = true + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -328,7 +330,7 @@ fun RegisterTestInfoBottomSheet( .clip(RoundedCornerShape(4.dp)) .padding(horizontal = screenWidthDp(12.dp)) .noRippleClickable { - if (cityText.isNotEmpty()) { + if (cityText.isNotEmpty() && !showPlaceP1List) { showPlaceP2List = true } }, From 1060a75dfb22d67e1359ec6a40e8c008f31e74da Mon Sep 17 00:00:00 2001 From: kimjw Date: Tue, 10 Feb 2026 17:32:40 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[Feat]=20=EC=8B=A0=EA=B3=A0=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dialog/ReportCommentDialog.kt | 226 ++++++++++++++++++ .../screen/CertDetailCommentScreen.kt | 13 +- .../sideeffect/CommentDialogState.kt | 1 + .../main/res/drawable/ic_checkbox_empty.xml | 11 + .../main/res/drawable/ic_checkbox_fill.xml | 15 ++ app/src/main/res/values/strings.xml | 10 +- 6 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt create mode 100644 app/src/main/res/drawable/ic_checkbox_empty.xml create mode 100644 app/src/main/res/drawable/ic_checkbox_fill.xml diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt new file mode 100644 index 00000000..57df5e56 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt @@ -0,0 +1,226 @@ +package org.sopt.certi.presentation.ui.certdetail.component.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +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.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import org.sopt.certi.R +import org.sopt.certi.core.util.heightForScreenPercentage +import org.sopt.certi.core.util.noRippleClickable +import org.sopt.certi.core.util.pressedClickable +import org.sopt.certi.core.util.screenHeightDp +import org.sopt.certi.core.util.screenWidthDp +import org.sopt.certi.core.util.widthForScreenPercentage +import org.sopt.certi.ui.theme.CertiTheme + +@Composable +fun ReportCommentDialog( + onReportClick: () -> Unit, + onDismissClick: () -> Unit +) { + var contentText by remember { mutableStateOf("") } + var blockState by remember { mutableStateOf(false) } + + Dialog(onDismissRequest = onDismissClick) { + Column( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background(CertiTheme.colors.white) + .padding( + top = screenHeightDp(18.dp), + bottom = screenHeightDp(20.dp), + start = screenHeightDp(20.dp), + end = screenHeightDp(20.dp) + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = stringResource(R.string.dialog_comment_report_title), + style = CertiTheme.typography.caption.semibold_14, + color = CertiTheme.colors.black, + ) + + Spacer(Modifier.weight(1f)) + + Icon( + painter = painterResource(R.drawable.ic_close_20), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .widthForScreenPercentage(24.dp) + .heightForScreenPercentage(24.dp) + ) + } + + Spacer(Modifier.heightForScreenPercentage(20.dp)) + + Text( + text = stringResource(R.string.dialog_comment_report_content_title), + style = CertiTheme.typography.caption.semibold_12, + color = CertiTheme.colors.gray400, + modifier = Modifier.align(Alignment.Start) + ) + + Spacer(Modifier.heightForScreenPercentage(4.dp)) + + BasicTextField( + value = contentText, + onValueChange = { + if(contentText.length <= 100) { + contentText = it + } + }, + decorationBox = { innerTextField -> + if (contentText.isEmpty()) { + Text( + text = stringResource(R.string.dialog_comment_report_content_hint), + style = CertiTheme.typography.caption.regular_12, + color = CertiTheme.colors.gray300, + ) + } + innerTextField() + }, + cursorBrush = SolidColor(CertiTheme.colors.gray600), + textStyle = CertiTheme.typography.caption.regular_12.copy( + color = CertiTheme.colors.gray600 + ), + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = screenHeightDp(114.dp)) + .clip(RoundedCornerShape(12.dp)) + .background(CertiTheme.colors.gray0) + .padding(vertical = screenHeightDp(12.dp), horizontal = screenHeightDp(12.dp)) + ) + + Spacer(Modifier.heightForScreenPercentage(4.dp)) + + Text( + text = "${contentText.length}/100", + style = CertiTheme.typography.caption.regular_10, + color = CertiTheme.colors.gray300, + modifier = Modifier.align(Alignment.End) + ) + + Spacer(Modifier.heightForScreenPercentage(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth() + ) { + Icon( + painter = painterResource(if(blockState) R.drawable.ic_checkbox_fill else R.drawable.ic_checkbox_empty), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .noRippleClickable { + blockState = !blockState + } + ) + + Spacer(Modifier.widthForScreenPercentage(8.dp)) + + Text( + text = stringResource(R.string.dialog_comment_report_block_title), + style = CertiTheme.typography.caption.semibold_12, + color = CertiTheme.colors.gray600, + ) + } + + Spacer(Modifier.heightForScreenPercentage(8.dp)) + + Row { + Text( + text = " ∙ ", + style = CertiTheme.typography.caption.regular_12, + color = CertiTheme.colors.gray400, + ) + + Text( + text = stringResource(R.string.dialog_comment_report_note_p1), + style = CertiTheme.typography.caption.regular_12, + color = CertiTheme.colors.gray400, + ) + } + + Spacer(Modifier.heightForScreenPercentage(8.dp)) + + Row { + Text( + text = " ∙ ", + style = CertiTheme.typography.caption.regular_12, + color = CertiTheme.colors.gray400, + ) + + Text( + text = stringResource(R.string.dialog_comment_report_note_p2), + style = CertiTheme.typography.caption.regular_12, + color = CertiTheme.colors.gray400, + ) + } + + Spacer(Modifier.heightForScreenPercentage(14.dp)) + + Box( + modifier = Modifier + .background( + color = CertiTheme.colors.white, + shape = RoundedCornerShape(100.dp) + ) + .border(width = 1.dp, color = CertiTheme.colors.mainBlue, shape = RoundedCornerShape(100.dp)) + .padding(vertical = screenHeightDp(8.dp), horizontal = screenWidthDp(22.dp)) + .align(Alignment.End) + .noRippleClickable { + onReportClick() + }, + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.dialog_comment_report_confirm), + style = CertiTheme.typography.caption.semibold_12, + color = CertiTheme.colors.mainBlue + ) + } + } + } +} + +@Preview +@Composable +private fun PreviewReportCommentDialog() { + ReportCommentDialog( + onReportClick = { + + }, + onDismissClick = { + + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 817efcb4..450c7109 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -83,7 +83,7 @@ fun CertDetailCommentRoute( val listState = rememberLazyListState() - var commentDeleteDialogState by remember { mutableStateOf(CommentDialogState.Hidden) } + var commentDialogState by remember { mutableStateOf(CommentDialogState.Hidden) } @@ -125,11 +125,11 @@ fun CertDetailCommentRoute( }, deleteOnClick = { commentId -> - commentDeleteDialogState = CommentDialogState.ShowDeleteCommentDialog(commentId) + commentDialogState = CommentDialogState.ShowDeleteCommentDialog(commentId) } ) - when (val state = commentDeleteDialogState) { + when (val state = commentDialogState) { is CommentDialogState.Hidden -> { } is CommentDialogState.ShowDeleteCommentDialog -> { CertiContentDialog( @@ -137,13 +137,16 @@ fun CertDetailCommentRoute( contentText = stringResource(R.string.dialog_comment_delete_content), onConfirmClick = { viewModel.deleteComment(state.commentId) - commentDeleteDialogState = CommentDialogState.Hidden + commentDialogState = CommentDialogState.Hidden }, onDismissClick = { - commentDeleteDialogState = CommentDialogState.Hidden + commentDialogState = CommentDialogState.Hidden } ) } + is CommentDialogState.ShowReportCommentDialog -> { + + } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt index 00ef74c5..261676ff 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt @@ -3,4 +3,5 @@ package org.sopt.certi.presentation.ui.certdetail.sideeffect sealed interface CommentDialogState { data object Hidden : CommentDialogState data class ShowDeleteCommentDialog(val commentId: Long) : CommentDialogState + data class ShowReportCommentDialog(val commentId: Long, val content: String) : CommentDialogState } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_checkbox_empty.xml b/app/src/main/res/drawable/ic_checkbox_empty.xml new file mode 100644 index 00000000..5917a48d --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_empty.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_checkbox_fill.xml b/app/src/main/res/drawable/ic_checkbox_fill.xml new file mode 100644 index 00000000..882124aa --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_fill.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index feaa1c43..e2f989e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -292,6 +292,14 @@ 자격증 취득예정/완료 후 댓글 작성이 가능합니다. 삭제하시겠습니까? 삭제하게 되면 복구할 수 없어요. - + 신고하기 + 신고 내용 + 내용을 입력해주세요. + 해당 유저 차단하기 + 신고된 콘텐츠는 관리자가 검토하며, 필요 시 댓글 삭제, 계정 제한 등의 조치가 이루어집니다. + 차단 기능을 사용하면, 관리자의 확인을 거쳐 해당 사용자의 댓글이 사용자에게 노출되지 않도록 처리됩니다. + 제출 + + \ No newline at end of file From 0fd51883bac50bd13809d599b9fc601090759fef Mon Sep 17 00:00:00 2001 From: kimjw Date: Thu, 12 Feb 2026 02:19:30 +0900 Subject: [PATCH 14/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/mapper/todto/report/ReportMapper.kt | 11 ++++++++++ .../datasource/ReportRemoteDataSource.kt | 11 ++++++++++ .../ReportRemoteDataSourceImpl.kt | 15 +++++++++++++ .../request/report/ReportCommentRequestDto.kt | 12 ++++++++++ .../data/remote/service/ReportService.kt | 15 +++++++++++++ .../repositoryimpl/ReportRepositoryImpl.kt | 19 ++++++++++++++++ .../org/sopt/certi/di/DataSourceModule.kt | 6 +++++ .../org/sopt/certi/di/RepositoryModule.kt | 6 +++++ .../java/org/sopt/certi/di/ServiceModule.kt | 6 +++++ .../java/org/sopt/certi/di/UseCaseModule.kt | 8 +++++++ .../model/report/ReportCommentRequest.kt | 6 +++++ .../domain/repository/ReportRepository.kt | 7 ++++++ .../usecase/report/ReportCommentUseCase.kt | 12 ++++++++++ .../ui/certdetail/CertDetailViewModel.kt | 10 +++++++-- .../component/comment/CommentItem.kt | 22 ++++++++++--------- .../component/dialog/ReportCommentDialog.kt | 8 +++---- .../screen/CertDetailCommentScreen.kt | 13 +++++++++-- .../sideeffect/CommentDialogState.kt | 2 +- 18 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt create mode 100644 app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt create mode 100644 app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt create mode 100644 app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt new file mode 100644 index 00000000..08223e18 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt @@ -0,0 +1,11 @@ +package org.sopt.certi.data.mapper.todto.report + +import org.sopt.certi.data.remote.dto.request.report.ReportCommentRequestDto +import org.sopt.certi.domain.model.report.ReportCommentRequest + +fun ReportCommentRequest.toDto() : ReportCommentRequestDto { + return ReportCommentRequestDto( + content = this.content, + shouldBlockUser = this.shouldBlockUser + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt b/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt new file mode 100644 index 00000000..b280f3a7 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt @@ -0,0 +1,11 @@ +package org.sopt.certi.data.remote.datasource + +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.report.ReportCommentRequestDto + +interface ReportRemoteDataSource { + suspend fun reportComment( + certificationCommentId: Long, + reportCommentRequestDto: ReportCommentRequestDto + ): NullableApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt new file mode 100644 index 00000000..76cd9600 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt @@ -0,0 +1,15 @@ +package org.sopt.certi.data.remote.datasourceimpl + +import org.sopt.certi.data.remote.datasource.ReportRemoteDataSource +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.report.ReportCommentRequestDto +import org.sopt.certi.data.remote.service.ReportService +import javax.inject.Inject + +class ReportRemoteDataSourceImpl @Inject constructor( + private val reportService: ReportService +) : ReportRemoteDataSource { + override suspend fun reportComment(certificationCommentId: Long, reportCommentRequestDto: ReportCommentRequestDto): NullableApiResponse { + return reportService.reportComment(certificationCommentId, reportCommentRequestDto) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt new file mode 100644 index 00000000..1c0cfe53 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.data.remote.dto.request.report + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReportCommentRequestDto ( + @SerialName("content") + val content: String, + @SerialName("shouldBlockUser") + val shouldBlockUser: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt new file mode 100644 index 00000000..6ad29f5c --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt @@ -0,0 +1,15 @@ +package org.sopt.certi.data.remote.service + +import org.sopt.certi.data.remote.dto.base.NullableApiResponse +import org.sopt.certi.data.remote.dto.request.report.ReportCommentRequestDto +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +interface ReportService { + @POST("/api/v1/report/comment/{certification_comment_id}") + suspend fun reportComment( + @Path("certification_comment_id") certificationCommentId: Long, + @Body reportCommentRequestDto: ReportCommentRequestDto + ): NullableApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt new file mode 100644 index 00000000..2fcd0b22 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt @@ -0,0 +1,19 @@ +package org.sopt.certi.data.repositoryimpl + +import org.sopt.certi.data.mapper.todto.report.toDto +import org.sopt.certi.data.remote.datasource.ReportRemoteDataSource +import org.sopt.certi.data.remote.util.HttpResponseHandler.handleNullableApiResponse +import org.sopt.certi.domain.model.report.ReportCommentRequest +import org.sopt.certi.domain.repository.ReportRepository +import javax.inject.Inject + +class ReportRepositoryImpl @Inject constructor( + private val reportRemoteDataSource: ReportRemoteDataSource +) : ReportRepository { + override suspend fun reportComment(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest): Result { + return runCatching { reportRemoteDataSource.reportComment(certificationCommentId, reportCommentRequest.toDto()) + .handleNullableApiResponse() + .getOrThrow() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt b/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt index baa83cec..586408ae 100644 --- a/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/certi/di/DataSourceModule.kt @@ -15,6 +15,7 @@ import org.sopt.certi.data.remote.datasource.DummyRemoteDataSource import org.sopt.certi.data.remote.datasource.HomeRemoteDataSource import org.sopt.certi.data.remote.datasource.PreCertEditRemoteDataSource import org.sopt.certi.data.remote.datasource.PreCertRemoteDataSource +import org.sopt.certi.data.remote.datasource.ReportRemoteDataSource import org.sopt.certi.data.remote.datasource.S3DataSource import org.sopt.certi.data.remote.datasourceimpl.AcquisitionRemoteDataSourceImpl import org.sopt.certi.data.remote.datasource.UserRemoteDataSource @@ -27,6 +28,7 @@ import org.sopt.certi.data.remote.datasourceimpl.DummyRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.HomeRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.PreCertEditRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.PreCertRemoteDataSourceImpl +import org.sopt.certi.data.remote.datasourceimpl.ReportRemoteDataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.S3DataSourceImpl import org.sopt.certi.data.remote.datasourceimpl.UserRemoteDataSourceImpl @@ -80,4 +82,8 @@ abstract class DataSourceModule { @Binds @Singleton abstract fun bindsCommentDataSource(commentRemoteDataSourceImpl: CommentRemoteDataSourceImpl): CommentRemoteDataSource + + @Binds + @Singleton + abstract fun bindsReportDataSource(reportRemoteDataSourceImpl: ReportRemoteDataSourceImpl): ReportRemoteDataSource } diff --git a/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt b/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt index 3be97291..02d269f1 100644 --- a/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/certi/di/RepositoryModule.kt @@ -14,6 +14,7 @@ import org.sopt.certi.data.repositoryimpl.DummyRepositoryImpl import org.sopt.certi.data.repositoryimpl.HomeRepositoryImpl import org.sopt.certi.data.repositoryimpl.PreCertEditRepositoryImpl import org.sopt.certi.data.repositoryimpl.PreCertRepositoryImpl +import org.sopt.certi.data.repositoryimpl.ReportRepositoryImpl import org.sopt.certi.data.repositoryimpl.S3RepositoryImpl import org.sopt.certi.domain.repository.AcquisitionRepository import org.sopt.certi.data.repositoryimpl.UserRepositoryImpl @@ -26,6 +27,7 @@ import org.sopt.certi.domain.repository.DummyRepository import org.sopt.certi.domain.repository.HomeRepository import org.sopt.certi.domain.repository.PreCertEditRepository import org.sopt.certi.domain.repository.PreCertRepository +import org.sopt.certi.domain.repository.ReportRepository import org.sopt.certi.domain.repository.S3Repository import org.sopt.certi.domain.repository.UserRepository import javax.inject.Singleton @@ -79,4 +81,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindCommentRepository(commentRepositoryImpl: CommentRepositoryImpl): CommentRepository + + @Binds + @Singleton + abstract fun bindReportRepository(reportRepositoryImpl: ReportRepositoryImpl): ReportRepository } diff --git a/app/src/main/java/org/sopt/certi/di/ServiceModule.kt b/app/src/main/java/org/sopt/certi/di/ServiceModule.kt index 10f0af37..6dc7d7c2 100644 --- a/app/src/main/java/org/sopt/certi/di/ServiceModule.kt +++ b/app/src/main/java/org/sopt/certi/di/ServiceModule.kt @@ -15,6 +15,7 @@ import org.sopt.certi.data.remote.service.DummyService import org.sopt.certi.data.remote.service.HomeService import org.sopt.certi.data.remote.service.PreCertEditService import org.sopt.certi.data.remote.service.PreCertService +import org.sopt.certi.data.remote.service.ReportService import org.sopt.certi.data.remote.service.S3Service import org.sopt.certi.data.remote.service.UserService import retrofit2.Retrofit @@ -83,4 +84,9 @@ object ServiceModule { @Singleton fun provideCommentService(retrofit: Retrofit): CommentService = retrofit.create(CommentService::class.java) + + @Provides + @Singleton + fun provideReportService(retrofit: Retrofit): ReportService = + retrofit.create(ReportService::class.java) } diff --git a/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt b/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt index 1e432b26..f07c475d 100644 --- a/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt +++ b/app/src/main/java/org/sopt/certi/di/UseCaseModule.kt @@ -14,6 +14,7 @@ import org.sopt.certi.domain.repository.DummyRepository import org.sopt.certi.domain.repository.HomeRepository import org.sopt.certi.domain.repository.PreCertEditRepository import org.sopt.certi.domain.repository.PreCertRepository +import org.sopt.certi.domain.repository.ReportRepository import org.sopt.certi.domain.repository.UserRepository import org.sopt.certi.domain.usecase.activity.AddActivityUseCase import org.sopt.certi.domain.usecase.career.AddCareerUseCase @@ -48,6 +49,7 @@ import org.sopt.certi.domain.usecase.comment.GetCommentListUseCase import org.sopt.certi.domain.usecase.comment.LikeCommentUseCase import org.sopt.certi.domain.usecase.comment.RegisterCommentUseCase import org.sopt.certi.domain.usecase.precert.AcquireExpectCertUseCase +import org.sopt.certi.domain.usecase.report.ReportCommentUseCase import org.sopt.certi.domain.usecase.user.GetInterestedJobListUseCase import org.sopt.certi.domain.usecase.user.ModifyInterestedJobListUseCase import javax.inject.Singleton @@ -260,4 +262,10 @@ object UseCaseModule { fun provideDeleteCommentUseCase( commentRepository: CommentRepository ): DeleteCommentUseCase = DeleteCommentUseCase(commentRepository) + + @Provides + @Singleton + fun provideReportCommentUseCase( + reportRepository: ReportRepository + ): ReportCommentUseCase = ReportCommentUseCase(reportRepository) } diff --git a/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt new file mode 100644 index 00000000..34313512 --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt @@ -0,0 +1,6 @@ +package org.sopt.certi.domain.model.report + +data class ReportCommentRequest ( + val content: String, + val shouldBlockUser: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt new file mode 100644 index 00000000..e256962f --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt @@ -0,0 +1,7 @@ +package org.sopt.certi.domain.repository + +import org.sopt.certi.domain.model.report.ReportCommentRequest + +interface ReportRepository { + suspend fun reportComment(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt new file mode 100644 index 00000000..d67c347b --- /dev/null +++ b/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.certi.domain.usecase.report + +import org.sopt.certi.domain.model.report.ReportCommentRequest +import org.sopt.certi.domain.repository.ReportRepository +import javax.inject.Inject + +class ReportCommentUseCase @Inject constructor( + private val reportRepository: ReportRepository +) { + suspend operator fun invoke(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest) : Result = + reportRepository.reportComment(certificationCommentId, reportCommentRequest) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index e21e6a07..7149c42a 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -20,6 +20,7 @@ import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.core.state.UiState import org.sopt.certi.domain.model.certification.CertificationData import org.sopt.certi.domain.model.comment.RegisterCommentRequest +import org.sopt.certi.domain.model.report.ReportCommentRequest import org.sopt.certi.domain.usecase.acquisition.AcquiredCertUseCase import org.sopt.certi.domain.usecase.certification.GetCertInfoUseCase import org.sopt.certi.domain.usecase.comment.DeleteCommentUseCase @@ -27,6 +28,7 @@ import org.sopt.certi.domain.usecase.comment.GetCommentListUseCase import org.sopt.certi.domain.usecase.comment.LikeCommentUseCase import org.sopt.certi.domain.usecase.comment.RegisterCommentUseCase import org.sopt.certi.domain.usecase.precert.AcquireExpectCertUseCase +import org.sopt.certi.domain.usecase.report.ReportCommentUseCase import org.sopt.certi.presentation.type.CommentSortType import org.sopt.certi.presentation.ui.certdetail.sideeffect.DetailSideEffect import org.sopt.certi.presentation.ui.certdetail.state.DetailUiState @@ -41,6 +43,7 @@ class CertDetailViewModel @Inject constructor( private val registerCommentUseCase: RegisterCommentUseCase, private val likeCommentUseCase: LikeCommentUseCase, private val deleteCommentUseCase: DeleteCommentUseCase, + private val reportCommentUseCase: ReportCommentUseCase, private val tokenManager: TokenManager ) : ViewModel() { @@ -177,7 +180,10 @@ class CertDetailViewModel @Inject constructor( ) } - fun reportComment(commentId: Long) = viewModelScope.launch { - + fun reportComment(commentId: Long, content: String, block: Boolean) = viewModelScope.launch { + reportCommentUseCase.invoke(commentId, ReportCommentRequest(content, block)).fold( + onSuccess = {}, + onFailure = {} + ) } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt index bcd091a3..6527f022 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt @@ -144,16 +144,18 @@ fun CommentItem( Spacer(Modifier.widthForScreenPercentage(8.dp)) - Text( - text = stringResource(R.string.comment_report), - style = CertiTheme.typography.caption.semibold_12, - color = CertiTheme.colors.gray400, - modifier = Modifier.noRippleClickable { - reportOnClick() - } - ) - - Spacer(Modifier.widthForScreenPercentage(8.dp)) + if(commentData.userId != myUserId) { + Text( + text = stringResource(R.string.comment_report), + style = CertiTheme.typography.caption.semibold_12, + color = CertiTheme.colors.gray400, + modifier = Modifier.noRippleClickable { + reportOnClick() + } + ) + + Spacer(Modifier.widthForScreenPercentage(8.dp)) + } Text( text = commentData.createdTime.split("T")[0].replace("-", "."), diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt index 57df5e56..a5b88b9d 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt @@ -40,7 +40,7 @@ import org.sopt.certi.ui.theme.CertiTheme @Composable fun ReportCommentDialog( - onReportClick: () -> Unit, + onReportClick: (content: String, block: Boolean) -> Unit, onDismissClick: () -> Unit ) { var contentText by remember { mutableStateOf("") } @@ -95,7 +95,7 @@ fun ReportCommentDialog( BasicTextField( value = contentText, onValueChange = { - if(contentText.length <= 100) { + if(contentText.length < 100) { contentText = it } }, @@ -198,7 +198,7 @@ fun ReportCommentDialog( .padding(vertical = screenHeightDp(8.dp), horizontal = screenWidthDp(22.dp)) .align(Alignment.End) .noRippleClickable { - onReportClick() + onReportClick(contentText, blockState) }, contentAlignment = Alignment.Center ) { @@ -216,7 +216,7 @@ fun ReportCommentDialog( @Composable private fun PreviewReportCommentDialog() { ReportCommentDialog( - onReportClick = { + onReportClick = { _, _ -> }, onDismissClick = { diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index 450c7109..f54787a1 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -67,6 +67,7 @@ import org.sopt.certi.presentation.ui.certdetail.CertDetailViewModel import org.sopt.certi.presentation.ui.certdetail.component.chip.CommentArrayButton import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentEmptyView import org.sopt.certi.presentation.ui.certdetail.component.comment.CommentItem +import org.sopt.certi.presentation.ui.certdetail.component.dialog.ReportCommentDialog import org.sopt.certi.presentation.ui.certdetail.sideeffect.CommentDialogState import org.sopt.certi.ui.theme.CertiTheme @@ -122,7 +123,7 @@ fun CertDetailCommentRoute( viewModel.likeComment(commentId) }, reportOnClick = { commentId -> - + commentDialogState = CommentDialogState.ShowReportCommentDialog(commentId) }, deleteOnClick = { commentId -> commentDialogState = CommentDialogState.ShowDeleteCommentDialog(commentId) @@ -145,7 +146,15 @@ fun CertDetailCommentRoute( ) } is CommentDialogState.ShowReportCommentDialog -> { - + ReportCommentDialog( + onReportClick = { content, block -> + viewModel.reportComment(state.commentId, content, block) + commentDialogState = CommentDialogState.Hidden + }, + onDismissClick = { + commentDialogState = CommentDialogState.Hidden + } + ) } } } diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt index 261676ff..00d0d45e 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt @@ -3,5 +3,5 @@ package org.sopt.certi.presentation.ui.certdetail.sideeffect sealed interface CommentDialogState { data object Hidden : CommentDialogState data class ShowDeleteCommentDialog(val commentId: Long) : CommentDialogState - data class ShowReportCommentDialog(val commentId: Long, val content: String) : CommentDialogState + data class ShowReportCommentDialog(val commentId: Long) : CommentDialogState } \ No newline at end of file From 8b13b64fc92db302d5078c1bcb7683d873be334f Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 20 Feb 2026 03:26:56 +0900 Subject: [PATCH 15/17] [Chore] ktlint --- .../RegisterTestInfoBottomSheet.kt | 2 +- .../component/dialog/CertiContentDialog.kt | 3 +-- .../core/component/dialog/CertiDialog.kt | 2 +- .../mapper/todomain/comment/CommentMapper.kt | 6 ++--- .../mapper/todto/comment/CommentMapper.kt | 6 ++--- .../data/mapper/todto/report/ReportMapper.kt | 4 ++-- .../certi/data/pagingsource/CertiPaging.kt | 8 +++---- .../datasource/CommentRemoteDataSource.kt | 2 +- .../datasource/ReportRemoteDataSource.kt | 2 +- .../CommentRemoteDataSourceImpl.kt | 2 +- .../ReportRemoteDataSourceImpl.kt | 2 +- .../comment/RegisterCommentRequestDto.kt | 2 +- .../request/report/ReportCommentRequestDto.kt | 4 ++-- .../comment/GetCommentListResponseDto.kt | 2 +- .../data/remote/service/CommentService.kt | 11 ++++----- .../data/remote/service/ReportService.kt | 2 +- .../repositoryimpl/CommentRepositoryImpl.kt | 3 +-- .../repositoryimpl/ReportRepositoryImpl.kt | 9 +++---- .../comment/CommentListPageableRequest.kt | 2 +- .../model/comment/RegisterCommentRequest.kt | 2 +- .../model/report/ReportCommentRequest.kt | 4 ++-- .../certi/domain/model/user/UserInfoData.kt | 2 +- .../domain/repository/CommentRepository.kt | 4 ++-- .../domain/repository/ReportRepository.kt | 2 +- .../usecase/comment/DeleteCommentUseCase.kt | 2 +- .../usecase/comment/GetCommentListUseCase.kt | 2 +- .../usecase/comment/LikeCommentUseCase.kt | 2 +- .../usecase/comment/RegisterCommentUseCase.kt | 2 +- .../usecase/report/ReportCommentUseCase.kt | 4 ++-- .../presentation/type/CommentSortType.kt | 2 +- .../ui/certdetail/CertDetailViewModel.kt | 2 -- .../component/comment/CommentEmptyView.kt | 11 ++++----- .../component/comment/CommentItem.kt | 4 ++-- .../component/dialog/ReportCommentDialog.kt | 24 ++++++++----------- .../screen/CertDetailCommentScreen.kt | 9 +++---- .../sideeffect/CommentDialogState.kt | 2 +- 36 files changed, 70 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt b/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt index 8f756145..7d64132d 100644 --- a/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt +++ b/app/src/main/java/org/sopt/certi/core/component/bottomsheet/RegisterTestInfoBottomSheet.kt @@ -298,7 +298,7 @@ fun RegisterTestInfoBottomSheet( } .padding(horizontal = screenWidthDp(12.dp)) .noRippleClickable { - if(!showPlaceP2List) { + if (!showPlaceP2List) { showPlaceP1List = true } }, diff --git a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt index 1103cd21..9f431611 100644 --- a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt +++ b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiContentDialog.kt @@ -1,6 +1,5 @@ package org.sopt.certi.core.component.dialog - import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -97,4 +96,4 @@ private fun PreviewCertiContentDialog() { onConfirmClick = {}, onDismissClick = {} ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt index 920be779..15c27d75 100644 --- a/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt +++ b/app/src/main/java/org/sopt/certi/core/component/dialog/CertiDialog.kt @@ -84,4 +84,4 @@ private fun PreviewCertiDialog() { onConfirmClick = {}, onDismissClick = {} ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt index 9a05a43e..bb345914 100644 --- a/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt +++ b/app/src/main/java/org/sopt/certi/data/mapper/todomain/comment/CommentMapper.kt @@ -6,7 +6,7 @@ import org.sopt.certi.domain.model.comment.CommentData import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.type.CertStateType -fun GetCommentListResponseDto.toDomain() : CommentData { +fun GetCommentListResponseDto.toDomain(): CommentData { return CommentData( content = content.map { it.toDomain() }, totalPages = totalPages, @@ -15,7 +15,7 @@ fun GetCommentListResponseDto.toDomain() : CommentData { ) } -fun CommentItemResponseDto.toDomain() : CommentItemData { +fun CommentItemResponseDto.toDomain(): CommentItemData { return CommentItemData( commentId = commentId, userId = userId, @@ -29,4 +29,4 @@ fun CommentItemResponseDto.toDomain() : CommentItemData { isLike = isLike, likeCount = likeCount ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt index f9442820..de7ca611 100644 --- a/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt +++ b/app/src/main/java/org/sopt/certi/data/mapper/todto/comment/CommentMapper.kt @@ -5,17 +5,17 @@ import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto import org.sopt.certi.domain.model.comment.CommentListPageableRequest import org.sopt.certi.domain.model.comment.RegisterCommentRequest -fun RegisterCommentRequest.toDto() : RegisterCommentRequestDto { +fun RegisterCommentRequest.toDto(): RegisterCommentRequestDto { return RegisterCommentRequestDto( content = content, certificationId = certificationId ) } -fun CommentListPageableRequest.toDto() : CommentListPageableRequestDto { +fun CommentListPageableRequest.toDto(): CommentListPageableRequestDto { return CommentListPageableRequestDto( page = page, size = size, sort = sort ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt b/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt index 08223e18..f73ea7a2 100644 --- a/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt +++ b/app/src/main/java/org/sopt/certi/data/mapper/todto/report/ReportMapper.kt @@ -3,9 +3,9 @@ package org.sopt.certi.data.mapper.todto.report import org.sopt.certi.data.remote.dto.request.report.ReportCommentRequestDto import org.sopt.certi.domain.model.report.ReportCommentRequest -fun ReportCommentRequest.toDto() : ReportCommentRequestDto { +fun ReportCommentRequest.toDto(): ReportCommentRequestDto { return ReportCommentRequestDto( content = this.content, shouldBlockUser = this.shouldBlockUser ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt index c7cb1dbb..a67aabf2 100644 --- a/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt +++ b/app/src/main/java/org/sopt/certi/data/pagingsource/CertiPaging.kt @@ -7,7 +7,7 @@ import androidx.paging.PagingState class CertiPagingSource( private val pageSize: Int, - private val getList: suspend (Int) -> List, + private val getList: suspend (Int) -> List ) : PagingSource() { override fun getRefreshKey(state: PagingState): Int? { val anchor = state.anchorPosition ?: return null @@ -30,13 +30,13 @@ class CertiPagingSource( } } -fun createPager( +fun createPager( limit: Int = 10, initialLoadSize: Int = 20, q: List? = null, startPage: Int? = null, pagingSourceFactory: suspend (page: Int, limit: Int, sort: List?) -> List -) : Pager { +): Pager { return Pager( config = PagingConfig( pageSize = limit, @@ -51,4 +51,4 @@ fun createPager( } } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt b/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt index d99125a0..961321af 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasource/CommentRemoteDataSource.kt @@ -11,4 +11,4 @@ interface CommentRemoteDataSource { suspend fun registerComment(registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse suspend fun likeComment(commentId: Long): NullableApiResponse suspend fun deleteComment(commentId: Long): NullableApiResponse -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt b/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt index b280f3a7..912b325f 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasource/ReportRemoteDataSource.kt @@ -8,4 +8,4 @@ interface ReportRemoteDataSource { certificationCommentId: Long, reportCommentRequestDto: ReportCommentRequestDto ): NullableApiResponse -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt index 1aec937e..5a0daf9c 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt @@ -23,4 +23,4 @@ class CommentRemoteDataSourceImpl @Inject constructor( override suspend fun deleteComment(commentId: Long): NullableApiResponse = commentService.deleteComment(commentId) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt index 76cd9600..674ff036 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/ReportRemoteDataSourceImpl.kt @@ -12,4 +12,4 @@ class ReportRemoteDataSourceImpl @Inject constructor( override suspend fun reportComment(certificationCommentId: Long, reportCommentRequestDto: ReportCommentRequestDto): NullableApiResponse { return reportService.reportComment(certificationCommentId, reportCommentRequestDto) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt index cf83318c..648b88e3 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/request/comment/RegisterCommentRequestDto.kt @@ -9,4 +9,4 @@ data class RegisterCommentRequestDto( val content: String, @SerialName("certificationId") val certificationId: Long -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt index 1c0cfe53..3bb20025 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/request/report/ReportCommentRequestDto.kt @@ -4,9 +4,9 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ReportCommentRequestDto ( +data class ReportCommentRequestDto( @SerialName("content") val content: String, @SerialName("shouldBlockUser") val shouldBlockUser: Boolean -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt b/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt index cc57ee15..bc2a2354 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/dto/response/comment/GetCommentListResponseDto.kt @@ -39,4 +39,4 @@ data class CommentItemResponseDto( val lastModifiedTime: String, @SerialName("isLike") val isLike: Boolean -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt index fd175d6f..7180d2b4 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt @@ -2,7 +2,6 @@ package org.sopt.certi.data.remote.service import org.sopt.certi.data.remote.dto.base.ApiResponse import org.sopt.certi.data.remote.dto.base.NullableApiResponse -import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto import org.sopt.certi.data.remote.dto.request.comment.RegisterCommentRequestDto import org.sopt.certi.data.remote.dto.response.comment.GetCommentListResponseDto import retrofit2.http.Body @@ -17,14 +16,14 @@ interface CommentService { suspend fun getCommentList( @Query("certificationId") certificationId: Long, @Query("pageable") pageable: String - ) : ApiResponse + ): ApiResponse @POST("api/v1/comments") - suspend fun registerComment(@Body registerCommentRequest: RegisterCommentRequestDto) : NullableApiResponse + suspend fun registerComment(@Body registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse @POST("api/v1/comments/{commentId}/like") - suspend fun likeComment(@Path("commentId") commentId: Long) : NullableApiResponse + suspend fun likeComment(@Path("commentId") commentId: Long): NullableApiResponse @DELETE("api/v1/comments/{commentId}") - suspend fun deleteComment(@Path("commentId") commentId: Long) : NullableApiResponse -} \ No newline at end of file + suspend fun deleteComment(@Path("commentId") commentId: Long): NullableApiResponse +} diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt index 6ad29f5c..cefa0a47 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/service/ReportService.kt @@ -12,4 +12,4 @@ interface ReportService { @Path("certification_comment_id") certificationCommentId: Long, @Body reportCommentRequestDto: ReportCommentRequestDto ): NullableApiResponse -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt index 1c82e677..d765e9fb 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/CommentRepositoryImpl.kt @@ -8,7 +8,6 @@ import org.sopt.certi.data.mapper.todto.comment.toDto import org.sopt.certi.data.pagingsource.createPager import org.sopt.certi.data.remote.datasource.CommentRemoteDataSource import org.sopt.certi.data.remote.dto.request.comment.CommentListPageableRequestDto -import org.sopt.certi.data.remote.util.HttpResponseHandler.handleApiResponse import org.sopt.certi.data.remote.util.HttpResponseHandler.handleNullableApiResponse import org.sopt.certi.domain.model.comment.CommentItemData import org.sopt.certi.domain.model.comment.RegisterCommentRequest @@ -68,4 +67,4 @@ class CommentRepositoryImpl @Inject constructor( .getOrThrow() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt b/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt index 2fcd0b22..9b64dd43 100644 --- a/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/repositoryimpl/ReportRepositoryImpl.kt @@ -11,9 +11,10 @@ class ReportRepositoryImpl @Inject constructor( private val reportRemoteDataSource: ReportRemoteDataSource ) : ReportRepository { override suspend fun reportComment(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest): Result { - return runCatching { reportRemoteDataSource.reportComment(certificationCommentId, reportCommentRequest.toDto()) - .handleNullableApiResponse() - .getOrThrow() + return runCatching { + reportRemoteDataSource.reportComment(certificationCommentId, reportCommentRequest.toDto()) + .handleNullableApiResponse() + .getOrThrow() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt index c926e16a..dc0e333a 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/comment/CommentListPageableRequest.kt @@ -4,4 +4,4 @@ data class CommentListPageableRequest( val page: Int, val size: Int, val sort: List -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt index e51e9557..ba67bf79 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/comment/RegisterCommentRequest.kt @@ -3,4 +3,4 @@ package org.sopt.certi.domain.model.comment data class RegisterCommentRequest( val content: String, val certificationId: Long -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt b/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt index 34313512..3e209e7d 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/report/ReportCommentRequest.kt @@ -1,6 +1,6 @@ package org.sopt.certi.domain.model.report -data class ReportCommentRequest ( +data class ReportCommentRequest( val content: String, val shouldBlockUser: Boolean -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt b/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt index d8138fe4..938199fe 100644 --- a/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt +++ b/app/src/main/java/org/sopt/certi/domain/model/user/UserInfoData.kt @@ -26,4 +26,4 @@ data class UserInfoData( profileImageUrl = null ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt index 7e052d0c..47d71cf7 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/CommentRepository.kt @@ -11,5 +11,5 @@ interface CommentRepository { suspend fun likeComment(commentId: Long): Result suspend fun deleteComment(commentId: Long): Result - fun getTotalCommentCount() : Flow -} \ No newline at end of file + fun getTotalCommentCount(): Flow +} diff --git a/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt b/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt index e256962f..4b45932c 100644 --- a/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt +++ b/app/src/main/java/org/sopt/certi/domain/repository/ReportRepository.kt @@ -4,4 +4,4 @@ import org.sopt.certi.domain.model.report.ReportCommentRequest interface ReportRepository { suspend fun reportComment(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest): Result -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt index 53f2bdc7..f41a86e2 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/DeleteCommentUseCase.kt @@ -9,4 +9,4 @@ class DeleteCommentUseCase @Inject constructor( suspend operator fun invoke(commentId: Long): Result { return commentRepository.deleteComment(commentId) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt index 5a66ca27..1b7618cd 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/GetCommentListUseCase.kt @@ -16,4 +16,4 @@ class GetCommentListUseCase @Inject constructor( fun getTotalCommentCount(): Flow { return commentRepository.getTotalCommentCount() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt index 0a802841..a1777188 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/LikeCommentUseCase.kt @@ -9,4 +9,4 @@ class LikeCommentUseCase @Inject constructor( suspend operator fun invoke(commentId: Long): Result { return commentRepository.likeComment(commentId) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt index fc1e06a8..344567e6 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/comment/RegisterCommentUseCase.kt @@ -10,4 +10,4 @@ class RegisterCommentUseCase @Inject constructor( suspend operator fun invoke(registerCommentRequest: RegisterCommentRequest): Result { return commentRepository.registerComment(registerCommentRequest) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt b/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt index d67c347b..17cbb9b4 100644 --- a/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt +++ b/app/src/main/java/org/sopt/certi/domain/usecase/report/ReportCommentUseCase.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class ReportCommentUseCase @Inject constructor( private val reportRepository: ReportRepository ) { - suspend operator fun invoke(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest) : Result = + suspend operator fun invoke(certificationCommentId: Long, reportCommentRequest: ReportCommentRequest): Result = reportRepository.reportComment(certificationCommentId, reportCommentRequest) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt b/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt index 58093625..354cd6ad 100644 --- a/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt +++ b/app/src/main/java/org/sopt/certi/presentation/type/CommentSortType.kt @@ -2,4 +2,4 @@ package org.sopt.certi.presentation.type enum class CommentSortType { Recent, Famous -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt index 7149c42a..0c27a317 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/CertDetailViewModel.kt @@ -61,7 +61,6 @@ class CertDetailViewModel @Inject constructor( private val _updateCommentSuccess = MutableStateFlow>(UiState.Init) val updateCommentSuccess: StateFlow> = _updateCommentSuccess.asStateFlow() - val detailUiState: StateFlow = combine( _certDetailInfo @@ -80,7 +79,6 @@ class CertDetailViewModel @Inject constructor( private val _sideEffect = Channel() val sideEffect = _sideEffect.receiveAsFlow() - fun getCertDetailInfo(certId: Long) = viewModelScope.launch { getCertInfoUseCase.invoke(certId).fold( onSuccess = { diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt index 8dfed8d3..c810133a 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentEmptyView.kt @@ -2,12 +2,9 @@ package org.sopt.certi.presentation.ui.certdetail.component.comment import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -23,9 +20,9 @@ import org.sopt.certi.ui.theme.CertiTheme @Composable fun CommentEmptyView( - modifier : Modifier = Modifier + modifier: Modifier = Modifier ) { - Column ( + Column( modifier = modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -33,7 +30,7 @@ fun CommentEmptyView( ) { Image( painter = painterResource(R.drawable.img_empty), - contentDescription = null, + contentDescription = null ) Spacer(Modifier.heightForScreenPercentage(20.dp)) @@ -51,4 +48,4 @@ fun CommentEmptyView( @Composable private fun PreviewCommentEmptyView() { CommentEmptyView() -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt index 6527f022..0f578e04 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/comment/CommentItem.kt @@ -53,7 +53,7 @@ fun CommentItem( var likeCountStatus by remember { mutableStateOf(commentData.likeCount) } when (commentData.state) { - CertStateType.ANTICIPATED, -> { + CertStateType.ANTICIPATED -> { acquireStateText = stringResource(R.string.comment_state_pre) acquireStateTextColor = CertiTheme.colors.purpleBlue } @@ -144,7 +144,7 @@ fun CommentItem( Spacer(Modifier.widthForScreenPercentage(8.dp)) - if(commentData.userId != myUserId) { + if (commentData.userId != myUserId) { Text( text = stringResource(R.string.comment_report), style = CertiTheme.typography.caption.semibold_12, diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt index a5b88b9d..daf4d46b 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/component/dialog/ReportCommentDialog.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,7 +31,6 @@ import androidx.compose.ui.window.Dialog import org.sopt.certi.R import org.sopt.certi.core.util.heightForScreenPercentage import org.sopt.certi.core.util.noRippleClickable -import org.sopt.certi.core.util.pressedClickable import org.sopt.certi.core.util.screenHeightDp import org.sopt.certi.core.util.screenWidthDp import org.sopt.certi.core.util.widthForScreenPercentage @@ -66,7 +64,7 @@ fun ReportCommentDialog( Text( text = stringResource(R.string.dialog_comment_report_title), style = CertiTheme.typography.caption.semibold_14, - color = CertiTheme.colors.black, + color = CertiTheme.colors.black ) Spacer(Modifier.weight(1f)) @@ -95,7 +93,7 @@ fun ReportCommentDialog( BasicTextField( value = contentText, onValueChange = { - if(contentText.length < 100) { + if (contentText.length < 100) { contentText = it } }, @@ -104,7 +102,7 @@ fun ReportCommentDialog( Text( text = stringResource(R.string.dialog_comment_report_content_hint), style = CertiTheme.typography.caption.regular_12, - color = CertiTheme.colors.gray300, + color = CertiTheme.colors.gray300 ) } innerTextField() @@ -136,7 +134,7 @@ fun ReportCommentDialog( modifier = Modifier.fillMaxWidth() ) { Icon( - painter = painterResource(if(blockState) R.drawable.ic_checkbox_fill else R.drawable.ic_checkbox_empty), + painter = painterResource(if (blockState) R.drawable.ic_checkbox_fill else R.drawable.ic_checkbox_empty), contentDescription = null, tint = Color.Unspecified, modifier = Modifier @@ -150,7 +148,7 @@ fun ReportCommentDialog( Text( text = stringResource(R.string.dialog_comment_report_block_title), style = CertiTheme.typography.caption.semibold_12, - color = CertiTheme.colors.gray600, + color = CertiTheme.colors.gray600 ) } @@ -160,13 +158,13 @@ fun ReportCommentDialog( Text( text = " ∙ ", style = CertiTheme.typography.caption.regular_12, - color = CertiTheme.colors.gray400, + color = CertiTheme.colors.gray400 ) Text( text = stringResource(R.string.dialog_comment_report_note_p1), style = CertiTheme.typography.caption.regular_12, - color = CertiTheme.colors.gray400, + color = CertiTheme.colors.gray400 ) } @@ -176,13 +174,13 @@ fun ReportCommentDialog( Text( text = " ∙ ", style = CertiTheme.typography.caption.regular_12, - color = CertiTheme.colors.gray400, + color = CertiTheme.colors.gray400 ) Text( text = stringResource(R.string.dialog_comment_report_note_p2), style = CertiTheme.typography.caption.regular_12, - color = CertiTheme.colors.gray400, + color = CertiTheme.colors.gray400 ) } @@ -217,10 +215,8 @@ fun ReportCommentDialog( private fun PreviewReportCommentDialog() { ReportCommentDialog( onReportClick = { _, _ -> - }, onDismissClick = { - } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt index f54787a1..ebcdaf1e 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/screen/CertDetailCommentScreen.kt @@ -86,8 +86,6 @@ fun CertDetailCommentRoute( var commentDialogState by remember { mutableStateOf(CommentDialogState.Hidden) } - - LaunchedEffect(commentSortType) { viewModel.getMyUserId() viewModel.getCommentList(certificationId, commentSortType) @@ -95,7 +93,7 @@ fun CertDetailCommentRoute( LaunchedEffect(Unit) { viewModel.updateCommentSuccess.collect { uiState -> - when(uiState) { + when (uiState) { is UiState.Success -> { commentList.refresh() @@ -234,7 +232,7 @@ fun CertDetailCommentScreen( ) } - if(commentData.itemCount == 0) { + if (commentData.itemCount == 0) { CommentEmptyView() } else { LazyColumn( @@ -299,8 +297,7 @@ fun CertDetailCommentScreen( .padding(end = screenWidthDp(12.dp)), verticalAlignment = Alignment.CenterVertically ) { - - if(certStateType == CertStateType.NORMAL) { + if (certStateType == CertStateType.NORMAL) { Spacer(Modifier.widthForScreenPercentage(12.dp)) Icon( diff --git a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt index 00d0d45e..a28f411f 100644 --- a/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt +++ b/app/src/main/java/org/sopt/certi/presentation/ui/certdetail/sideeffect/CommentDialogState.kt @@ -4,4 +4,4 @@ sealed interface CommentDialogState { data object Hidden : CommentDialogState data class ShowDeleteCommentDialog(val commentId: Long) : CommentDialogState data class ShowReportCommentDialog(val commentId: Long) : CommentDialogState -} \ No newline at end of file +} From af85e83be3c5b3f1135b03bbc388e8bc30b15763 Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 22 Feb 2026 03:55:25 +0900 Subject: [PATCH 16/17] [Fix] strings --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2f989e2..581763d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,7 +279,7 @@ 취득 완료 - 취득 예장 + 취득 예정 삭제 좋아요 %s 신고 From d6dbb3c9855d00323586b53a0c71a1891cfc4d2b Mon Sep 17 00:00:00 2001 From: kimjw Date: Sun, 22 Feb 2026 23:49:07 +0900 Subject: [PATCH 17/17] =?UTF-8?q?[Feat]=20=EB=8C=93=EA=B8=80=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=A0=95=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt | 2 +- .../java/org/sopt/certi/data/remote/service/CommentService.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt index 5a0daf9c..bf3b3728 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/datasourceimpl/CommentRemoteDataSourceImpl.kt @@ -13,7 +13,7 @@ class CommentRemoteDataSourceImpl @Inject constructor( private val commentService: CommentService ) : CommentRemoteDataSource { override suspend fun getCommentList(certificationId: Long, pageable: CommentListPageableRequestDto): ApiResponse = - commentService.getCommentList(certificationId, "{\"page\": ${pageable.page}, \"size\": ${pageable.size}, \"sort\": ${pageable.sort}}") + commentService.getCommentList(certificationId, page = pageable.page, size = pageable.size, sort = if (pageable.sort.isNotEmpty()) pageable.sort.toString() else null) override suspend fun registerComment(registerCommentRequest: RegisterCommentRequestDto): NullableApiResponse = commentService.registerComment(registerCommentRequest) diff --git a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt index 7180d2b4..237787bc 100644 --- a/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt +++ b/app/src/main/java/org/sopt/certi/data/remote/service/CommentService.kt @@ -15,7 +15,9 @@ interface CommentService { @GET("api/v1/comments") suspend fun getCommentList( @Query("certificationId") certificationId: Long, - @Query("pageable") pageable: String + @Query("page") page: Int, + @Query("size") size: Int, + @Query("sort") sort: String? = null ): ApiResponse @POST("api/v1/comments")