From 6a7b30064c8f86f7173d927b388e4af76e01b452 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Fri, 6 Feb 2026 18:32:30 +0900 Subject: [PATCH 01/30] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EB=8D=94=EB=AF=B8=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD?= =?UTF-8?q?=EC=9E=85=20repository=EB=A5=BC=20FakeRepository=EB=A1=9C=20ren?= =?UTF-8?q?ame=ED=9B=84=20=EA=B7=B8=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sitoryImpl.kt => FakeDiaryRepositoryImpl.kt} | 17 ++++++++++++++--- .../java/com/egobook/app/di/RepositoryModule.kt | 11 ++++++++--- .../java/com/egobook/app/di/UseCaseModule.kt | 4 ++-- ...iaryRepository.kt => FakeDiaryRepository.kt} | 14 +++++++++++++- .../app/domain/usecase/diaryusecase/AddDiary.kt | 4 ++-- .../domain/usecase/diaryusecase/DeleteDiary.kt | 4 ++-- .../domain/usecase/diaryusecase/GetDiaries.kt | 4 ++-- .../app/domain/usecase/diaryusecase/GetDiary.kt | 4 ++-- .../domain/usecase/diaryusecase/UpdateDiary.kt | 4 ++-- 9 files changed, 47 insertions(+), 19 deletions(-) rename app/src/main/java/com/egobook/app/data/repository/{DiaryRepositoryImpl.kt => FakeDiaryRepositoryImpl.kt} (95%) rename app/src/main/java/com/egobook/app/domain/repository/{DiaryRepository.kt => FakeDiaryRepository.kt} (60%) diff --git a/app/src/main/java/com/egobook/app/data/repository/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt similarity index 95% rename from app/src/main/java/com/egobook/app/data/repository/DiaryRepositoryImpl.kt rename to app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt index 32fabb33..b992bb4c 100644 --- a/app/src/main/java/com/egobook/app/data/repository/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt @@ -2,7 +2,7 @@ package com.egobook.app.data.repository import com.egobook.app.domain.model.Diary import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -10,9 +10,20 @@ import kotlinx.coroutines.flow.update import java.time.LocalDateTime import javax.inject.Inject -class DiaryRepositoryImpl @Inject constructor() : DiaryRepository { +/** + * 테스트 및 개발용 더미 일기 저장소 + * + * 메모리 내에서 더미 데이터를 관리하며, 실제 API 호출 없이 일기 기능을 테스트할 수 있습니다. + * + * 실제 백엔드 API 연동 시: + * 1. 새로운 DiaryRepositoryImpl 클래스를 생성하여 ApiService를 사용하여 구현 + * 2. RepositoryModule.kt에서 FakeDiaryRepositoryImpl -> DiaryRepositoryImpl로 변경 + * + * @see FakeDiaryRepository + */ +class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { - //더미데이터 삽입 + // 더미 데이터 삽입 private val diariesFlow = MutableStateFlow( listOf( // --- 오늘 날짜 (Today) --- diff --git a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt index 09f30d79..39dcac59 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt @@ -1,13 +1,13 @@ package com.egobook.app.di import com.egobook.app.data.repository.CounselingRepositoryImpl -import com.egobook.app.data.repository.DiaryRepositoryImpl +import com.egobook.app.data.repository.FakeDiaryRepositoryImpl import com.egobook.app.data.repository.FriendsRepositoryImpl import com.egobook.app.data.repository.NotificationRepositoryImpl import com.egobook.app.domain.repository.CounselingRepository import com.egobook.app.data.repository.auth.AuthRepositoryImpl import com.egobook.app.data.repository.QuestionRepositoryImpl -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import com.egobook.app.domain.repository.FriendsRepository import com.egobook.app.domain.repository.NotificationRepository import com.egobook.app.domain.repository.auth.AuthRepository @@ -43,9 +43,14 @@ abstract class RepositoryModule { @Singleton abstract fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository + // TODO: 백엔드 API 연동 시: + // 1. DiaryRepository 인터페이스 생성 + // 2. DiaryRepositoryImpl 구현 (ApiService 사용) + // 3. 이 바인딩을 DiaryRepositoryImpl -> DiaryRepository로 변경 + // 4. 모든 UseCase의 FakeDiaryRepository -> DiaryRepository로 변경 @Binds @Singleton - abstract fun bindDiaryRepository(impl: DiaryRepositoryImpl): DiaryRepository + abstract fun bindFakeDiaryRepository(impl: FakeDiaryRepositoryImpl): FakeDiaryRepository @Binds @Singleton diff --git a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt index 5149f347..0505567e 100644 --- a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt +++ b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt @@ -1,6 +1,6 @@ package com.egobook.app.di -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import com.egobook.app.domain.repository.auth.AuthRepository import com.egobook.app.domain.usecase.authusecase.AuthUseCases import com.egobook.app.domain.usecase.authusecase.GoogleAutoLogin @@ -27,7 +27,7 @@ object UseCaseModule { @Provides @Singleton - fun provideDiaryUseCases(repository: DiaryRepository): DiaryUseCases { + fun provideDiaryUseCases(repository: FakeDiaryRepository): DiaryUseCases { return DiaryUseCases( getDiaries = GetDiaries(repository), diff --git a/app/src/main/java/com/egobook/app/domain/repository/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt similarity index 60% rename from app/src/main/java/com/egobook/app/domain/repository/DiaryRepository.kt rename to app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt index d4481eef..3d3314f6 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt @@ -5,7 +5,19 @@ import com.egobook.app.domain.model.DiaryType import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime -interface DiaryRepository { +/** + * 테스트 및 개발용 일기 저장소 인터페이스 + * + * 백엔드 API 연동 전에 더미 데이터로 개발하기 위한 임시 인터페이스입니다. + * + * 실제 백엔드 API 연동 시: + * - 새로운 DiaryRepository 인터페이스를 생성 + * - DiaryRepositoryImpl에서 실제 API 호출 구현 + * - 모든 UseCase에서 FakeDiaryRepository → DiaryRepository로 변경 + * + * @see FakeDiaryRepositoryImpl + */ +interface FakeDiaryRepository { fun getDiaries(): Flow> diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt index 989e5d48..c0d98621 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt @@ -2,12 +2,12 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.Diary import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import java.time.LocalDateTime import javax.inject.Inject class AddDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke( content: String, diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DeleteDiary.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DeleteDiary.kt index c55f2bd5..35fbd827 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DeleteDiary.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DeleteDiary.kt @@ -1,10 +1,10 @@ package com.egobook.app.domain.usecase.diaryusecase -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import javax.inject.Inject class DeleteDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.deleteDiaryById(id) diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiaries.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiaries.kt index 48eb3806..52e350fb 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiaries.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiaries.kt @@ -2,14 +2,14 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.Diary import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import java.time.LocalDateTime import javax.inject.Inject class GetDiaries @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { operator fun invoke( selectedDate: LocalDateTime = LocalDateTime.now(), diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiary.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiary.kt index 23c99c57..f208db8b 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiary.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/GetDiary.kt @@ -1,11 +1,11 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import javax.inject.Inject class GetDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.getDiaryById(id) diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/UpdateDiary.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/UpdateDiary.kt index 5e286632..9baa97a1 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/UpdateDiary.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/UpdateDiary.kt @@ -2,11 +2,11 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.Diary import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import javax.inject.Inject class UpdateDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke( id: Long, From 2e98be89f183d708aaff9b8bba9b6d40c579ed58 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Fri, 6 Feb 2026 21:17:45 +0900 Subject: [PATCH 02/30] resolve conflict --- .../app/domain/usecase/diaryusecase/DiaryUseCases.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 1bb30afa..379b312e 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -2,7 +2,7 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.Diary import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.DiaryRepository +import com.egobook.app.domain.repository.FakeDiaryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import java.time.LocalDateTime @@ -20,7 +20,7 @@ data class DiaryUseCases @Inject constructor ( // 각 유스케이스들 정의 class GetDiaries @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { operator fun invoke( selectedDate: LocalDateTime = LocalDateTime.now(), @@ -43,7 +43,7 @@ class GetDiaries @Inject constructor( } class GetDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.getDiaryById(id) @@ -51,7 +51,7 @@ class GetDiary @Inject constructor( } class AddDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke( content: String, @@ -69,7 +69,7 @@ class AddDiary @Inject constructor( } class UpdateDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke( id: Long, @@ -87,7 +87,7 @@ class UpdateDiary @Inject constructor( } class DeleteDiary @Inject constructor( - private val repository: DiaryRepository + private val repository: FakeDiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.deleteDiaryById(id) From f2878ea20bb1d457bf52b6dc79f2c8058663d43c Mon Sep 17 00:00:00 2001 From: princehw03 Date: Fri, 6 Feb 2026 22:33:26 +0900 Subject: [PATCH 03/30] =?UTF-8?q?feat:=20util=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20ApiResponseExt=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/data/model/ApiResponse.kt | 24 +++++- .../model/diary/request/DiariesRequest.kt | 16 ++++ .../DiariesResponse.kt} | 9 +- .../DiaryResponse.kt} | 4 +- .../model/diary/response/EmptyResponse.kt | 6 ++ .../egobook/app/data/util/ApiResponseExt.kt | 82 +++++++++++++++++++ .../domain/usecase/diaryusecase/AddDiary.kt | 0 7 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt rename app/src/main/java/com/egobook/app/data/model/diary/{GetDiariesResponse.kt => response/DiariesResponse.kt} (69%) rename app/src/main/java/com/egobook/app/data/model/diary/{GetDiaryResponse.kt => response/DiaryResponse.kt} (73%) create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/response/EmptyResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/util/ApiResponseExt.kt delete mode 100644 app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt diff --git a/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt b/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt index 8bf7a78a..3c14b76c 100644 --- a/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt @@ -1,13 +1,35 @@ package com.egobook.app.data.model -data class ApiResponse( +import com.egobook.app.data.model.diary.response.EmptyResponse +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable +data class ApiResponse( + @SerialName("code") val code: String, + @SerialName("message") val message: String, + @SerialName("status") val status: Int, + @SerialName("data") val data: T +) + +@Serializable +data class ApiResponseEmpty( + @SerialName("code") + val code: String, + + @SerialName("message") + val message: String, + + @SerialName("status") + val status: Int, + @SerialName("result") + val result: EmptyResponse = EmptyResponse() ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt new file mode 100644 index 00000000..a381f4ac --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt @@ -0,0 +1,16 @@ +package com.egobook.app.data.model.diary.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DiariesRequest ( + @SerialName("date") + val date: String, + @SerialName("type") + val type: String, + @SerialName("page") + val page: Int, + @SerialName("size") + val size: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/GetDiariesResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt similarity index 69% rename from app/src/main/java/com/egobook/app/data/model/diary/GetDiariesResponse.kt rename to app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt index adf3514b..787ea542 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/GetDiariesResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt @@ -1,11 +1,15 @@ -package com.egobook.app.data.model.diary +package com.egobook.app.data.model.diary.response +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -data class GetDiariesResponse ( +@Serializable +data class DiariesResponse ( val dailyCount: Int, val diaries: DiarySliceResponse ) +@Serializable data class DiarySliceResponse( val content: List, val currentSlice: Long, @@ -13,6 +17,7 @@ data class DiarySliceResponse( val hasNext: Boolean ) +@Serializable data class DiaryItemResponse( val diaryId: Long, val date: String, diff --git a/app/src/main/java/com/egobook/app/data/model/diary/GetDiaryResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt similarity index 73% rename from app/src/main/java/com/egobook/app/data/model/diary/GetDiaryResponse.kt rename to app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt index 6c7dfb57..04fd9192 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/GetDiaryResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt @@ -1,6 +1,6 @@ -package com.egobook.app.data.model.diary +package com.egobook.app.data.model.diary.response -data class GetDiaryResponse ( +data class DiaryResponse ( val diaryId: Long, val date: String, diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/EmptyResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/EmptyResponse.kt new file mode 100644 index 00000000..cfb34dfd --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/EmptyResponse.kt @@ -0,0 +1,6 @@ +package com.egobook.app.data.model.diary.response + +import kotlinx.serialization.Serializable + +@Serializable +class EmptyResponse \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/util/ApiResponseExt.kt b/app/src/main/java/com/egobook/app/data/util/ApiResponseExt.kt new file mode 100644 index 00000000..28d47cef --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/util/ApiResponseExt.kt @@ -0,0 +1,82 @@ +package com.egobook.app.data.util + +import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.ApiResponseEmpty + + +/** + * ApiResponse를 Result로 변환하는 확장 함수 + */ +inline fun ApiResponse.toResult( + transform: (T) -> R +): Result { + return if (this.code == "SUCCESS") { + Result.success(transform(this.data)) + } else { + Result.failure(Exception(this.message)) + } +} + +/** + * ApiResponse를 Result로 변환 (변환 없이) + */ +fun ApiResponse.toResult(): Result { + return if (this.code == "SUCCESS") { + Result.success(this.data) + } else { + Result.failure(Exception(this.message)) + } +} + +/** + * API 호출을 안전하게 실행하는 헬퍼 함수 + */ +suspend fun safeApiCall( + apiCall: suspend () -> ApiResponse +): Result { + return try { + apiCall().toResult() + } catch (e: Exception) { + Result.failure(e) + } +} + + +/** + * API 호출을 안전하게 실행하고 변환하는 헬퍼 함수 + */ +suspend inline fun safeApiCall( + crossinline apiCall: suspend () -> ApiResponse, + crossinline transform: (T) -> R +): Result { + return try { + apiCall().toResult(transform) + } catch (e: Exception) { + Result.failure(e) + } +} + +/** + * 의미있는 데이터를 반환하지 않는 API 응답을 처리하는 전용 확장 함수 + */ + +fun ApiResponseEmpty.toResult(): Result { + return if (this.code == "SUCCESS") { + Result.success(Unit) + } else { + Result.failure(Exception(this.message)) + } +} + +/** + * 빈 응답 API 호출을 안전하게 실행하는 헬퍼 함수 + */ +suspend fun safeApiCallEmpty( + apiCall: suspend () -> ApiResponseEmpty +): Result { + return try { + apiCall().toResult() + } catch (e: Exception) { + Result.failure(e) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/AddDiary.kt deleted file mode 100644 index e69de29b..00000000 From 7c5878592bc26fc1ba4fa370fdbc4971ca86486b Mon Sep 17 00:00:00 2001 From: princehw03 Date: Fri, 6 Feb 2026 23:03:56 +0900 Subject: [PATCH 04/30] =?UTF-8?q?feat:=20request=EB=93=A4=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 --- .../egobook/app/data/api/DiaryApiService.kt | 5 ++++ .../egobook/app/data/local/UUIDProvider.kt | 11 -------- .../data/model/diary/request/DiaryRequest.kt | 22 ++++++++++++++++ .../model/diary/response/DiariesResponse.kt | 26 ++++++++++++++----- 4 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt delete mode 100644 app/src/main/java/com/egobook/app/data/local/UUIDProvider.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt new file mode 100644 index 00000000..4c6727e6 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -0,0 +1,5 @@ +package com.egobook.app.data.api + +interface DiaryApiService { + +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/local/UUIDProvider.kt b/app/src/main/java/com/egobook/app/data/local/UUIDProvider.kt deleted file mode 100644 index 5f751c52..00000000 --- a/app/src/main/java/com/egobook/app/data/local/UUIDProvider.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.egobook.app.data.local - -import java.util.UUID - -object UUIDProvider { - - fun generateUUID(): String { - return UUID.randomUUID().toString() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt new file mode 100644 index 00000000..e25411d9 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt @@ -0,0 +1,22 @@ +package com.egobook.app.data.model.diary.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DiaryRequest( + @SerialName("diaryId") + val diaryId: Long, +) + +@Serializable +data class DiaryCreateRequest( + @SerialName("type") + val type: List, + @SerialName("emotionLevel") + val emotionLevel: Int?, + @SerialName("content") + val content: String, + @SerialName("dateTime") + val dateTime: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt index 787ea542..88357b3f 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt @@ -5,26 +5,40 @@ import kotlinx.serialization.Serializable @Serializable data class DiariesResponse ( + @SerialName("dailyCount") val dailyCount: Int, - val diaries: DiarySliceResponse + @SerialName("diaries") + val diaries: DiarySlice ) @Serializable -data class DiarySliceResponse( - val content: List, - val currentSlice: Long, +data class DiarySlice( + @SerialName("content") + val content: List, + @SerialName("page") + val page: Long, + @SerialName("size") val size: Long, + @SerialName("hasNext") val hasNext: Boolean ) @Serializable -data class DiaryItemResponse( +data class DiaryItem( + @SerialName("diaryId") val diaryId: Long, + @SerialName("date") val date: String, + @SerialName("writtenAt") val writtenAt: String, + @SerialName("type") val type: List, - val emotionLevel: Long?, + @SerialName("emotionLevel") + val emotionLevel: Int?, + @SerialName("content") val content: String, + @SerialName("createdAt") val createdAt: String, + @SerialName("updatedAt") val updatedAt: String ) \ No newline at end of file From 14bcf03e24722478a539e2fc096a1cbecf326e57 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sat, 7 Feb 2026 00:02:06 +0900 Subject: [PATCH 05/30] =?UTF-8?q?feat:=20response=EB=93=A4=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 --- .../egobook/app/data/api/AuthApiService.kt | 1 - .../com/egobook/app/data/model/ApiResponse.kt | 4 +-- .../model/diary/response/DiariesResponse.kt | 30 ++++------------- .../diary/response/DiaryCreateResponse.kt | 22 +++++++++++++ .../diary/response/DiaryDeleteResponse.kt | 10 ++++++ .../diary/response/DiaryEntryResponse.kt | 33 +++++++++++++++++++ .../model/diary/response/DiaryResponse.kt | 20 ----------- 7 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/response/DiaryCreateResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/response/DiaryDeleteResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/response/DiaryEntryResponse.kt delete mode 100644 app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt diff --git a/app/src/main/java/com/egobook/app/data/api/AuthApiService.kt b/app/src/main/java/com/egobook/app/data/api/AuthApiService.kt index dda87f42..538f9c0e 100644 --- a/app/src/main/java/com/egobook/app/data/api/AuthApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/AuthApiService.kt @@ -46,5 +46,4 @@ interface AuthApiService { @Body request: TokensRequestAgainByGuest ): Response - } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt b/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt index 3c14b76c..35df8ce0 100644 --- a/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/ApiResponse.kt @@ -30,6 +30,6 @@ data class ApiResponseEmpty( @SerialName("status") val status: Int, - @SerialName("result") - val result: EmptyResponse = EmptyResponse() + @SerialName("data") + val data: EmptyResponse = EmptyResponse() ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt index 88357b3f..56b478e3 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiariesResponse.kt @@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable data class DiariesResponse ( @SerialName("dailyCount") val dailyCount: Int, + @SerialName("diaries") val diaries: DiarySlice ) @@ -14,31 +15,14 @@ data class DiariesResponse ( @Serializable data class DiarySlice( @SerialName("content") - val content: List, + val content: List, + @SerialName("page") - val page: Long, + val page: Int, + @SerialName("size") - val size: Long, + val size: Int, + @SerialName("hasNext") val hasNext: Boolean -) - -@Serializable -data class DiaryItem( - @SerialName("diaryId") - val diaryId: Long, - @SerialName("date") - val date: String, - @SerialName("writtenAt") - val writtenAt: String, - @SerialName("type") - val type: List, - @SerialName("emotionLevel") - val emotionLevel: Int?, - @SerialName("content") - val content: String, - @SerialName("createdAt") - val createdAt: String, - @SerialName("updatedAt") - val updatedAt: String ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryCreateResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryCreateResponse.kt new file mode 100644 index 00000000..9c4b73b0 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryCreateResponse.kt @@ -0,0 +1,22 @@ +package com.egobook.app.data.model.diary.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +//일기 생성 +@Serializable +data class DiaryCreateResponse( + @SerialName("entry") + val entry: DiaryEntryResponse, + @SerialName("rewards") + val rewards: List +) +@Serializable +data class Reward( + @SerialName("rewardType") + val rewardType: String, + @SerialName("amount") + val amount: Int, + @SerialName("message") + val message: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryDeleteResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryDeleteResponse.kt new file mode 100644 index 00000000..c4be5b7a --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryDeleteResponse.kt @@ -0,0 +1,10 @@ +package com.egobook.app.data.model.diary.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DiaryDeleteResponse ( + @SerialName("deleted") + val deleted: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryEntryResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryEntryResponse.kt new file mode 100644 index 00000000..312e7ca5 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryEntryResponse.kt @@ -0,0 +1,33 @@ +package com.egobook.app.data.model.diary.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +//일기 상세 확인, 일기 수정 이 직접 사용, 일기 생성에서 부분 사용 +@Serializable +data class DiaryEntryResponse ( + @SerialName("diaryId") + val diaryId: Long, + + @SerialName("date") + val date: String, + + @SerialName("writtenAt") + val writtenAt: String, + + @SerialName("type") + val type: List, + + @SerialName("emotionLevel") + val emotionLevel: Int?, + + @SerialName("content") + val content: String, + + @SerialName("createdAt") + val createdAt: String, + + @SerialName("updatedAt") + val updatedAt: String + +) diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt deleted file mode 100644 index 04fd9192..00000000 --- a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryResponse.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.egobook.app.data.model.diary.response - -data class DiaryResponse ( - val diaryId: Long, - - val date: String, - - val writtenAt: String, - - val types: List, - - val emotionLevel: Long?, - - val content: String, - - val createdAt: String, - - val updatedAt: String - -) \ No newline at end of file From cecb355dd12c82e4aba5b1e54f9e41b228d96eb3 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sat, 7 Feb 2026 01:22:12 +0900 Subject: [PATCH 06/30] =?UTF-8?q?fix:=20=EA=B0=91=EC=9E=90=EA=B8=B0=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=97=90=20=EC=A0=84=EB=B6=80=20=EB=B9=A8?= =?UTF-8?q?=EA=B0=84=EC=83=89=EC=A4=84=EC=9D=B4=20=EB=9C=A8=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/data/api/DiaryApiService.kt | 15 +++++++++++++++ .../data/model/diary/request/DiariesRequest.kt | 16 ---------------- 2 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt index 4c6727e6..748acb72 100644 --- a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -1,5 +1,20 @@ package com.egobook.app.data.api +import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.diary.response.DiariesResponse +import retrofit2.http.GET +import retrofit2.http.Query + interface DiaryApiService { + //일기 목록 불러오기 + @GET("/diaries") + suspend fun getDiaries( + @Query("date") date: String, + @Query("type") type: String, + @Query("page") page: Int = 1, + @Query("size") size: Int = 10 + ): ApiResponse + + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt deleted file mode 100644 index a381f4ac..00000000 --- a/app/src/main/java/com/egobook/app/data/model/diary/request/DiariesRequest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.egobook.app.data.model.diary.request - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class DiariesRequest ( - @SerialName("date") - val date: String, - @SerialName("type") - val type: String, - @SerialName("page") - val page: Int, - @SerialName("size") - val size: Int -) \ No newline at end of file From e0d2f55ef505ad8d26544f9d5354df1b052774e4 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sat, 7 Feb 2026 16:22:18 +0900 Subject: [PATCH 07/30] =?UTF-8?q?feat:=20DiaryApiService=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 --- .../egobook/app/data/api/DiaryApiService.kt | 35 +++++++++++++++++++ .../paging/MyRepliesHistoryPagingSource.kt | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt index 748acb72..b5458c7a 100644 --- a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -1,8 +1,17 @@ package com.egobook.app.data.api import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.diary.request.DiaryCreateRequest import com.egobook.app.data.model.diary.response.DiariesResponse +import com.egobook.app.data.model.diary.response.DiaryCreateResponse +import com.egobook.app.data.model.diary.response.DiaryDeleteResponse +import com.egobook.app.data.model.diary.response.DiaryEntryResponse +import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.POST +import retrofit2.http.Path import retrofit2.http.Query interface DiaryApiService { @@ -16,5 +25,31 @@ interface DiaryApiService { @Query("size") size: Int = 10 ): ApiResponse + //일기 상세 확인 + @GET("/diaries/{diaryId}") + suspend fun getDiary( + @Path("diaryId") diaryId: Long + ): ApiResponse + + //일기 추가 + @POST("/diaries") + suspend fun addDiary( + @Body request: DiaryCreateRequest + ): ApiResponse + + //일기 삭제 + @DELETE("/diaries/{diaryId}") + suspend fun deleteDiary( + @Path("diaryId") diaryId: Long + ): ApiResponse + + //일기 수정 + @PATCH("/diaries/{diaryId}") + suspend fun updateDiary( + @Path("diaryId") diaryId: Long, + @Body request: DiaryCreateRequest + ): ApiResponse + + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/paging/MyRepliesHistoryPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/MyRepliesHistoryPagingSource.kt index e57df355..a2932de4 100644 --- a/app/src/main/java/com/egobook/app/data/repository/paging/MyRepliesHistoryPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/paging/MyRepliesHistoryPagingSource.kt @@ -18,12 +18,12 @@ import kotlinx.coroutines.delay class MyRepliesHistoryPagingSource(private val apiService: QuestionApiService): PagingSource() { override fun getRefreshKey(state: PagingState): Int { - return 0 // 1 + return 1 // 1 } override suspend fun load(params: LoadParams): LoadResult { return try { - val page = params.key ?: 0 // 2 + val page = params.key ?: 1 // 2 val size = params.loadSize val result = apiService.fetchMyRepliesHistory(page = page, size = size).data From b1b6105e4228086befa178f85dd6fb8a97b70057 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 16:08:44 +0900 Subject: [PATCH 08/30] =?UTF-8?q?refactor:=20mapper=20=EC=8B=A0=EC=84=A4?= =?UTF-8?q?=20=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=EA=B3=BC=20Fragment?= =?UTF-8?q?=EA=B0=84=EC=9D=98=20=EA=B4=80=EC=8B=AC=EC=82=AC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{DiaryRequest.kt => DiaryCreateRequest.kt} | 6 -- .../repository/diary/DiaryRepositoryImpl.kt | 4 + .../{ => diary}/FakeDiaryRepositoryImpl.kt | 27 ++++--- .../diary/paging/DiariesPagingSource.kt | 4 + .../com/egobook/app/di/RepositoryModule.kt | 4 +- .../java/com/egobook/app/di/UseCaseModule.kt | 2 +- .../com/egobook/app/domain/mapper/gitkeep | 1 - .../com/egobook/app/domain/model/Diary.kt | 46 ----------- .../app/domain/model/diary/entity/Diary.kt | 72 +++++++++++++++++ .../domain/model/diary/entity/DiaryFilter.kt | 8 ++ .../domain/model/diary/mapper/DiaryMapper.kt | 77 +++++++++++++++++++ .../repository/diary/DiaryRepository.kt | 55 +++++++++++++ .../{ => diary}/FakeDiaryRepository.kt | 12 +-- .../usecase/diaryusecase/DiaryUseCases.kt | 14 ++-- .../app/ui/diary/adapter/DiaryRVAdapter.kt | 6 +- .../{DiaryMapper.kt => DiaryEntityMapper.kt} | 35 +++++---- .../com/egobook/app/ui/diary/mapper/gitkeep | 1 - .../app/ui/diary/view/DiaryCheckFragment.kt | 12 +-- .../app/ui/diary/view/DiaryFragment.kt | 41 ++++++---- .../app/ui/diary/view/DiaryListFragment.kt | 12 +-- .../app/ui/diary/view/DiaryWriteFragment.kt | 8 +- .../ui/diary/viewmodel/DiariesViewModel.kt | 67 ++++++++++++---- .../ui/diary/viewmodel/DiaryCheckViewModel.kt | 2 +- .../ui/diary/viewmodel/DiaryWriteViewModel.kt | 4 +- .../ui/{diary => }/util/DateTimeExtensions.kt | 3 +- 25 files changed, 370 insertions(+), 153 deletions(-) rename app/src/main/java/com/egobook/app/data/model/diary/request/{DiaryRequest.kt => DiaryCreateRequest.kt} (81%) create mode 100644 app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt rename app/src/main/java/com/egobook/app/data/repository/{ => diary}/FakeDiaryRepositoryImpl.kt (95%) create mode 100644 app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt delete mode 100644 app/src/main/java/com/egobook/app/domain/mapper/gitkeep delete mode 100644 app/src/main/java/com/egobook/app/domain/model/Diary.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/diary/entity/Diary.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/diary/entity/DiaryFilter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt create mode 100644 app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt rename app/src/main/java/com/egobook/app/domain/repository/{ => diary}/FakeDiaryRepository.kt (86%) rename app/src/main/java/com/egobook/app/ui/diary/mapper/{DiaryMapper.kt => DiaryEntityMapper.kt} (74%) delete mode 100644 app/src/main/java/com/egobook/app/ui/diary/mapper/gitkeep rename app/src/main/java/com/egobook/app/ui/{diary => }/util/DateTimeExtensions.kt (97%) diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt similarity index 81% rename from app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt rename to app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt index e25411d9..ad4f1286 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryRequest.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt @@ -3,12 +3,6 @@ package com.egobook.app.data.model.diary.request import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@Serializable -data class DiaryRequest( - @SerialName("diaryId") - val diaryId: Long, -) - @Serializable data class DiaryCreateRequest( @SerialName("type") diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt new file mode 100644 index 00000000..842cae23 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -0,0 +1,4 @@ +package com.egobook.app.data.repository.diary + +class DiaryRepositoryImpl { +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt similarity index 95% rename from app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt rename to app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt index b992bb4c..df6f17f0 100644 --- a/app/src/main/java/com/egobook/app/data/repository/FakeDiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt @@ -1,8 +1,8 @@ -package com.egobook.app.data.repository +package com.egobook.app.data.repository.diary -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.FakeDiaryRepository +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryType +import com.egobook.app.domain.repository.diary.FakeDiaryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -12,13 +12,13 @@ import javax.inject.Inject /** * 테스트 및 개발용 더미 일기 저장소 - * + * * 메모리 내에서 더미 데이터를 관리하며, 실제 API 호출 없이 일기 기능을 테스트할 수 있습니다. - * + * * 실제 백엔드 API 연동 시: * 1. 새로운 DiaryRepositoryImpl 클래스를 생성하여 ApiService를 사용하여 구현 * 2. RepositoryModule.kt에서 FakeDiaryRepositoryImpl -> DiaryRepositoryImpl로 변경 - * + * * @see FakeDiaryRepository */ class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { @@ -170,7 +170,8 @@ class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { content = "어제 내가 해낸 작은 성과에 대해 스스로를 칭찬한다.", types = setOf(DiaryType.PRAISE), createdAt = LocalDateTime.now().minusDays(1).withHour(21).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(1).withHour(22).withMinute(30), // 1시간 30분 뒤 수정 + writtenAt = LocalDateTime.now().minusDays(1).withHour(22) + .withMinute(30), // 1시간 30분 뒤 수정 emotionLevel = null ), @@ -180,7 +181,8 @@ class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { content = "이틀 전, 진로에 대해 계속 고민만 하다 하루가 갔다.", types = setOf(DiaryType.WORRY), createdAt = LocalDateTime.now().minusDays(2).withHour(22).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(2).withHour(22).withMinute(10), // 10분 후 수정 + writtenAt = LocalDateTime.now().minusDays(2).withHour(22) + .withMinute(10), // 10분 후 수정 emotionLevel = null ), @@ -216,7 +218,12 @@ class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { Diary( id = 23L, content = "모든 타입이 포함된 종합 일기. 정말 많은 일이 있었다.", - types = setOf(DiaryType.EMOTION, DiaryType.WORRY, DiaryType.PRAISE, DiaryType.THANKS), + types = setOf( + DiaryType.EMOTION, + DiaryType.WORRY, + DiaryType.PRAISE, + DiaryType.THANKS + ), createdAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), writtenAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), emotionLevel = 3 // NORMAL diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt new file mode 100644 index 00000000..c37b13d8 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt @@ -0,0 +1,4 @@ +package com.egobook.app.data.repository.diary.paging + +class DiariesPagingSource { +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt index 39dcac59..6d15d66a 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt @@ -1,13 +1,13 @@ package com.egobook.app.di import com.egobook.app.data.repository.CounselingRepositoryImpl -import com.egobook.app.data.repository.FakeDiaryRepositoryImpl +import com.egobook.app.data.repository.diary.FakeDiaryRepositoryImpl import com.egobook.app.data.repository.FriendsRepositoryImpl import com.egobook.app.data.repository.NotificationRepositoryImpl import com.egobook.app.domain.repository.CounselingRepository import com.egobook.app.data.repository.auth.AuthRepositoryImpl import com.egobook.app.data.repository.QuestionRepositoryImpl -import com.egobook.app.domain.repository.FakeDiaryRepository +import com.egobook.app.domain.repository.diary.FakeDiaryRepository import com.egobook.app.domain.repository.FriendsRepository import com.egobook.app.domain.repository.NotificationRepository import com.egobook.app.domain.repository.auth.AuthRepository diff --git a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt index 0505567e..c6db9201 100644 --- a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt +++ b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt @@ -1,6 +1,6 @@ package com.egobook.app.di -import com.egobook.app.domain.repository.FakeDiaryRepository +import com.egobook.app.domain.repository.diary.FakeDiaryRepository import com.egobook.app.domain.repository.auth.AuthRepository import com.egobook.app.domain.usecase.authusecase.AuthUseCases import com.egobook.app.domain.usecase.authusecase.GoogleAutoLogin diff --git a/app/src/main/java/com/egobook/app/domain/mapper/gitkeep b/app/src/main/java/com/egobook/app/domain/mapper/gitkeep deleted file mode 100644 index 1689899e..00000000 --- a/app/src/main/java/com/egobook/app/domain/mapper/gitkeep +++ /dev/null @@ -1 +0,0 @@ -sss \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/Diary.kt b/app/src/main/java/com/egobook/app/domain/model/Diary.kt deleted file mode 100644 index ee78a187..00000000 --- a/app/src/main/java/com/egobook/app/domain/model/Diary.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.egobook.app.domain.model - - -import java.time.LocalDateTime - - -data class Diary( - val id: Long, //일기 id - val content: String, //내용 - val types: Set, //중복 방지 - val createdAt: LocalDateTime, //최초 생성 시각 - val writtenAt: LocalDateTime, //마지막 수정 시각 - val emotionLevel: Int? //감정 레벨 (1: 매우 나쁨, 2: 나쁨, 3: 보통, 4: 좋음, 5: 매우 좋음). null일 수도 있음 -) { - init { - //일기 타입에 감정이 포함되어 있어야만 기분 선택 가능 -> 도메인 규칙으로 정의 - if (DiaryType.EMOTION !in types && emotionLevel != null) { - throw IllegalStateException( - "emotionLevel은 EMOTION 타입이 포함된 경우에만 설정할 수 있습니다." - ) - } - - // emotionLevel이 null이 아닌 경우 1~5 범위 체크 - if (emotionLevel != null && emotionLevel !in 1..5) { - throw IllegalArgumentException( - "emotionLevel은 1~5 사이의 값이어야 합니다. 현재 값: $emotionLevel" - ) - } - } -} - -enum class DiaryType(val value: String, val displayType: String) { - EMOTION("EMOTION", "감정"), - WORRY("WORRY", "고민"), - PRAISE("PRAISE", "칭찬"), - THANKS("THANKS", "감사"); - - - companion object { - fun fromDisplayType(displayType: String): DiaryType { - return entries.find { it.displayType == displayType } - ?: throw IllegalArgumentException("Unknown display type: $displayType") - } - - } -} diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/entity/Diary.kt b/app/src/main/java/com/egobook/app/domain/model/diary/entity/Diary.kt new file mode 100644 index 00000000..27c38778 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/diary/entity/Diary.kt @@ -0,0 +1,72 @@ +package com.egobook.app.domain.model.diary.entity + + +import java.time.LocalDate +import java.time.LocalDateTime + + +data class DayDiaries( + val dailyCount: Int, + val diaries: DiaryList +) + +/** + * 일기 페이징 사용 용도의 도메인 엔티티 + */ +data class DiaryList( + val content: List, + val page: Int, + val size: Int, + val hasNext: Boolean +) +/** + * 일기 간단 정보 (목록용) + */ +data class DiarySummary( + val diaryId: Long, + val writtenAt: LocalDateTime, + val types: Set, + val emotionLevel: Int?, + val content: String, +) + +/** + * 일기 상세 정보 도메인 엔티티 + */ +data class Diary( + val diaryId: Long, //일기 id + val date: LocalDate, //종속 날짜 + val writtenAt: LocalDateTime, //마지막 수정 시각 + val types: Set, //중복 방지 + val emotionLevel: Int?, //감정 레벨 (1: 매우 나쁨, 2: 나쁨, 3: 보통, 4: 좋음, 5: 매우 좋음). null일 수도 있음 + val content: String, //내용 + val createdAt: LocalDateTime, //최초 생성 시각 +) { + +} + + +enum class DiaryType(val value: String, val displayType: String) { + EMOTION("EMOTION", "감정"), + CONCERN("CONCERN", "고민"), + PRAISE("PRAISE", "칭찬"), + GRATITUDE("GRATITUDE", "감사"); + companion object { + /** + * API value로 일기 타입 찾기 (예: "EMOTION", "CONCERN") + */ + fun from(value: String): DiaryType { + return entries.find { it.value == value } + ?: throw IllegalArgumentException("Unknown diary type: $value") + } + + /** + * 한글 displayType으로 DiaryType 찾기 (예: "감정", "고민") + */ + fun fromDisplayType(displayType: String): DiaryType { + return entries.find { it.displayType == displayType } + ?: throw IllegalArgumentException("Unknown display type: $displayType") + } + + } +} diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/entity/DiaryFilter.kt b/app/src/main/java/com/egobook/app/domain/model/diary/entity/DiaryFilter.kt new file mode 100644 index 00000000..da3bf36d --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/diary/entity/DiaryFilter.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.model.diary.entity + +import java.time.LocalDate + +data class DiaryFilter( + val date: LocalDate, + val types: Set? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt new file mode 100644 index 00000000..88f9e4c6 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -0,0 +1,77 @@ +package com.egobook.app.domain.model.diary.mapper + +import com.egobook.app.data.model.diary.response.DiariesResponse +import com.egobook.app.data.model.diary.response.DiaryEntryResponse +import com.egobook.app.data.model.diary.response.DiarySlice +import com.egobook.app.domain.model.diary.entity.DayDiaries +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryList +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.entity.DiaryType +import java.time.LocalDate +import java.time.LocalDateTime + + +/** + * Data Layer ↔ Domain Layer 변환 Mapper + */ +object DiaryMapper { + + // ========== Response → Domain Entity ========== + + /** + * DiariesResponse → DayDiaries + */ + fun DiariesResponse.toEntity(): DayDiaries { + return DayDiaries( + dailyCount = dailyCount, + diaries = diaries.toEntity() + ) + } + + /** + * DiaryEntryResponse → Diary + */ + fun DiaryEntryResponse.toEntity(): Diary { + return Diary( + diaryId = diaryId, + date = LocalDate.parse(date), + writtenAt = LocalDateTime.parse(writtenAt), + types = type.map { DiaryType.from(it) }.toSet(), + emotionLevel = emotionLevel, + content = content, + createdAt = LocalDateTime.parse(createdAt) + ) + } + + /** + * DiarySlice → DiaryList + */ + fun DiarySlice.toEntity(): DiaryList { + return DiaryList( + content = content.map { it.toEntity().toDiarySummary() }, + page = page, + size = size, + hasNext = hasNext + ) + } + + // ========== Domain Entity → Domain Entity ========== + + /** + * Diary → DiarySummary (도메인 엔티티 간 변환) + */ + fun Diary.toDiarySummary(): DiarySummary { + return DiarySummary( + diaryId = diaryId, + writtenAt = writtenAt, + types = types, + emotionLevel = emotionLevel, + content = content + ) + } + + // ========== Domain Entity -> Request ========== + + +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt new file mode 100644 index 00000000..51b5f188 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -0,0 +1,55 @@ +package com.egobook.app.domain.repository.diary + +import androidx.paging.PagingData +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.entity.DiaryType +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate + +interface DiaryRepository { + + /** + * 일기 목록 가져오기 (페이징) + * @param date 날짜 필터 (예: "2026-02-07") + * @param type 타입 필터 (예: "EMOTION") + * @param size 페이지 크기 + */ + fun getDiaries( + date: LocalDate, + type: DiaryType, + size: Int = 10 + ): Flow> + + /** + * 일기 상세 조회 + * @param diaryId 일기 ID + */ + suspend fun getDiary(diaryId: Long): Result + + /** + * 일기 생성 + */ + suspend fun addDiary( + date: String, + types: List, + emotionLevel: Int?, + content: String + ): Result // diaryId 반환 + + /** + * 일기 수정 + */ + suspend fun updateDiary( + diaryId: Long, + date: String, + types: List, + emotionLevel: Int?, + content: String + ): Result + + /** + * 일기 삭제 + */ + suspend fun deleteDiary(diaryId: Long): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/FakeDiaryRepository.kt similarity index 86% rename from app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt rename to app/src/main/java/com/egobook/app/domain/repository/diary/FakeDiaryRepository.kt index 3d3314f6..e44eb477 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/FakeDiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/FakeDiaryRepository.kt @@ -1,20 +1,20 @@ -package com.egobook.app.domain.repository +package com.egobook.app.domain.repository.diary -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryType import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime /** * 테스트 및 개발용 일기 저장소 인터페이스 - * + * * 백엔드 API 연동 전에 더미 데이터로 개발하기 위한 임시 인터페이스입니다. - * + * * 실제 백엔드 API 연동 시: * - 새로운 DiaryRepository 인터페이스를 생성 * - DiaryRepositoryImpl에서 실제 API 호출 구현 * - 모든 UseCase에서 FakeDiaryRepository → DiaryRepository로 변경 - * + * * @see FakeDiaryRepositoryImpl */ interface FakeDiaryRepository { diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 379b312e..17616d88 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -1,14 +1,16 @@ package com.egobook.app.domain.usecase.diaryusecase -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType -import com.egobook.app.domain.repository.FakeDiaryRepository +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.entity.DiaryType +import com.egobook.app.domain.repository.diary.FakeDiaryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject -// 의존성 주입을 쉽게 하기 위한 래퍼 클래스. +// 의존성 주입을 쉽게 하기 위한 래퍼 클래스 data class DiaryUseCases @Inject constructor ( val getDiaries: GetDiaries, val getDiary: GetDiary, @@ -23,9 +25,9 @@ class GetDiaries @Inject constructor( private val repository: FakeDiaryRepository ) { operator fun invoke( - selectedDate: LocalDateTime = LocalDateTime.now(), + selectedDate: LocalDate = LocalDate.now(), types: Set? = null // null = 전체 탭 - ): Flow> { + ): Flow> { return repository.getDiaries() .map { diaries -> diaries diff --git a/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt b/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt index c1cc96a8..22ffbca5 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt @@ -10,9 +10,9 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.egobook.app.R import com.egobook.app.databinding.ItemDiaryBinding -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType -import com.egobook.app.ui.diary.util.toTimeString +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryType +import com.egobook.app.ui.util.toTimeString class DiaryRVAdapter : ListAdapter(DiaryDiffCallback()) { diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt similarity index 74% rename from app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryMapper.kt rename to app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index 1e662165..b28f97b8 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -1,20 +1,21 @@ package com.egobook.app.ui.diary.mapper -import com.egobook.app.domain.model.DiaryType +import com.egobook.app.domain.model.diary.entity.DiaryType +import java.time.LocalDate /** * Domain 모델과 UI 레이어 간의 데이터 변환을 담당하는 매퍼 * 순수하게 데이터 변환만 담당하며, UI 리소스(이미지, 색상 등)는 UI 레이어에서 처리 */ -object DiaryMapper { +object DiaryEntityMapper { + + // ========== Domain Entity -> UI ========== - // ========== DiaryType 변환 ========== - /** - * UI displayType("감정", "고민", "칭찬", "감사") -> Domain DiaryType + * Domain DiaryType Set -> UI displayTypes Set */ - fun uiDisplayTypeToDomain(displayType: String): DiaryType { - return DiaryType.fromDisplayType(displayType) + fun domainToUiDisplayTypes(types: Set): Set { + return types.map { it.displayType }.toSet() } /** @@ -23,6 +24,8 @@ object DiaryMapper { fun domainToUiDisplayType(diaryType: DiaryType): String { return diaryType.displayType } + + // ========== UI -> Domain Entity ========== /** * UI displayTypes Set -> Domain DiaryType Set @@ -36,15 +39,21 @@ object DiaryMapper { } }.toSet() } - + /** - * Domain DiaryType Set -> UI displayTypes Set + * UI displayType("감정", "고민", "칭찬", "감사") -> Domain DiaryType */ - fun domainToUiDisplayTypes(types: Set): Set { - return types.map { it.displayType }.toSet() + fun uiDisplayTypeToDomain(displayType: String): DiaryType { + return DiaryType.fromDisplayType(displayType) + } + + /** + * UI 년원일 -> Domain Entity LocalDate + */ + fun uiYearMonthDateToDomain(year: Int, month: Int, date: Int): LocalDate { + return LocalDate.of(year, month, date) } // ========== EmotionLevel 변환 ========== - // emotionLevel이 Int로 변경되어 별도 변환 불필요 - // Domain과 UI 모두 Int (1~5)를 사용 + // Domain과 UI 모두 Int (1~5)를 사용 -> 변환 불필요 } diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/gitkeep b/app/src/main/java/com/egobook/app/ui/diary/mapper/gitkeep deleted file mode 100644 index 1689899e..00000000 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/gitkeep +++ /dev/null @@ -1 +0,0 @@ -sss \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt index 65391626..fd0f16ca 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt @@ -16,12 +16,12 @@ import com.egobook.app.BlurLevel import com.egobook.app.R import com.egobook.app.applyScreenBlur import com.egobook.app.databinding.FragmentDiaryCheckBinding -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType -import com.egobook.app.ui.diary.util.toDateTimeString -import com.egobook.app.ui.diary.util.toDayOfMonthString -import com.egobook.app.ui.diary.util.toMonthString -import com.egobook.app.ui.diary.util.toYearString +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryType +import com.egobook.app.ui.util.toDateTimeString +import com.egobook.app.ui.util.toDayOfMonthString +import com.egobook.app.ui.util.toMonthString +import com.egobook.app.ui.util.toYearString import com.egobook.app.ui.diary.viewmodel.DiaryCheckViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index abec3f2c..aa71ece3 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -15,11 +15,7 @@ import com.egobook.app.R import com.egobook.app.applyScreenBlur import com.egobook.app.databinding.FragmentDiaryBinding - import com.egobook.app.domain.model.DiaryType import com.egobook.app.ui.diary.adapter.DiaryVPAdapter - import com.egobook.app.ui.diary.util.toDayOfMonthString - import com.egobook.app.ui.diary.util.toMonthString - import com.egobook.app.ui.diary.util.toYearString import com.egobook.app.ui.diary.viewmodel.DiariesEvent import com.egobook.app.ui.diary.viewmodel.DiariesViewModel import com.google.android.material.tabs.TabLayout @@ -79,12 +75,24 @@ } btnPrevDate.setOnClickListener { val prevDate = viewModel.state.value.selectedDate.minusDays(1) - viewModel.onEvent(DiariesEvent.ChangeDate(prevDate)) + viewModel.onEvent( + DiariesEvent.ChangeDate( + year = prevDate.year, + month = prevDate.monthValue, + day = prevDate.dayOfMonth + ) + ) binding.vpDiary.setCurrentItem(0, false) // "전체" 탭으로 이동 } btnNextDate.setOnClickListener { val nextDate = viewModel.state.value.selectedDate.plusDays(1) - viewModel.onEvent(DiariesEvent.ChangeDate(nextDate)) + viewModel.onEvent( + DiariesEvent.ChangeDate( + year = nextDate.year, + month = nextDate.monthValue, + day = nextDate.dayOfMonth + ) + ) binding.vpDiary.setCurrentItem(0, false) // "전체" 탭으로 이동 } btnGoToTop.setOnClickListener { @@ -97,10 +105,9 @@ viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collectLatest { state -> - binding.tvYear.text = state.selectedDate.toYearString() - binding.tvMonth.text = state.selectedDate.toMonthString() - binding.tvDate.text = state.selectedDate.toDayOfMonthString() - + binding.tvYear.text = state.yearText + binding.tvMonth.text = state.monthText + binding.tvDate.text = state.dayText } } } @@ -119,8 +126,8 @@ // 탭 선택 이벤트 처리 binding.tbType.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { - val types = getDiaryTypesByPosition(tab.position) - viewModel.onEvent(DiariesEvent.SwipeTab(types)) + val displayTypes = getDisplayTypesByPosition(tab.position) + viewModel.onEvent(DiariesEvent.SwipeTab(displayTypes)) } override fun onTabUnselected(tab: TabLayout.Tab) {} @@ -153,13 +160,13 @@ }) } - private fun getDiaryTypesByPosition(position: Int): Set? { + private fun getDisplayTypesByPosition(position: Int): Set? { return when(position) { 0 -> null // 전체 - 1 -> setOf(DiaryType.EMOTION) - 2 -> setOf(DiaryType.WORRY) - 3 -> setOf(DiaryType.PRAISE) - 4 -> setOf(DiaryType.THANKS) + 1 -> setOf("감정") + 2 -> setOf("고민") + 3 -> setOf("칭찬") + 4 -> setOf("감사") else -> null } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt index f01cef3a..89991fd7 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt @@ -5,8 +5,6 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.result.launch -import androidx.core.os.bundleOf import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -14,17 +12,11 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.egobook.app.R import com.egobook.app.databinding.FragmentDiaryListBinding -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType +import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.ui.diary.adapter.DiaryRVAdapter import com.egobook.app.ui.diary.viewmodel.DiariesViewModel import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class DiaryListFragment : Fragment() { @@ -95,7 +87,7 @@ class DiaryListFragment : Fragment() { override fun onItemClick(diary: Diary) { // 💡 1. 부모 프래그먼트(DiaryFragment)가 생성한 Directions를 사용합니다. val action = DiaryFragmentDirections.actionDiaryFragmentToDiaryCheckFragment( - diaryId = diary.id + diaryId = diary.diaryId ) // 💡 2. 부모 프래그먼트의 NavController로 action을 실행합니다. parentFragment?.findNavController()?.navigate(action) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt index 3ff7e29f..8ad50a1f 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt @@ -19,10 +19,10 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.egobook.app.R import com.egobook.app.databinding.FragmentDiaryWriteBinding -import com.egobook.app.ui.diary.util.toDateTimeString -import com.egobook.app.ui.diary.util.toDayOfMonthString -import com.egobook.app.ui.diary.util.toMonthString -import com.egobook.app.ui.diary.util.toYearString +import com.egobook.app.ui.util.toDateTimeString +import com.egobook.app.ui.util.toDayOfMonthString +import com.egobook.app.ui.util.toMonthString +import com.egobook.app.ui.util.toYearString import com.egobook.app.ui.diary.viewmodel.DiaryWriteViewModel import com.google.android.material.imageview.ShapeableImageView import dagger.hilt.android.AndroidEntryPoint diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt index 0bfd4e75..5137b9e6 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt @@ -2,9 +2,11 @@ package com.egobook.app.ui.diary.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.Diary -import com.egobook.app.domain.model.DiaryType +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases +import com.egobook.app.ui.diary.mapper.DiaryEntityMapper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -12,6 +14,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import javax.inject.Inject import kotlinx.coroutines.flow.asStateFlow +import java.time.LocalDate import java.time.LocalDateTime @HiltViewModel @@ -24,41 +27,73 @@ class DiariesViewModel @Inject constructor( private var getDiariesJob: Job? = null init { - getDiaries(LocalDateTime.now(), null) + loadDiaries(LocalDate.now(), null) } fun onEvent(event: DiariesEvent) { when(event) { is DiariesEvent.SwipeTab -> { - getDiaries(state.value.selectedDate, event.types) + val domainTypes = event.displayTypes?.let { + DiaryEntityMapper.uiDisplayTypesToDomain(it) + } + + _state.value = state.value.copy(selectedTabType = domainTypes) + loadDiaries(state.value.selectedDate, domainTypes) } is DiariesEvent.ChangeDate -> { - getDiaries(event.date, null) // 날짜 변경 시 "전체" 탭으로 리셋 + // 도메인 엔티티 형식으로 날짜 변환 + val date = DiaryEntityMapper.uiYearMonthDateToDomain( + event.year, + event.month, + event.day, + ) + _state.value = state.value + .withDate(date) + .copy(selectedTabType = null) + loadDiaries(date, null) // 날짜 변경 시 "전체" 탭으로 리셋 } } } - private fun getDiaries(selectedDate: LocalDateTime, types: Set?) { + //상태를 보지 말고 뷰모델 내부 state 기반으로만 동작 + private fun loadDiaries(selectedDate: LocalDate, types: Set?) { + val currentState = state.value + getDiariesJob?.cancel() - getDiariesJob = diaryUseCases.getDiaries(selectedDate, types) + getDiariesJob = diaryUseCases + .getDiaries(currentState.selectedDate, currentState.selectedTabType) .onEach { diaries -> - _state.value = state.value.copy( - diaries = diaries, - selectedTabType = types, - selectedDate = selectedDate - ) + _state.value = currentState.copy(diaries = diaries) } .launchIn(viewModelScope) } + + // 날짜거 바뀌면 UI 표시값까지 자동 변경하는 확장함수 + private fun DiariesState.withDate(date: LocalDate): DiariesState { + return copy( + selectedDate = date, + yearText = date.year.toString(), + monthText = date.monthValue.toString(), + dayText = date.dayOfMonth.toString() + ) + } } sealed class DiariesEvent { - data class SwipeTab(val types: Set?) : DiariesEvent() - data class ChangeDate(val date: LocalDateTime) : DiariesEvent() + data class SwipeTab(val displayTypes: Set?) : DiariesEvent() + data class ChangeDate(val year: Int, val month: Int, val day: Int) : DiariesEvent() + } data class DiariesState( - val diaries: List = emptyList(), + val diaries: List = emptyList(), val selectedTabType: Set? = null, - val selectedDate: LocalDateTime = LocalDateTime.now() + + // UI 표시용 + val yearText: String = "", + val monthText: String = "", + val dayText: String = "", + + // 내부 로직용 + val selectedDate: LocalDate = LocalDate.now() ) diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt index 085853c6..56b89343 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt @@ -3,7 +3,7 @@ package com.egobook.app.ui.diary.viewmodel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.Diary +import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index ba53f5f1..0fc3bfc3 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases -import com.egobook.app.ui.diary.mapper.DiaryMapper +import com.egobook.app.ui.diary.mapper.DiaryEntityMapper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -143,7 +143,7 @@ class DiaryWriteViewModel @Inject constructor( val state = _contentState.value // UI displayType을 Domain DiaryType으로 변환 - val diaryTypes = DiaryMapper.uiDisplayTypesToDomain(state.selectedTypes) + val diaryTypes = DiaryEntityMapper.uiDisplayTypesToDomain(state.selectedTypes) // 감정 타입이 선택되지 않았으면 emotionLevel은 null val emotionLevel = if (state.selectedTypes.contains("감정")) { diff --git a/app/src/main/java/com/egobook/app/ui/diary/util/DateTimeExtensions.kt b/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt similarity index 97% rename from app/src/main/java/com/egobook/app/ui/diary/util/DateTimeExtensions.kt rename to app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt index 7c98c1ad..3e145002 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/util/DateTimeExtensions.kt +++ b/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt @@ -1,6 +1,5 @@ -package com.egobook.app.ui.diary.util +package com.egobook.app.ui.util -import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter From 4822153af0a0e0b63a4496ba3fa356147213494b Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 16:50:55 +0900 Subject: [PATCH 09/30] =?UTF-8?q?refactor:=20DiariesPagingSource=20?= =?UTF-8?q?=EC=8B=A0=EC=84=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/diary/DiaryRepositoryImpl.kt | 47 ++++++++++++++++++- .../diary/paging/DiariesPagingSource.kt | 41 +++++++++++++++- .../java/com/egobook/app/di/ServiceModule.kt | 7 +++ .../domain/model/diary/mapper/DiaryMapper.kt | 13 +++++ .../repository/diary/DiaryRepository.kt | 4 +- .../usecase/diaryusecase/DiaryUseCases.kt | 20 ++------ .../ui/diary/viewmodel/DiariesViewModel.kt | 6 ++- 7 files changed, 115 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 842cae23..51bb6aa5 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -1,4 +1,49 @@ package com.egobook.app.data.repository.diary -class DiaryRepositoryImpl { +import androidx.paging.PagingData +import com.egobook.app.data.api.DiaryApiService +import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryFilter +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.repository.diary.DiaryRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class DiaryRepositoryImpl @Inject constructor( + private val apiService: DiaryApiService +) : DiaryRepository { + override fun getDiaries( + filter: DiaryFilter, + size: Int + ): Flow> { + TODO("Not yet implemented") + } + + override suspend fun getDiary(diaryId: Long): Result { + TODO("Not yet implemented") + } + + override suspend fun addDiary( + date: String, + types: List, + emotionLevel: Int?, + content: String + ): Result { + TODO("Not yet implemented") + } + + override suspend fun updateDiary( + diaryId: Long, + date: String, + types: List, + emotionLevel: Int?, + content: String + ): Result { + TODO("Not yet implemented") + } + + override suspend fun deleteDiary(diaryId: Long): Result { + TODO("Not yet implemented") + } + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt index c37b13d8..901f5ad6 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt @@ -1,4 +1,43 @@ package com.egobook.app.data.repository.diary.paging -class DiariesPagingSource { +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.egobook.app.data.api.DiaryApiService +import com.egobook.app.domain.model.diary.entity.DiaryFilter +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toEntity +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toRequestParams +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiarySummary + + +class DiariesPagingSource( + private val apiService: DiaryApiService, + private val filter: DiaryFilter +): PagingSource() { + override fun getRefreshKey(state: PagingState): Int { + return 1 + } + + override suspend fun load(params: LoadParams): LoadResult { + val page = params.key ?: 1 // api가 보내주는 디폴트값이 1이어서 + val size = params.loadSize + + val (dateParam, typesParam) = filter.toRequestParams() + + val response = apiService.getDiaries( + date = dateParam, + type = typesParam, + page = page, + size = size + ) + + val diarySlice = response.data.diaries + + return LoadResult.Page( + data = diarySlice.content.map { it.toEntity().toDiarySummary() }, + prevKey = if (page == 1) null else page - 1, + nextKey = if (diarySlice.hasNext) page + 1 else null + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/ServiceModule.kt index dbf031db..0e826898 100644 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/ServiceModule.kt @@ -2,6 +2,7 @@ package com.egobook.app.di import com.egobook.app.data.api.AuthApiService import com.egobook.app.data.api.CounselingApiService +import com.egobook.app.data.api.DiaryApiService import com.egobook.app.data.api.FriendsApiService import com.egobook.app.data.api.NotificationApiService import com.egobook.app.data.api.QuestionApiService @@ -43,4 +44,10 @@ object ServiceModule { @Singleton fun provideAuthService(retrofit: Retrofit): AuthApiService = retrofit.create(AuthApiService::class.java) + + @Provides + @Singleton + fun provideDiaryService(retrofit: Retrofit): DiaryApiService = + retrofit.create(DiaryApiService::class.java) + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index 88f9e4c6..23e0380a 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -5,6 +5,7 @@ import com.egobook.app.data.model.diary.response.DiaryEntryResponse import com.egobook.app.data.model.diary.response.DiarySlice import com.egobook.app.domain.model.diary.entity.DayDiaries import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiaryList import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType @@ -73,5 +74,17 @@ object DiaryMapper { // ========== Domain Entity -> Request ========== + /** + * DiaryFilter → RequestParams + */ + + fun DiaryFilter.toRequestParams(): Pair { + val dateParam = date.toString() + val typesParam = types?.joinToString(",") { it.name } ?: "" + + return dateParam to typesParam + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt index 51b5f188..ee9888a2 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -2,6 +2,7 @@ package com.egobook.app.domain.repository.diary import androidx.paging.PagingData import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import kotlinx.coroutines.flow.Flow @@ -16,8 +17,7 @@ interface DiaryRepository { * @param size 페이지 크기 */ fun getDiaries( - date: LocalDate, - type: DiaryType, + filter: DiaryFilter, size: Int = 10 ): Flow> diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 17616d88..6407c616 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -1,6 +1,7 @@ package com.egobook.app.domain.usecase.diaryusecase import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.repository.diary.FakeDiaryRepository @@ -24,23 +25,8 @@ data class DiaryUseCases @Inject constructor ( class GetDiaries @Inject constructor( private val repository: FakeDiaryRepository ) { - operator fun invoke( - selectedDate: LocalDate = LocalDate.now(), - types: Set? = null // null = 전체 탭 - ): Flow> { - return repository.getDiaries() - .map { diaries -> - diaries - .filter { diary -> - val isSameDate = diary.createdAt.year == selectedDate.year && - diary.createdAt.month == selectedDate.month && - diary.createdAt.dayOfMonth == selectedDate.dayOfMonth - val isCorrectType = types == null || diary.types.any { it in types } - - isSameDate && isCorrectType - } - .sortedByDescending { it.writtenAt } - } + operator fun invoke(filter: DiaryFilter): Flow> { + return repository.getDiaries(filter) } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt index 5137b9e6..2aca7c08 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt @@ -3,6 +3,7 @@ package com.egobook.app.ui.diary.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases @@ -57,18 +58,19 @@ class DiariesViewModel @Inject constructor( //상태를 보지 말고 뷰모델 내부 state 기반으로만 동작 private fun loadDiaries(selectedDate: LocalDate, types: Set?) { + val filter = DiaryFilter(selectedDate, types) val currentState = state.value getDiariesJob?.cancel() getDiariesJob = diaryUseCases - .getDiaries(currentState.selectedDate, currentState.selectedTabType) + .getDiaries(filter) .onEach { diaries -> _state.value = currentState.copy(diaries = diaries) } .launchIn(viewModelScope) } - // 날짜거 바뀌면 UI 표시값까지 자동 변경하는 확장함수 + // 날짜가 바뀌면 UI 표시값까지 자동 변경하는 확장함수 private fun DiariesState.withDate(date: LocalDate): DiariesState { return copy( selectedDate = date, From 210ac26be7350659377794f5fc2596c48f107c34 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 17:59:57 +0900 Subject: [PATCH 10/30] =?UTF-8?q?refactor:=20repository=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/diary/DiaryRepositoryImpl.kt | 26 +++++++---- .../diary/paging/DiariesPagingSource.kt | 7 ++- .../java/com/egobook/app/di/UseCaseModule.kt | 3 +- .../domain/model/diary/mapper/DiaryMapper.kt | 10 ++--- .../repository/diary/DiaryRepository.kt | 21 +++------ .../usecase/diaryusecase/DiaryUseCases.kt | 43 +++++++------------ 6 files changed, 53 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 51bb6aa5..a3cda387 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -1,12 +1,17 @@ package com.egobook.app.data.repository.diary +import androidx.paging.Pager +import androidx.paging.PagingConfig import androidx.paging.PagingData import com.egobook.app.data.api.DiaryApiService +import com.egobook.app.data.repository.diary.paging.DiariesPagingSource import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow +import java.time.LocalDateTime import javax.inject.Inject class DiaryRepositoryImpl @Inject constructor( @@ -16,33 +21,38 @@ class DiaryRepositoryImpl @Inject constructor( filter: DiaryFilter, size: Int ): Flow> { - TODO("Not yet implemented") + return Pager( + config = PagingConfig( + pageSize = size, + enablePlaceholders = false + ), + pagingSourceFactory = { DiariesPagingSource(apiService, filter) } + ).flow } - override suspend fun getDiary(diaryId: Long): Result { + override suspend fun getDiaryById(diaryId: Long): Result { TODO("Not yet implemented") } override suspend fun addDiary( - date: String, - types: List, + types: Set, emotionLevel: Int?, - content: String + content: String, + dateTime: LocalDateTime, ): Result { TODO("Not yet implemented") } override suspend fun updateDiary( diaryId: Long, - date: String, - types: List, + types: Set, emotionLevel: Int?, content: String ): Result { TODO("Not yet implemented") } - override suspend fun deleteDiary(diaryId: Long): Result { + override suspend fun deleteDiaryById(diaryId: Long): Result { TODO("Not yet implemented") } diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt index 901f5ad6..2a9f6624 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt @@ -5,7 +5,7 @@ import androidx.paging.PagingState import com.egobook.app.data.api.DiaryApiService import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary -import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toEntity +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toRequestParams import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiarySummary @@ -14,6 +14,7 @@ class DiariesPagingSource( private val apiService: DiaryApiService, private val filter: DiaryFilter ): PagingSource() { + override fun getRefreshKey(state: PagingState): Int { return 1 } @@ -24,6 +25,7 @@ class DiariesPagingSource( val (dateParam, typesParam) = filter.toRequestParams() + //응답 받은거 val response = apiService.getDiaries( date = dateParam, type = typesParam, @@ -33,8 +35,9 @@ class DiariesPagingSource( val diarySlice = response.data.diaries + return LoadResult.Page( - data = diarySlice.content.map { it.toEntity().toDiarySummary() }, + data = diarySlice.content.map { it.toDiaryEntity().toDiarySummary() }, prevKey = if (page == 1) null else page - 1, nextKey = if (diarySlice.hasNext) page + 1 else null ) diff --git a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt index c6db9201..382fecea 100644 --- a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt +++ b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt @@ -2,6 +2,7 @@ package com.egobook.app.di import com.egobook.app.domain.repository.diary.FakeDiaryRepository import com.egobook.app.domain.repository.auth.AuthRepository +import com.egobook.app.domain.repository.diary.DiaryRepository import com.egobook.app.domain.usecase.authusecase.AuthUseCases import com.egobook.app.domain.usecase.authusecase.GoogleAutoLogin import com.egobook.app.domain.usecase.authusecase.GoogleLogin @@ -27,7 +28,7 @@ object UseCaseModule { @Provides @Singleton - fun provideDiaryUseCases(repository: FakeDiaryRepository): DiaryUseCases { + fun provideDiaryUseCases(repository: DiaryRepository): DiaryUseCases { return DiaryUseCases( getDiaries = GetDiaries(repository), diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index 23e0380a..add5222e 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -23,17 +23,17 @@ object DiaryMapper { /** * DiariesResponse → DayDiaries */ - fun DiariesResponse.toEntity(): DayDiaries { + fun DiariesResponse.toDayDiariesEntity(): DayDiaries { return DayDiaries( dailyCount = dailyCount, - diaries = diaries.toEntity() + diaries = diaries.toDiaryListEntity() ) } /** * DiaryEntryResponse → Diary */ - fun DiaryEntryResponse.toEntity(): Diary { + fun DiaryEntryResponse.toDiaryEntity(): Diary { return Diary( diaryId = diaryId, date = LocalDate.parse(date), @@ -48,9 +48,9 @@ object DiaryMapper { /** * DiarySlice → DiaryList */ - fun DiarySlice.toEntity(): DiaryList { + fun DiarySlice.toDiaryListEntity(): DiaryList { return DiaryList( - content = content.map { it.toEntity().toDiarySummary() }, + content = content.map { it.toDiaryEntity().toDiarySummary() }, page = page, size = size, hasNext = hasNext diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt index ee9888a2..ee9b4dc5 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -7,15 +7,9 @@ import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import kotlinx.coroutines.flow.Flow import java.time.LocalDate +import java.time.LocalDateTime interface DiaryRepository { - - /** - * 일기 목록 가져오기 (페이징) - * @param date 날짜 필터 (예: "2026-02-07") - * @param type 타입 필터 (예: "EMOTION") - * @param size 페이지 크기 - */ fun getDiaries( filter: DiaryFilter, size: Int = 10 @@ -25,16 +19,16 @@ interface DiaryRepository { * 일기 상세 조회 * @param diaryId 일기 ID */ - suspend fun getDiary(diaryId: Long): Result + suspend fun getDiaryById(diaryId: Long): Result /** * 일기 생성 */ suspend fun addDiary( - date: String, - types: List, + types: Set, emotionLevel: Int?, - content: String + content: String, + dateTime: LocalDateTime, ): Result // diaryId 반환 /** @@ -42,8 +36,7 @@ interface DiaryRepository { */ suspend fun updateDiary( diaryId: Long, - date: String, - types: List, + types: Set, emotionLevel: Int?, content: String ): Result @@ -51,5 +44,5 @@ interface DiaryRepository { /** * 일기 삭제 */ - suspend fun deleteDiary(diaryId: Long): Result + suspend fun deleteDiaryById(diaryId: Long): Result } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 6407c616..d008240e 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -1,13 +1,12 @@ package com.egobook.app.domain.usecase.diaryusecase +import androidx.paging.PagingData import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType -import com.egobook.app.domain.repository.diary.FakeDiaryRepository +import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject @@ -23,15 +22,15 @@ data class DiaryUseCases @Inject constructor ( // 각 유스케이스들 정의 class GetDiaries @Inject constructor( - private val repository: FakeDiaryRepository + private val repository: DiaryRepository ) { - operator fun invoke(filter: DiaryFilter): Flow> { + operator fun invoke(filter: DiaryFilter): Flow> { return repository.getDiaries(filter) } } class GetDiary @Inject constructor( - private val repository: FakeDiaryRepository + private val repository: DiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.getDiaryById(id) @@ -39,43 +38,33 @@ class GetDiary @Inject constructor( } class AddDiary @Inject constructor( - private val repository: FakeDiaryRepository + private val repository: DiaryRepository ) { suspend operator fun invoke( - content: String, types: Set, - emotionLevel: Int?, // 1~5 사이의 감정 레벨 - createdAt: LocalDateTime // 일기가 귀속될 날짜 + emotionLevel: Int?, + content: String, + dateTime: LocalDateTime, ): Result { - return repository.addDiary( - content = content, - types = types, - emotionLevel = emotionLevel, - createdAt = createdAt - ) + return TODO() } } class UpdateDiary @Inject constructor( - private val repository: FakeDiaryRepository + private val repository: DiaryRepository ) { suspend operator fun invoke( - id: Long, - content: String, + diaryId: Long, types: Set, - emotionLevel: Int? + emotionLevel: Int?, + content: String ): Result { - return repository.updateDiary( - id = id, - content = content, - types = types, - emotionLevel = emotionLevel - ) + return TODO() } } class DeleteDiary @Inject constructor( - private val repository: FakeDiaryRepository + private val repository: DiaryRepository ) { suspend operator fun invoke(id: Long): Result { return repository.deleteDiaryById(id) From da29a1bc794e0c3668724f7e808cf9dce7313848 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 18:22:37 +0900 Subject: [PATCH 11/30] =?UTF-8?q?refactor:=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diary/FakeDiaryRepositoryImpl.kt | 596 +++++++++--------- .../com/egobook/app/di/RepositoryModule.kt | 11 +- .../app/ui/diary/adapter/DiaryRVAdapter.kt | 33 +- .../app/ui/diary/view/DiaryCheckFragment.kt | 6 +- .../app/ui/diary/view/DiaryListFragment.kt | 32 +- .../ui/diary/viewmodel/DiariesViewModel.kt | 39 +- .../ui/diary/viewmodel/DiaryWriteViewModel.kt | 84 +-- 7 files changed, 401 insertions(+), 400 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt index df6f17f0..2e74639d 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/FakeDiaryRepositoryImpl.kt @@ -1,298 +1,298 @@ -package com.egobook.app.data.repository.diary - -import com.egobook.app.domain.model.diary.entity.Diary -import com.egobook.app.domain.model.diary.entity.DiaryType -import com.egobook.app.domain.repository.diary.FakeDiaryRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import java.time.LocalDateTime -import javax.inject.Inject - -/** - * 테스트 및 개발용 더미 일기 저장소 - * - * 메모리 내에서 더미 데이터를 관리하며, 실제 API 호출 없이 일기 기능을 테스트할 수 있습니다. - * - * 실제 백엔드 API 연동 시: - * 1. 새로운 DiaryRepositoryImpl 클래스를 생성하여 ApiService를 사용하여 구현 - * 2. RepositoryModule.kt에서 FakeDiaryRepositoryImpl -> DiaryRepositoryImpl로 변경 - * - * @see FakeDiaryRepository - */ -class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { - - // 더미 데이터 삽입 - private val diariesFlow = MutableStateFlow( - listOf( - // --- 오늘 날짜 (Today) --- - Diary( - id = 1L, - content = "오늘 아침, 상쾌하게 하루를 시작했다. 기분이 좋다.", - types = setOf(DiaryType.EMOTION, DiaryType.THANKS), - createdAt = LocalDateTime.now().withHour(8).withMinute(30), - writtenAt = LocalDateTime.now().withHour(8).withMinute(30), - emotionLevel = 5 // VERY_GOOD - ), - Diary( - id = 2L, - content = "아침 운동을 하니까 몸이 가벼워진 느낌이다.", - types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(9).withMinute(0), - writtenAt = LocalDateTime.now().withHour(9).withMinute(0), - emotionLevel = 4 // GOOD - ), - Diary( - id = 3L, - content = "회의에서 좋은 아이디어를 냈다. 팀원들이 좋아해줘서 기뻤다.", - types = setOf(DiaryType.PRAISE, DiaryType.THANKS), - createdAt = LocalDateTime.now().withHour(10).withMinute(30), - writtenAt = LocalDateTime.now().withHour(10).withMinute(30), - emotionLevel = null - ), - Diary( - id = 4L, - content = "커피를 마시면서 잠깐 쉬는 시간. 행복한 순간이다.", - types = setOf(DiaryType.THANKS), - createdAt = LocalDateTime.now().withHour(11).withMinute(0), - writtenAt = LocalDateTime.now().withHour(11).withMinute(0), - emotionLevel = null - ), - Diary( - id = 5L, - content = "점심 먹고 나니 너무 졸리다. 오후 업무가 걱정된다.", - types = setOf(DiaryType.EMOTION, DiaryType.WORRY), - createdAt = LocalDateTime.now().withHour(13).withMinute(10), - writtenAt = LocalDateTime.now().withHour(13).withMinute(15), - emotionLevel = 2 // BAD - ), - Diary( - id = 6L, - content = "프로젝트 진행이 잘 안 풀린다. 답답한 마음이 든다.", - types = setOf(DiaryType.WORRY, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(14).withMinute(30), - writtenAt = LocalDateTime.now().withHour(14).withMinute(30), - emotionLevel = 2 // BAD - ), - Diary( - id = 7L, - content = "동료가 도와줘서 문제를 해결했다. 정말 고맙다.", - types = setOf(DiaryType.THANKS, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(15).withMinute(20), - writtenAt = LocalDateTime.now().withHour(15).withMinute(20), - emotionLevel = 4 // GOOD - ), - Diary( - id = 8L, - content = "오후 간식으로 먹은 케이크가 정말 맛있었다. 작은 행복!", - types = setOf(DiaryType.THANKS), - createdAt = LocalDateTime.now().withHour(16).withMinute(0), - writtenAt = LocalDateTime.now().withHour(16).withMinute(0), - emotionLevel = null - ), - Diary( - id = 9L, - content = "오늘 하루 일을 다 끝냈다. 뿌듯하다!", - types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(17).withMinute(30), - writtenAt = LocalDateTime.now().withHour(17).withMinute(30), - emotionLevel = 5 // VERY_GOOD - ), - Diary( - id = 10L, - content = "오늘 저녁은 뭘 먹을지 고민이다. 하루 중 가장 큰 고민.", - types = setOf(DiaryType.WORRY), - createdAt = LocalDateTime.now().withHour(18).withMinute(0), - writtenAt = LocalDateTime.now().withHour(18).withMinute(0), - emotionLevel = null - ), - Diary( - id = 11L, - content = "가족과 함께 저녁을 먹었다. 따뜻한 시간이었다.", - types = setOf(DiaryType.THANKS, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(19).withMinute(30), - writtenAt = LocalDateTime.now().withHour(19).withMinute(30), - emotionLevel = 4 // GOOD - ), - Diary( - id = 12L, - content = "TV 보면서 편하게 쉬는 중. 이런 여유가 필요했다.", - types = setOf(DiaryType.THANKS), - createdAt = LocalDateTime.now().withHour(20).withMinute(0), - writtenAt = LocalDateTime.now().withHour(20).withMinute(0), - emotionLevel = null - ), - Diary( - id = 13L, - content = "오늘 하루를 돌아보니 감사한 일이 많았다.", - types = setOf(DiaryType.THANKS, DiaryType.PRAISE), - createdAt = LocalDateTime.now().withHour(21).withMinute(0), - writtenAt = LocalDateTime.now().withHour(21).withMinute(0), - emotionLevel = null - ), - Diary( - id = 14L, - content = "내일은 더 잘할 수 있을 것 같다. 파이팅!", - types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), - createdAt = LocalDateTime.now().withHour(22).withMinute(0), - writtenAt = LocalDateTime.now().withHour(22).withMinute(0), - emotionLevel = 4 // GOOD - ), - Diary( - id = 15L, - content = "하루를 마무리하며 일기를 쓴다. 좋은 습관이다.", - types = setOf(DiaryType.PRAISE), - createdAt = LocalDateTime.now().withHour(23).withMinute(0), - writtenAt = LocalDateTime.now().withHour(23).withMinute(0), - emotionLevel = null - ), - - // --- 어제 날짜 (Yesterday) --- - Diary( - id = 16L, - content = "어제는 정말 힘든 하루였다. 빨리 잊고 싶다.", - types = setOf(DiaryType.EMOTION), - createdAt = LocalDateTime.now().minusDays(1).withHour(23).withMinute(50), - writtenAt = LocalDateTime.now().minusDays(1).withHour(23).withMinute(50), - emotionLevel = 1 // VERY_BAD - ), - Diary( - id = 17L, - content = "친구에게 작은 선물을 받았는데, 정말 고마웠다.", - types = setOf(DiaryType.THANKS), - createdAt = LocalDateTime.now().minusDays(1).withHour(15).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(1).withHour(15).withMinute(0), - emotionLevel = null - ), - Diary( - id = 18L, - content = "어제 내가 해낸 작은 성과에 대해 스스로를 칭찬한다.", - types = setOf(DiaryType.PRAISE), - createdAt = LocalDateTime.now().minusDays(1).withHour(21).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(1).withHour(22) - .withMinute(30), // 1시간 30분 뒤 수정 - emotionLevel = null - ), - - // --- 2일 전 날짜 --- - Diary( - id = 19L, - content = "이틀 전, 진로에 대해 계속 고민만 하다 하루가 갔다.", - types = setOf(DiaryType.WORRY), - createdAt = LocalDateTime.now().minusDays(2).withHour(22).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(2).withHour(22) - .withMinute(10), // 10분 후 수정 - emotionLevel = null - ), - - // --- 3일 전 날짜 --- - Diary( - id = 20L, - content = "3일 전, 오늘 나 자신을 조금은 칭찬해주고 싶었다.", - types = setOf(DiaryType.PRAISE, DiaryType.THANKS), - createdAt = LocalDateTime.now().minusDays(3).withHour(21).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(3).withHour(22).withMinute(0), // 1시간 후 수정 - emotionLevel = null - ), - - // --- 4일 전 날짜 --- - Diary( - id = 21L, - content = "프로젝트를 잘 할 수 있을까 너무 걱정됐던 날.", - types = setOf(DiaryType.EMOTION, DiaryType.WORRY), - createdAt = LocalDateTime.now().minusDays(4).withHour(10).withMinute(0), - writtenAt = LocalDateTime.now().minusDays(3).withHour(11).withMinute(0), // 하루 뒤에 수정 - emotionLevel = 1 // VERY_BAD - ), - - // --- 일주일 전 날짜 --- - Diary( - id = 22L, - content = "일주일 전의 나는 무엇을 하고 있었을까? 평범하지만 괜찮은 하루였다.", - types = setOf(DiaryType.EMOTION), - createdAt = LocalDateTime.now().minusWeeks(1).withHour(16).withMinute(0), - writtenAt = LocalDateTime.now().minusWeeks(1).withHour(16).withMinute(0), - emotionLevel = 4 // GOOD - ), - Diary( - id = 23L, - content = "모든 타입이 포함된 종합 일기. 정말 많은 일이 있었다.", - types = setOf( - DiaryType.EMOTION, - DiaryType.WORRY, - DiaryType.PRAISE, - DiaryType.THANKS - ), - createdAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), - writtenAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), - emotionLevel = 3 // NORMAL - ) - ) - ) - - override fun getDiaries(): Flow> = - diariesFlow.asStateFlow() - - override suspend fun getDiaryById(id: Long): Result = - runCatching { - diariesFlow.value.find { it.id == id } - } - - override suspend fun addDiary( - content: String, - types: Set, - emotionLevel: Int?, // 1~5 사이의 감정 레벨 - createdAt: LocalDateTime // 일기가 귀속될 날짜 - ): Result = - runCatching { - val now = LocalDateTime.now() // 작성(수정) 시각 - - val newDiary = Diary( - id = (diariesFlow.value.maxOfOrNull { it.id } ?: 0L) + 1, - content = content, - types = types, - emotionLevel = emotionLevel, - createdAt = createdAt, // 선택한 날짜 (일기가 귀속될 날짜) - writtenAt = now // 실제 작성 시각 - ) - - diariesFlow.update { current -> - current + newDiary - } - - newDiary - } - - override suspend fun updateDiary( - id: Long, - content: String, - types: Set, - emotionLevel: Int? - ): Result = - runCatching { - val existing = diariesFlow.value.find { it.id == id } - ?: throw IllegalArgumentException("일기를 찾을 수 없습니다.") - - val updatedDiary = existing.copy( - content = content, - types = types, - emotionLevel = emotionLevel, - // createdAt은 기존 값 유지 (copy시 자동) - writtenAt = LocalDateTime.now() // 수정 시각만 갱신 - ) - - diariesFlow.update { current -> - current.map { if (it.id == id) updatedDiary else it } - } - - updatedDiary - } - - override suspend fun deleteDiaryById(id: Long): Result = - runCatching { - diariesFlow.update { current -> - current.filterNot { it.id == id } - } - } -} \ No newline at end of file +//package com.egobook.app.data.repository.diary +// +//import com.egobook.app.domain.model.diary.entity.Diary +//import com.egobook.app.domain.model.diary.entity.DiaryType +//import com.egobook.app.domain.repository.diary.FakeDiaryRepository +//import kotlinx.coroutines.flow.Flow +//import kotlinx.coroutines.flow.MutableStateFlow +//import kotlinx.coroutines.flow.asStateFlow +//import kotlinx.coroutines.flow.update +//import java.time.LocalDateTime +//import javax.inject.Inject +// +///** +// * 테스트 및 개발용 더미 일기 저장소 +// * +// * 메모리 내에서 더미 데이터를 관리하며, 실제 API 호출 없이 일기 기능을 테스트할 수 있습니다. +// * +// * 실제 백엔드 API 연동 시: +// * 1. 새로운 DiaryRepositoryImpl 클래스를 생성하여 ApiService를 사용하여 구현 +// * 2. RepositoryModule.kt에서 FakeDiaryRepositoryImpl -> DiaryRepositoryImpl로 변경 +// * +// * @see FakeDiaryRepository +// */ +//class FakeDiaryRepositoryImpl @Inject constructor() : FakeDiaryRepository { +// +// // 더미 데이터 삽입 +// private val diariesFlow = MutableStateFlow( +// listOf( +// // --- 오늘 날짜 (Today) --- +// Diary( +// id = 1L, +// content = "오늘 아침, 상쾌하게 하루를 시작했다. 기분이 좋다.", +// types = setOf(DiaryType.EMOTION, DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().withHour(8).withMinute(30), +// writtenAt = LocalDateTime.now().withHour(8).withMinute(30), +// emotionLevel = 5 // VERY_GOOD +// ), +// Diary( +// id = 2L, +// content = "아침 운동을 하니까 몸이 가벼워진 느낌이다.", +// types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(9).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(9).withMinute(0), +// emotionLevel = 4 // GOOD +// ), +// Diary( +// id = 3L, +// content = "회의에서 좋은 아이디어를 냈다. 팀원들이 좋아해줘서 기뻤다.", +// types = setOf(DiaryType.PRAISE, DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().withHour(10).withMinute(30), +// writtenAt = LocalDateTime.now().withHour(10).withMinute(30), +// emotionLevel = null +// ), +// Diary( +// id = 4L, +// content = "커피를 마시면서 잠깐 쉬는 시간. 행복한 순간이다.", +// types = setOf(DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().withHour(11).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(11).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 5L, +// content = "점심 먹고 나니 너무 졸리다. 오후 업무가 걱정된다.", +// types = setOf(DiaryType.EMOTION, DiaryType.CONCERN), +// createdAt = LocalDateTime.now().withHour(13).withMinute(10), +// writtenAt = LocalDateTime.now().withHour(13).withMinute(15), +// emotionLevel = 2 // BAD +// ), +// Diary( +// id = 6L, +// content = "프로젝트 진행이 잘 안 풀린다. 답답한 마음이 든다.", +// types = setOf(DiaryType.CONCERN, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(14).withMinute(30), +// writtenAt = LocalDateTime.now().withHour(14).withMinute(30), +// emotionLevel = 2 // BAD +// ), +// Diary( +// id = 7L, +// content = "동료가 도와줘서 문제를 해결했다. 정말 고맙다.", +// types = setOf(DiaryType.GRATITUDE, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(15).withMinute(20), +// writtenAt = LocalDateTime.now().withHour(15).withMinute(20), +// emotionLevel = 4 // GOOD +// ), +// Diary( +// id = 8L, +// content = "오후 간식으로 먹은 케이크가 정말 맛있었다. 작은 행복!", +// types = setOf(DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().withHour(16).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(16).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 9L, +// content = "오늘 하루 일을 다 끝냈다. 뿌듯하다!", +// types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(17).withMinute(30), +// writtenAt = LocalDateTime.now().withHour(17).withMinute(30), +// emotionLevel = 5 // VERY_GOOD +// ), +// Diary( +// id = 10L, +// content = "오늘 저녁은 뭘 먹을지 고민이다. 하루 중 가장 큰 고민.", +// types = setOf(DiaryType.CONCERN), +// createdAt = LocalDateTime.now().withHour(18).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(18).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 11L, +// content = "가족과 함께 저녁을 먹었다. 따뜻한 시간이었다.", +// types = setOf(DiaryType.GRATITUDE, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(19).withMinute(30), +// writtenAt = LocalDateTime.now().withHour(19).withMinute(30), +// emotionLevel = 4 // GOOD +// ), +// Diary( +// id = 12L, +// content = "TV 보면서 편하게 쉬는 중. 이런 여유가 필요했다.", +// types = setOf(DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().withHour(20).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(20).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 13L, +// content = "오늘 하루를 돌아보니 감사한 일이 많았다.", +// types = setOf(DiaryType.GRATITUDE, DiaryType.PRAISE), +// createdAt = LocalDateTime.now().withHour(21).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(21).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 14L, +// content = "내일은 더 잘할 수 있을 것 같다. 파이팅!", +// types = setOf(DiaryType.PRAISE, DiaryType.EMOTION), +// createdAt = LocalDateTime.now().withHour(22).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(22).withMinute(0), +// emotionLevel = 4 // GOOD +// ), +// Diary( +// id = 15L, +// content = "하루를 마무리하며 일기를 쓴다. 좋은 습관이다.", +// types = setOf(DiaryType.PRAISE), +// createdAt = LocalDateTime.now().withHour(23).withMinute(0), +// writtenAt = LocalDateTime.now().withHour(23).withMinute(0), +// emotionLevel = null +// ), +// +// // --- 어제 날짜 (Yesterday) --- +// Diary( +// id = 16L, +// content = "어제는 정말 힘든 하루였다. 빨리 잊고 싶다.", +// types = setOf(DiaryType.EMOTION), +// createdAt = LocalDateTime.now().minusDays(1).withHour(23).withMinute(50), +// writtenAt = LocalDateTime.now().minusDays(1).withHour(23).withMinute(50), +// emotionLevel = 1 // VERY_BAD +// ), +// Diary( +// id = 17L, +// content = "친구에게 작은 선물을 받았는데, 정말 고마웠다.", +// types = setOf(DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().minusDays(1).withHour(15).withMinute(0), +// writtenAt = LocalDateTime.now().minusDays(1).withHour(15).withMinute(0), +// emotionLevel = null +// ), +// Diary( +// id = 18L, +// content = "어제 내가 해낸 작은 성과에 대해 스스로를 칭찬한다.", +// types = setOf(DiaryType.PRAISE), +// createdAt = LocalDateTime.now().minusDays(1).withHour(21).withMinute(0), +// writtenAt = LocalDateTime.now().minusDays(1).withHour(22) +// .withMinute(30), // 1시간 30분 뒤 수정 +// emotionLevel = null +// ), +// +// // --- 2일 전 날짜 --- +// Diary( +// id = 19L, +// content = "이틀 전, 진로에 대해 계속 고민만 하다 하루가 갔다.", +// types = setOf(DiaryType.CONCERN), +// createdAt = LocalDateTime.now().minusDays(2).withHour(22).withMinute(0), +// writtenAt = LocalDateTime.now().minusDays(2).withHour(22) +// .withMinute(10), // 10분 후 수정 +// emotionLevel = null +// ), +// +// // --- 3일 전 날짜 --- +// Diary( +// id = 20L, +// content = "3일 전, 오늘 나 자신을 조금은 칭찬해주고 싶었다.", +// types = setOf(DiaryType.PRAISE, DiaryType.GRATITUDE), +// createdAt = LocalDateTime.now().minusDays(3).withHour(21).withMinute(0), +// writtenAt = LocalDateTime.now().minusDays(3).withHour(22).withMinute(0), // 1시간 후 수정 +// emotionLevel = null +// ), +// +// // --- 4일 전 날짜 --- +// Diary( +// id = 21L, +// content = "프로젝트를 잘 할 수 있을까 너무 걱정됐던 날.", +// types = setOf(DiaryType.EMOTION, DiaryType.CONCERN), +// createdAt = LocalDateTime.now().minusDays(4).withHour(10).withMinute(0), +// writtenAt = LocalDateTime.now().minusDays(3).withHour(11).withMinute(0), // 하루 뒤에 수정 +// emotionLevel = 1 // VERY_BAD +// ), +// +// // --- 일주일 전 날짜 --- +// Diary( +// id = 22L, +// content = "일주일 전의 나는 무엇을 하고 있었을까? 평범하지만 괜찮은 하루였다.", +// types = setOf(DiaryType.EMOTION), +// createdAt = LocalDateTime.now().minusWeeks(1).withHour(16).withMinute(0), +// writtenAt = LocalDateTime.now().minusWeeks(1).withHour(16).withMinute(0), +// emotionLevel = 4 // GOOD +// ), +// Diary( +// id = 23L, +// content = "모든 타입이 포함된 종합 일기. 정말 많은 일이 있었다.", +// types = setOf( +// DiaryType.EMOTION, +// DiaryType.CONCERN, +// DiaryType.PRAISE, +// DiaryType.GRATITUDE +// ), +// createdAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), +// writtenAt = LocalDateTime.now().minusWeeks(1).withHour(23).withMinute(0), +// emotionLevel = 3 // NORMAL +// ) +// ) +// ) +// +// override fun getDiaries(): Flow> = +// diariesFlow.asStateFlow() +// +// override suspend fun getDiaryById(id: Long): Result = +// runCatching { +// diariesFlow.value.find { it.diaryId == id } +// } +// +// override suspend fun addDiary( +// content: String, +// types: Set, +// emotionLevel: Int?, // 1~5 사이의 감정 레벨 +// createdAt: LocalDateTime // 일기가 귀속될 날짜 +// ): Result = +// runCatching { +// val now = LocalDateTime.now() // 작성(수정) 시각 +// +// val newDiary = Diary( +// id = (diariesFlow.value.maxOfOrNull { it.diaryId } ?: 0L) + 1, +// content = content, +// types = types, +// emotionLevel = emotionLevel, +// createdAt = createdAt, // 선택한 날짜 (일기가 귀속될 날짜) +// writtenAt = now // 실제 작성 시각 +// ) +// +// diariesFlow.update { current -> +// current + newDiary +// } +// +// newDiary +// } +// +// override suspend fun updateDiary( +// id: Long, +// content: String, +// types: Set, +// emotionLevel: Int? +// ): Result = +// runCatching { +// val existing = diariesFlow.value.find { it.id == id } +// ?: throw IllegalArgumentException("일기를 찾을 수 없습니다.") +// +// val updatedDiary = existing.copy( +// content = content, +// types = types, +// emotionLevel = emotionLevel, +// // createdAt은 기존 값 유지 (copy시 자동) +// writtenAt = LocalDateTime.now() // 수정 시각만 갱신 +// ) +// +// diariesFlow.update { current -> +// current.map { if (it.id == id) updatedDiary else it } +// } +// +// updatedDiary +// } +// +// override suspend fun deleteDiaryById(id: Long): Result = +// runCatching { +// diariesFlow.update { current -> +// current.filterNot { it.id == id } +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt index 6d15d66a..ff8bd7f5 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt @@ -1,13 +1,12 @@ package com.egobook.app.di import com.egobook.app.data.repository.CounselingRepositoryImpl -import com.egobook.app.data.repository.diary.FakeDiaryRepositoryImpl import com.egobook.app.data.repository.FriendsRepositoryImpl import com.egobook.app.data.repository.NotificationRepositoryImpl import com.egobook.app.domain.repository.CounselingRepository import com.egobook.app.data.repository.auth.AuthRepositoryImpl import com.egobook.app.data.repository.QuestionRepositoryImpl -import com.egobook.app.domain.repository.diary.FakeDiaryRepository +import com.egobook.app.data.repository.diary.DiaryRepositoryImpl import com.egobook.app.domain.repository.FriendsRepository import com.egobook.app.domain.repository.NotificationRepository import com.egobook.app.domain.repository.auth.AuthRepository @@ -15,6 +14,7 @@ import com.egobook.app.ui.shop.NetworkStoreRepository import com.egobook.app.ui.shop.StoreRepository import dagger.Binds import com.egobook.app.domain.repository.QuestionRepository +import com.egobook.app.domain.repository.diary.DiaryRepository import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent @@ -48,9 +48,14 @@ abstract class RepositoryModule { // 2. DiaryRepositoryImpl 구현 (ApiService 사용) // 3. 이 바인딩을 DiaryRepositoryImpl -> DiaryRepository로 변경 // 4. 모든 UseCase의 FakeDiaryRepository -> DiaryRepository로 변경 +// @Binds +// @Singleton +// abstract fun bindFakeDiaryRepository(impl: FakeDiaryRepositoryImpl): FakeDiaryRepository + @Binds @Singleton - abstract fun bindFakeDiaryRepository(impl: FakeDiaryRepositoryImpl): FakeDiaryRepository + abstract fun bindDiaryRepository(impl: DiaryRepositoryImpl): DiaryRepository + @Binds @Singleton diff --git a/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt b/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt index 22ffbca5..5524c7d7 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/adapter/DiaryRVAdapter.kt @@ -5,20 +5,20 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.DrawableRes import androidx.core.view.isVisible +import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.egobook.app.R import com.egobook.app.databinding.ItemDiaryBinding -import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.ui.util.toTimeString class DiaryRVAdapter : - ListAdapter(DiaryDiffCallback()) { + PagingDataAdapter(DiaryDiffCallback()) { interface MyItemClickListener { - fun onItemClick(diary: Diary) + fun onItemClick(diary: DiarySummary) } private var myItemClickListener: MyItemClickListener? = null @@ -37,17 +37,18 @@ class DiaryRVAdapter : override fun onBindViewHolder(holder: ViewHolder, position: Int) { val diary = getItem(position) - holder.bind(diary) - - holder.itemView.setOnClickListener { - myItemClickListener?.onItemClick(diary) + diary?.let { + holder.bind(it) + holder.itemView.setOnClickListener { + myItemClickListener?.onItemClick(diary) + } } } inner class ViewHolder(private val binding: ItemDiaryBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(diary: Diary) { + fun bind(diary: DiarySummary) { binding.tvDiaryContent.text = diary.content binding.tvTime.text = diary.writtenAt.toTimeString() @@ -61,14 +62,14 @@ class DiaryRVAdapter : } // 타입 순서 정의 - val typeOrder = listOf(DiaryType.EMOTION, DiaryType.WORRY, DiaryType.PRAISE, DiaryType.THANKS) + val typeOrder = listOf(DiaryType.EMOTION, DiaryType.CONCERN, DiaryType.PRAISE, DiaryType.GRATITUDE) // 각 TextView 맵핑 val typeToTextView = mapOf( DiaryType.EMOTION to binding.tvEmotion, - DiaryType.WORRY to binding.tvWorry, + DiaryType.CONCERN to binding.tvWorry, DiaryType.PRAISE to binding.tvPraise, - DiaryType.THANKS to binding.tvThanks + DiaryType.GRATITUDE to binding.tvThanks ) // 모든 TextView 숨김 @@ -98,12 +99,12 @@ class DiaryRVAdapter : } } - class DiaryDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Diary, newItem: Diary): Boolean { - return oldItem.id == newItem.id // ID 기준으로 같은 아이템인지 판단 + class DiaryDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DiarySummary, newItem: DiarySummary): Boolean { + return oldItem.diaryId == newItem.diaryId // ID 기준으로 같은 아이템인지 판단 } - override fun areContentsTheSame(oldItem: Diary, newItem: Diary): Boolean { + override fun areContentsTheSame(oldItem: DiarySummary, newItem: DiarySummary): Boolean { return oldItem == newItem // 내용까지 동일한지 판단 } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt index fd0f16ca..1de817f4 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt @@ -69,7 +69,7 @@ class DiaryCheckFragment : Fragment() { val action = DiaryCheckFragmentDirections .actionDiaryCheckFragmentToDiaryWriteFragment( selectedDate = currentDiary.createdAt.toString(), - diaryId = currentDiary.id + diaryId = currentDiary.diaryId ) findNavController().navigate(action) } else { @@ -135,9 +135,9 @@ class DiaryCheckFragment : Fragment() { private fun setDiaryTypes(types: Set) { binding.apply { cvEmotion.isSelected = DiaryType.EMOTION in types - cvThought.isSelected = DiaryType.WORRY in types + cvThought.isSelected = DiaryType.CONCERN in types cvPraise.isSelected = DiaryType.PRAISE in types - cvGratitude.isSelected = DiaryType.THANKS in types + cvGratitude.isSelected = DiaryType.GRATITUDE in types } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt index 89991fd7..cf57af6a 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryListFragment.kt @@ -5,15 +5,17 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController +import androidx.paging.LoadState import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.egobook.app.databinding.FragmentDiaryListBinding -import com.egobook.app.domain.model.diary.entity.Diary +import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.ui.diary.adapter.DiaryRVAdapter import com.egobook.app.ui.diary.viewmodel.DiariesViewModel import kotlinx.coroutines.flow.collectLatest @@ -62,29 +64,29 @@ class DiaryListFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collectLatest { state -> - - diaryRVAdapter.submitList(state.diaries) - - val isEmpty = state.diaries.isEmpty() - - binding.layoutEmpty.visibility = - if (isEmpty) View.VISIBLE else View.GONE - - binding.rvDiary.visibility = - if (isEmpty) View.GONE else View.VISIBLE - + state.diaries.collectLatest { pagingData -> + diaryRVAdapter.submitData(pagingData) + } } } } + + // LoadState를 관찰하여 빈 상태 처리 + viewLifecycleOwner.lifecycleScope.launch { + diaryRVAdapter.loadStateFlow.collectLatest { loadStates -> + val isEmpty = loadStates.refresh is LoadState.NotLoading && diaryRVAdapter.itemCount == 0 + + binding.layoutEmpty.isVisible = isEmpty + binding.rvDiary.isVisible = !isEmpty + } + } } - - private fun initRecyclerView() { diaryRVAdapter.setMyItemClickListener(object : DiaryRVAdapter.MyItemClickListener { - override fun onItemClick(diary: Diary) { + override fun onItemClick(diary: DiarySummary) { // 💡 1. 부모 프래그먼트(DiaryFragment)가 생성한 Directions를 사용합니다. val action = DiaryFragmentDirections.actionDiaryFragmentToDiaryCheckFragment( diaryId = diary.diaryId diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt index 2aca7c08..99a44d72 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt @@ -2,21 +2,20 @@ package com.egobook.app.ui.diary.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.diary.entity.Diary +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases import com.egobook.app.ui.diary.mapper.DiaryEntityMapper import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import javax.inject.Inject import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.emptyFlow import java.time.LocalDate -import java.time.LocalDateTime +import javax.inject.Inject @HiltViewModel class DiariesViewModel @Inject constructor( @@ -25,8 +24,6 @@ class DiariesViewModel @Inject constructor( private val _state = MutableStateFlow(DiariesState()) // 뷰모델 내부 갱신용 val state = _state.asStateFlow() // 외부(ui) 읽기 전용 - private var getDiariesJob: Job? = null - init { loadDiaries(LocalDate.now(), null) } @@ -59,15 +56,11 @@ class DiariesViewModel @Inject constructor( //상태를 보지 말고 뷰모델 내부 state 기반으로만 동작 private fun loadDiaries(selectedDate: LocalDate, types: Set?) { val filter = DiaryFilter(selectedDate, types) - val currentState = state.value - - getDiariesJob?.cancel() - getDiariesJob = diaryUseCases + val diariesFlow = diaryUseCases .getDiaries(filter) - .onEach { diaries -> - _state.value = currentState.copy(diaries = diaries) - } - .launchIn(viewModelScope) + .cachedIn(viewModelScope) + + _state.value = state.value.copy(diaries = diariesFlow) } // 날짜가 바뀌면 UI 표시값까지 자동 변경하는 확장함수 @@ -88,14 +81,14 @@ sealed class DiariesEvent { } data class DiariesState( - val diaries: List = emptyList(), + val diaries: Flow> = emptyFlow(), val selectedTabType: Set? = null, - // UI 표시용 - val yearText: String = "", - val monthText: String = "", - val dayText: String = "", - // 내부 로직용 - val selectedDate: LocalDate = LocalDate.now() + val selectedDate: LocalDate = LocalDate.now(), + + // UI 표시용 + val yearText: String = selectedDate.year.toString(), + val monthText: String = selectedDate.monthValue.toString(), + val dayText: String = selectedDate.dayOfMonth.toString() ) diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index 0fc3bfc3..fa29560f 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -130,7 +130,7 @@ class DiaryWriteViewModel @Inject constructor( ) } is ContentEvent.SaveDiary -> { - saveDiary() + //saveDiary() } } } @@ -138,47 +138,47 @@ class DiaryWriteViewModel @Inject constructor( /** * 일기 저장 처리 (생성 또는 수정) */ - private fun saveDiary() { - viewModelScope.launch { - val state = _contentState.value - - // UI displayType을 Domain DiaryType으로 변환 - val diaryTypes = DiaryEntityMapper.uiDisplayTypesToDomain(state.selectedTypes) - - // 감정 타입이 선택되지 않았으면 emotionLevel은 null - val emotionLevel = if (state.selectedTypes.contains("감정")) { - state.selectedEmotionLevel - } else { - null - } - - // 수정 모드 vs 생성 모드 분기 - val result = if (isEditMode) { - // 수정 모드: updateDiary 호출 - diaryUseCases.updateDiary( - id = diaryId, - content = state.content, - types = diaryTypes, - emotionLevel = emotionLevel - ) - } else { - // 생성 모드: addDiary 호출 - diaryUseCases.addDiary( - content = state.content, - types = diaryTypes, - emotionLevel = emotionLevel, - createdAt = _selectedDate.value // savedStateHandle로 받은 선택된 날짜 - ) - } - - // 저장 결과 전달 - result.onSuccess { - _saveSuccess.emit(true) // 저장 성공 - }.onFailure { - _saveSuccess.emit(false) // 저장 실패 - } - } - } +// private fun saveDiary() { +// viewModelScope.launch { +// val state = _contentState.value +// +// // UI displayType을 Domain DiaryType으로 변환 +// val diaryTypes = DiaryEntityMapper.uiDisplayTypesToDomain(state.selectedTypes) +// +// // 감정 타입이 선택되지 않았으면 emotionLevel은 null +// val emotionLevel = if (state.selectedTypes.contains("감정")) { +// state.selectedEmotionLevel +// } else { +// null +// } +// +// // 수정 모드 vs 생성 모드 분기 +// val result = if (isEditMode) { +// // 수정 모드: updateDiary 호출 +//// diaryUseCases.updateDiary( +//// id = diaryId, +//// content = state.content, +//// types = diaryTypes, +//// emotionLevel = emotionLevel +//// ) +// } else { +// // 생성 모드: addDiary 호출 +//// diaryUseCases.addDiary( +//// content = state.content, +//// types = diaryTypes, +//// emotionLevel = emotionLevel, +//// createdAt = _selectedDate.value // savedStateHandle로 받은 선택된 날짜 +//// ) +// } +// +// // 저장 결과 전달 +// result.onSuccess { +// _saveSuccess.emit(true) // 저장 성공 +// }.onFailure { +// _saveSuccess.emit(false) // 저장 실패 +// } +// } +// } /** * 저장 버튼 활성화 조건 체크 From fc753a4530082ce31eb1ea16eda65a88fe051ce2 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 19:53:09 +0900 Subject: [PATCH 12/30] =?UTF-8?q?refactor:=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/diary/request/DiaryCreateRequest.kt | 2 +- .../repository/diary/DiaryRepositoryImpl.kt | 19 ++-- .../domain/model/diary/mapper/DiaryMapper.kt | 14 ++- .../repository/diary/DiaryRepository.kt | 8 +- .../usecase/diaryusecase/DiaryUseCases.kt | 9 +- .../app/ui/diary/mapper/DiaryEntityMapper.kt | 40 ++++++++- .../app/ui/diary/view/DiaryFragment.kt | 6 ++ .../app/ui/diary/view/DiaryWriteFragment.kt | 6 +- .../ui/diary/viewmodel/DiariesViewModel.kt | 6 +- .../ui/diary/viewmodel/DiaryWriteViewModel.kt | 86 +++++++++---------- 10 files changed, 121 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt index ad4f1286..4568d8e9 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt @@ -12,5 +12,5 @@ data class DiaryCreateRequest( @SerialName("content") val content: String, @SerialName("dateTime") - val dateTime: String + val dateTime: String //LocalDateTime의 String형식 이어야 함. ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index a3cda387..30750d13 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -4,11 +4,14 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.egobook.app.data.api.DiaryApiService +import com.egobook.app.data.model.diary.request.DiaryCreateRequest import com.egobook.app.data.repository.diary.paging.DiariesPagingSource +import com.egobook.app.data.util.safeApiCall import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryCreateRequest import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime @@ -34,13 +37,15 @@ class DiaryRepositoryImpl @Inject constructor( TODO("Not yet implemented") } - override suspend fun addDiary( - types: Set, - emotionLevel: Int?, - content: String, - dateTime: LocalDateTime, - ): Result { - TODO("Not yet implemented") + override suspend fun addDiary(diary: Diary): Result { + return safeApiCall( + apiCall = { + apiService.addDiary( + diary.toDiaryCreateRequest() + ) + }, + transform = { Unit } + ) } override suspend fun updateDiary( diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index add5222e..25542562 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -1,5 +1,6 @@ package com.egobook.app.domain.model.diary.mapper +import com.egobook.app.data.model.diary.request.DiaryCreateRequest import com.egobook.app.data.model.diary.response.DiariesResponse import com.egobook.app.data.model.diary.response.DiaryEntryResponse import com.egobook.app.data.model.diary.response.DiarySlice @@ -85,6 +86,17 @@ object DiaryMapper { return dateParam to typesParam } - + /** + * Diary → DiaryCreateRequest + * writtenAt 시각을 기준으로 생성 요청 변환 + */ + fun Diary.toDiaryCreateRequest(): DiaryCreateRequest { + return DiaryCreateRequest( + type = types.map { it.value }, + emotionLevel = emotionLevel, + content = content, + dateTime = writtenAt.toString() + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt index ee9b4dc5..ea68496f 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -6,7 +6,6 @@ import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import kotlinx.coroutines.flow.Flow -import java.time.LocalDate import java.time.LocalDateTime interface DiaryRepository { @@ -24,12 +23,7 @@ interface DiaryRepository { /** * 일기 생성 */ - suspend fun addDiary( - types: Set, - emotionLevel: Int?, - content: String, - dateTime: LocalDateTime, - ): Result // diaryId 반환 + suspend fun addDiary(diary: Diary): Result /** * 일기 수정 diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index d008240e..35a6c289 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -40,13 +40,8 @@ class GetDiary @Inject constructor( class AddDiary @Inject constructor( private val repository: DiaryRepository ) { - suspend operator fun invoke( - types: Set, - emotionLevel: Int?, - content: String, - dateTime: LocalDateTime, - ): Result { - return TODO() + suspend operator fun invoke(diary: Diary): Result { + return repository.addDiary(diary) } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index b28f97b8..f7234cff 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -1,7 +1,9 @@ package com.egobook.app.ui.diary.mapper +import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.model.diary.entity.DiaryType import java.time.LocalDate +import java.time.LocalDateTime /** * Domain 모델과 UI 레이어 간의 데이터 변환을 담당하는 매퍼 @@ -53,7 +55,39 @@ object DiaryEntityMapper { fun uiYearMonthDateToDomain(year: Int, month: Int, date: Int): LocalDate { return LocalDate.of(year, month, date) } - - // ========== EmotionLevel 변환 ========== - // Domain과 UI 모두 Int (1~5)를 사용 -> 변환 불필요 + + /** + * UI 상태를 Domain Diary 엔티티로 변환 (새 일기 생성용) + * @param selectedTypes UI displayType Set (예: ["감정", "고민"]) + * @param content 일기 내용 + * @param emotionLevel 감정 레벨 (1~5) + * @param dateTime 선택된 날짜+시간 + * @return 새로 생성할 Diary 엔티티 (diaryId와 createdAt는 임시값) + */ + fun createNewDiary( + selectedTypes: Set, + content: String, + emotionLevel: Int?, + dateTime: LocalDateTime + ): Diary { + // UI displayType을 Domain DiaryType으로 변환 + val diaryTypes = uiDisplayTypesToDomain(selectedTypes) + + // 감정 타입이 선택되지 않았으면 emotionLevel은 null + val finalEmotionLevel = if (selectedTypes.contains("감정")) { + emotionLevel + } else { + null + } + + return Diary( + diaryId = 0L, // 새 일기는 임시 ID (서버가 생성), 임시 삽입. + date = dateTime.toLocalDate(), + writtenAt = dateTime, + types = diaryTypes, + emotionLevel = finalEmotionLevel, + content = content, + createdAt = dateTime // 서버가 실제 값으로 대체. 임시 삽입 + ) + } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index aa71ece3..29b22767 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -55,6 +55,12 @@ setupClickListener() observeViewModel() } + + override fun onResume() { + super.onResume() + //다른 프래그먼트에서 돌아왔을 때 데이터 새로고침 + viewModel.onEvent(DiariesEvent.RefreshDiaries) + } private fun setupClickListener() { binding.apply { diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt index 8ad50a1f..b5ef9656 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt @@ -183,14 +183,10 @@ class DiaryWriteFragment : Fragment() { // 일기 타입 카드 선택 상태 업데이트 binding.cvEmotion.isSelected = state.selectedTypes.contains("감정") - binding.tvEmotion.isSelected = state.selectedTypes.contains("감정") binding.cvWorry.isSelected = state.selectedTypes.contains("고민") - binding.tvWorry.isSelected = state.selectedTypes.contains("고민") - binding.cvPraise.isSelected = state.selectedTypes.contains("칭찬") binding.tvPraise.isSelected = state.selectedTypes.contains("칭찬") binding.cvThanks.isSelected = state.selectedTypes.contains("감사") - binding.tvThanks.isSelected = state.selectedTypes.contains("감사") - + // "감정" 타입이 선택되었을 때만 레벨 선택 섹션 표시 val isEmotionSelected = state.selectedTypes.contains("감정") val visibility = if (isEmotionSelected) View.VISIBLE else View.GONE diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt index 99a44d72..9b8429b8 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt @@ -50,6 +50,10 @@ class DiariesViewModel @Inject constructor( .copy(selectedTabType = null) loadDiaries(date, null) // 날짜 변경 시 "전체" 탭으로 리셋 } + is DiariesEvent.RefreshDiaries -> { + // 현재 선택된 날짜와 탭으로 다시 로드 + loadDiaries(state.value.selectedDate, state.value.selectedTabType) + } } } @@ -77,7 +81,7 @@ class DiariesViewModel @Inject constructor( sealed class DiariesEvent { data class SwipeTab(val displayTypes: Set?) : DiariesEvent() data class ChangeDate(val year: Int, val month: Int, val day: Int) : DiariesEvent() - + data object RefreshDiaries : DiariesEvent() } data class DiariesState( diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index fa29560f..27dbf139 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -130,55 +130,55 @@ class DiaryWriteViewModel @Inject constructor( ) } is ContentEvent.SaveDiary -> { - //saveDiary() + saveDiary() } } } /** - * 일기 저장 처리 (생성 또는 수정) + * 일기 저장 처리 */ -// private fun saveDiary() { -// viewModelScope.launch { -// val state = _contentState.value -// -// // UI displayType을 Domain DiaryType으로 변환 -// val diaryTypes = DiaryEntityMapper.uiDisplayTypesToDomain(state.selectedTypes) -// -// // 감정 타입이 선택되지 않았으면 emotionLevel은 null -// val emotionLevel = if (state.selectedTypes.contains("감정")) { -// state.selectedEmotionLevel -// } else { -// null -// } -// -// // 수정 모드 vs 생성 모드 분기 -// val result = if (isEditMode) { -// // 수정 모드: updateDiary 호출 -//// diaryUseCases.updateDiary( -//// id = diaryId, -//// content = state.content, -//// types = diaryTypes, -//// emotionLevel = emotionLevel -//// ) -// } else { -// // 생성 모드: addDiary 호출 -//// diaryUseCases.addDiary( -//// content = state.content, -//// types = diaryTypes, -//// emotionLevel = emotionLevel, -//// createdAt = _selectedDate.value // savedStateHandle로 받은 선택된 날짜 -//// ) -// } -// -// // 저장 결과 전달 -// result.onSuccess { -// _saveSuccess.emit(true) // 저장 성공 -// }.onFailure { -// _saveSuccess.emit(false) // 저장 실패 -// } -// } -// } + private fun saveDiary() { + viewModelScope.launch { + val state = _contentState.value + + // 수정 모드 vs 생성 모드 분기 + val result = if (isEditMode) { + // 수정 모드: updateDiary 호출 + val diaryTypes = DiaryEntityMapper.uiDisplayTypesToDomain(state.selectedTypes) + val emotionLevel = if (state.selectedTypes.contains("감정")) { + state.selectedEmotionLevel + } else { + null + } + + diaryUseCases.updateDiary( + diaryId = diaryId, + types = diaryTypes, + emotionLevel = emotionLevel, + content = state.content + ) + } else { + // 생성 모드: UI 상태를 Diary 엔티티로 변환 + val newDiary = DiaryEntityMapper.createNewDiary( + selectedTypes = state.selectedTypes, + content = state.content, + emotionLevel = state.selectedEmotionLevel, + dateTime = _selectedDate.value + ) + + // addDiary 호출 + diaryUseCases.addDiary(newDiary) + } + + // 저장 결과 전달 + result.onSuccess { + _saveSuccess.emit(true) // 저장 성공 + }.onFailure { + _saveSuccess.emit(false) // 저장 실패 + } + } + } /** * 저장 버튼 활성화 조건 체크 From 88061404e81647d96755d71fd7392f2c5720a6b4 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Sun, 8 Feb 2026 23:42:48 +0900 Subject: [PATCH 13/30] =?UTF-8?q?fix:=20=EC=9D=BC=EA=B8=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=B0=BD=EC=9D=B4=20=ED=95=AD=EC=83=81=20=EC=98=A4?= =?UTF-8?q?=EB=8A=98=20=EB=82=A0=EC=A7=9C=EB=A1=9C=EB=A7=8C=20=EB=82=98?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/diary/mapper/DiaryMapper.kt | 2 + .../app/ui/diary/mapper/DiaryEntityMapper.kt | 11 ++-- .../app/ui/diary/view/DiaryWriteFragment.kt | 4 +- .../ui/diary/viewmodel/DiaryWriteViewModel.kt | 66 ++++++++++--------- .../egobook/app/ui/util/DateTimeExtensions.kt | 10 +++ 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index 25542562..5bc11ae6 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -90,6 +90,8 @@ object DiaryMapper { * Diary → DiaryCreateRequest * writtenAt 시각을 기준으로 생성 요청 변환 */ + + //문제! -> date 종속 날짜는 버려지고, writtenAt 작성 시간만 dateTime으로 전송됨. fun Diary.toDiaryCreateRequest(): DiaryCreateRequest { return DiaryCreateRequest( type = types.map { it.value }, diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index f7234cff..5f8b475d 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -61,14 +61,15 @@ object DiaryEntityMapper { * @param selectedTypes UI displayType Set (예: ["감정", "고민"]) * @param content 일기 내용 * @param emotionLevel 감정 레벨 (1~5) - * @param dateTime 선택된 날짜+시간 * @return 새로 생성할 Diary 엔티티 (diaryId와 createdAt는 임시값) */ + //dateTime 하나로 date, writtenAt, createdAt을 모두 설정함 -> 문제점 발생. fun createNewDiary( selectedTypes: Set, content: String, emotionLevel: Int?, - dateTime: LocalDateTime + date: LocalDate, //선택된 날짜 + writtenAt: LocalDateTime // 실제 작성 시간 ): Diary { // UI displayType을 Domain DiaryType으로 변환 val diaryTypes = uiDisplayTypesToDomain(selectedTypes) @@ -82,12 +83,12 @@ object DiaryEntityMapper { return Diary( diaryId = 0L, // 새 일기는 임시 ID (서버가 생성), 임시 삽입. - date = dateTime.toLocalDate(), - writtenAt = dateTime, + date = date, + writtenAt = writtenAt, types = diaryTypes, emotionLevel = finalEmotionLevel, content = content, - createdAt = dateTime // 서버가 실제 값으로 대체. 임시 삽입 + createdAt = writtenAt // 서버가 실제 값으로 대체. 임시 삽입 ) } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt index b5ef9656..37bb53f9 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt @@ -157,9 +157,9 @@ class DiaryWriteFragment : Fragment() { private fun observeSelectedDate() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.selectedDate.collectLatest { date -> + viewModel.selectedDate.collectLatest { selectedDate -> // 날짜를 "2025년 12월 25일" 형식으로 표시 - binding.tvYear.text = "${date.toYearString()}년 ${date.toMonthString()}월 ${date.toDayOfMonthString()}일" + binding.tvYear.text = "${selectedDate.toYearString()}년 ${selectedDate.toMonthString()}월 ${selectedDate.toDayOfMonthString()}일" // 현재 시간을 "2025.12.25 17:32" 형식으로 표시 binding.tvInputTime.text = LocalDateTime.now().toDateTimeString() diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index 27dbf139..7b4b84e0 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject @@ -21,7 +22,7 @@ class DiaryWriteViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle ) : ViewModel() { - private val _selectedDate = MutableStateFlow(LocalDateTime.now()) + private val _selectedDate = MutableStateFlow(LocalDate.now()) val selectedDate = _selectedDate.asStateFlow() private val _contentState = MutableStateFlow(ContentState()) @@ -38,19 +39,19 @@ class DiaryWriteViewModel @Inject constructor( init { setupDate() if (isEditMode) { - loadDiaryForEdit() + //loadDiaryForEdit() } } fun setupDate() { - // Navigation argument에서 날짜 받기 + // Navigation argument에서 날짜 받기(DiaryFragment에서 넘어옴) val dateString: String? = savedStateHandle.get("selectedDate") if (dateString != null) { try { - _selectedDate.value = LocalDateTime.parse(dateString) + _selectedDate.value = LocalDate.parse(dateString) } catch (e: Exception) { // 파싱 실패 시 현재 날짜 사용 - _selectedDate.value = LocalDateTime.now() + _selectedDate.value = LocalDate.now() } } } @@ -58,31 +59,31 @@ class DiaryWriteViewModel @Inject constructor( /** * 수정 모드: 기존 일기 데이터 로드 */ - private fun loadDiaryForEdit() { - viewModelScope.launch { - diaryUseCases.getDiary(diaryId) - .onSuccess { diary -> - if (diary != null) { - // 기존 일기 데이터로 UI 상태 업데이트 - _selectedDate.value = diary.createdAt - - // Domain DiaryType을 UI displayType으로 변환 - val displayTypes = diary.types.map { it.displayType }.toSet() - - _contentState.value = _contentState.value.copy( - content = diary.content, - selectedTypes = displayTypes, - selectedEmotionLevel = diary.emotionLevel ?: 3, - charCount = diary.content.length, - isSaveButtonEnabled = true - ) - } - } - .onFailure { - // 로드 실패 시 에러 처리 (필요시 Toast 등으로 알림) - } - } - } +// private fun loadDiaryForEdit() { +// viewModelScope.launch { +// diaryUseCases.getDiary(diaryId) +// .onSuccess { diary -> +// if (diary != null) { +// // 기존 일기 데이터로 UI 상태 업데이트 +// _selectedDate.value = diary.createdAt +// +// // Domain DiaryType을 UI displayType으로 변환 +// val displayTypes = diary.types.map { it.displayType }.toSet() +// +// _contentState.value = _contentState.value.copy( +// content = diary.content, +// selectedTypes = displayTypes, +// selectedEmotionLevel = diary.emotionLevel ?: 3, +// charCount = diary.content.length, +// isSaveButtonEnabled = true +// ) +// } +// } +// .onFailure { +// // 로드 실패 시 에러 처리 (필요시 Toast 등으로 알림) +// } +// } +// } fun onEvent(event: ContentEvent) { when (event) { @@ -160,11 +161,14 @@ class DiaryWriteViewModel @Inject constructor( ) } else { // 생성 모드: UI 상태를 Diary 엔티티로 변환 + val now = LocalDateTime.now() + val newDiary = DiaryEntityMapper.createNewDiary( selectedTypes = state.selectedTypes, content = state.content, emotionLevel = state.selectedEmotionLevel, - dateTime = _selectedDate.value + date = _selectedDate.value, // 선택된 날짜의 일기로 + writtenAt = now // 실제 현재 시간으로 ) // addDiary 호출 diff --git a/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt b/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt index 3e145002..0f3489ad 100644 --- a/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt +++ b/app/src/main/java/com/egobook/app/ui/util/DateTimeExtensions.kt @@ -1,5 +1,6 @@ package com.egobook.app.ui.util +import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -97,3 +98,12 @@ fun LocalDateTime.toMonthString(): String = fun LocalDateTime.toDayOfMonthString(): String = this.format(DAY_OF_MONTH_FORMATTER) +fun LocalDate.toYearString(): String = + this.format(YEAR_FORMATTER) + +fun LocalDate.toMonthString(): String = + this.format(MONTH_FORMATTER) + +fun LocalDate.toDayOfMonthString(): String = + this.format(DAY_OF_MONTH_FORMATTER) + From df54e0068e9230acc409bc861f8096bfa324950e Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 00:28:02 +0900 Subject: [PATCH 14/30] =?UTF-8?q?refactor:=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20use?= =?UTF-8?q?case=EA=B9=8C=EC=A7=80=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/egobook/app/data/api/DiaryApiService.kt | 4 ++-- .../app/data/repository/diary/DiaryRepositoryImpl.kt | 8 +++++++- .../app/domain/usecase/diaryusecase/DiaryUseCases.kt | 2 +- .../com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt | 5 +++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt index b5458c7a..94e4485c 100644 --- a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -16,7 +16,7 @@ import retrofit2.http.Query interface DiaryApiService { - //일기 목록 불러오기 + //일기 목록 불러오기 - 완료 @GET("/diaries") suspend fun getDiaries( @Query("date") date: String, @@ -31,7 +31,7 @@ interface DiaryApiService { @Path("diaryId") diaryId: Long ): ApiResponse - //일기 추가 + //일기 추가 - 완료 @POST("/diaries") suspend fun addDiary( @Body request: DiaryCreateRequest diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 30750d13..664ecdea 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -12,6 +12,7 @@ import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryCreateRequest +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime @@ -34,7 +35,12 @@ class DiaryRepositoryImpl @Inject constructor( } override suspend fun getDiaryById(diaryId: Long): Result { - TODO("Not yet implemented") + return safeApiCall( + apiCall = { + apiService.getDiary(diaryId) + }, + transform = {it.toDiaryEntity()} + ) } override suspend fun addDiary(diary: Diary): Result { diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 35a6c289..bceb3c3f 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -32,7 +32,7 @@ class GetDiaries @Inject constructor( class GetDiary @Inject constructor( private val repository: DiaryRepository ) { - suspend operator fun invoke(id: Long): Result { + suspend operator fun invoke(id: Long): Result { return repository.getDiaryById(id) } } diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index 5f8b475d..852ea1cf 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -27,6 +27,11 @@ object DiaryEntityMapper { return diaryType.displayType } + /** + * Domain Diary -> DiaryCheckFragment에 표시될 값 + */ + + // ========== UI -> Domain Entity ========== /** From 37efc06bf0dfad374a573968edaf08bf1288f3b2 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 01:00:53 +0900 Subject: [PATCH 15/30] =?UTF-8?q?refactor:=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20api?= =?UTF-8?q?=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/diary/mapper/DiaryEntityMapper.kt | 2 +- .../app/ui/diary/view/DiaryCheckFragment.kt | 51 +++++++++++-------- .../ui/diary/viewmodel/DiaryCheckViewModel.kt | 19 ++++--- .../main/res/layout/fragment_diary_check.xml | 22 ++++++-- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index 852ea1cf..48d44dd0 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -28,7 +28,7 @@ object DiaryEntityMapper { } /** - * Domain Diary -> DiaryCheckFragment에 표시될 값 + * Domain Diary -> DiaryCheckFragment에 표시될 값..? 필요하면 정의하는 게 좋을 것 같은데.. */ diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt index 1de817f4..e5a43df1 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt @@ -23,6 +23,7 @@ import com.egobook.app.ui.util.toDayOfMonthString import com.egobook.app.ui.util.toMonthString import com.egobook.app.ui.util.toYearString import com.egobook.app.ui.diary.viewmodel.DiaryCheckViewModel +import com.egobook.app.util.UiState import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -63,18 +64,8 @@ class DiaryCheckFragment : Fragment() { findNavController().popBackStack() } btnModify.setOnClickListener { - // 수정하기 버튼 클릭 시 DiaryWriteFragment로 이동 - val currentDiary = viewModel.diary.value - if (currentDiary != null) { - val action = DiaryCheckFragmentDirections - .actionDiaryCheckFragmentToDiaryWriteFragment( - selectedDate = currentDiary.createdAt.toString(), - diaryId = currentDiary.diaryId - ) - findNavController().navigate(action) - } else { - Toast.makeText(requireContext(), "일기 정보를 불러올 수 없습니다.", Toast.LENGTH_SHORT).show() - } + // TODO: 수정 기능은 나중에 구현 + Toast.makeText(requireContext(), "수정 기능은 준비 중입니다.", Toast.LENGTH_SHORT).show() } btnDelete.setOnClickListener { applyScreenBlur(BlurLevel.BASE) @@ -98,13 +89,28 @@ class DiaryCheckFragment : Fragment() { private fun observeDiary() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.diary.collectLatest { diary -> - if (diary != null) { - // diary 객체가 null이 아닐 때 UI를 업데이트합니다. - updateUi(diary) - } else { - // diary가 null이면 (데이터 로딩 실패 등) 사용자에게 알립니다. - Toast.makeText(requireContext(), "일기를 불러오는데 실패했습니다.", Toast.LENGTH_SHORT).show() + viewModel.diaryState.collectLatest { state -> + when (state) { + is UiState.Idle -> { + binding.progressBar.visibility = View.GONE + binding.ivEmotion.visibility = View.GONE + } + is UiState.Loading -> { + binding.progressBar.visibility = View.VISIBLE + binding.ivEmotion.visibility = View.GONE + } + is UiState.Success -> { + binding.progressBar.visibility = View.GONE + updateUi(state.data) + } + is UiState.Failure -> { + binding.progressBar.visibility = View.GONE + binding.ivEmotion.visibility = View.GONE + val message = state.message ?: "알 수 없는 오류가 발생했습니다." + Toast.makeText(requireContext(), + "일기를 불러오는데 실패했습니다: $message", + Toast.LENGTH_SHORT).show() + } } } } @@ -115,14 +121,15 @@ class DiaryCheckFragment : Fragment() { private fun updateUi(diary: Diary) { binding.tvDiaryContent.text = diary.content binding.tvWrittenTime.text = diary.writtenAt.toDateTimeString() - binding.tvDate.text = "${diary.createdAt.toYearString()}년 ${diary.createdAt.toMonthString()}월 ${diary.createdAt.toDayOfMonthString()}일" + binding.tvDate.text = "${diary.date.toYearString()}년 ${diary.date.toMonthString()}월 ${diary.date.toDayOfMonthString()}일" - // 감정 레벨이 있으면 이미지 표시, 없으면 null + // 감정 레벨이 있으면 이미지 표시, 없으면 숨김 if (diary.emotionLevel != null) { + binding.ivEmotion.visibility = View.VISIBLE val emotionImageRes = getEmotionImageRes(diary.emotionLevel) binding.ivEmotion.setImageResource(emotionImageRes) } else { - binding.ivEmotion.setImageDrawable(null) // 기본 이미지 + binding.ivEmotion.visibility = View.GONE } // 일기 타입 표시 (selector를 통해 선택된 타입만 하이라이트) diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt index 56b89343..0b1e9c00 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryCheckViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.model.diary.entity.Diary import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases +import com.egobook.app.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -14,11 +15,11 @@ import javax.inject.Inject @HiltViewModel class DiaryCheckViewModel @Inject constructor( private val diaryUseCases: DiaryUseCases, - private val savedStateHandle: SavedStateHandle //다 + private val savedStateHandle: SavedStateHandle ) : ViewModel() { - private val _diary = MutableStateFlow(null) - val diary = _diary.asStateFlow() + private val _diaryState = MutableStateFlow>(UiState.Loading) + val diaryState = _diaryState.asStateFlow() private val _deleteSuccess = MutableStateFlow(null) val deleteSuccess = _deleteSuccess.asStateFlow() @@ -28,19 +29,21 @@ class DiaryCheckViewModel @Inject constructor( init { if (diaryId != -1L) { getDiary(diaryId) + } else { + _diaryState.value = UiState.Failure("잘못된 일기 ID입니다.") } } private fun getDiary(id: Long) { viewModelScope.launch { + _diaryState.value = UiState.Loading + diaryUseCases.getDiary(id) .onSuccess { fetchedDiary -> - // _diary StateFlow의 값을 직접 업데이트. - _diary.value = fetchedDiary + _diaryState.value = UiState.Success(fetchedDiary) } - .onFailure { - // 실패 시, 크래시를 방지하고 상태를 null로 유지. - _diary.value = null + .onFailure { exception -> + _diaryState.value = UiState.Failure(exception.message) } } } diff --git a/app/src/main/res/layout/fragment_diary_check.xml b/app/src/main/res/layout/fragment_diary_check.xml index c34e7c6e..4bd3d2dd 100644 --- a/app/src/main/res/layout/fragment_diary_check.xml +++ b/app/src/main/res/layout/fragment_diary_check.xml @@ -48,7 +48,7 @@ android:layout_width="0dp" android:layout_height="30dp" android:layout_weight="1" - android:text="2025년 12월 25일" + android:text="" android:textSize="20sp" android:fontFamily="@font/arita_semibold" android:gravity="center_vertical"/> @@ -58,7 +58,9 @@ android:layout_width="32dp" android:layout_height="32dp" android:layout_marginEnd="16dp" - android:src="@drawable/img_emotion_neutral"/> + android:visibility="gone" + tools:src="@drawable/img_emotion_neutral" + tools:visibility="visible"/> @@ -178,7 +180,7 @@ android:id="@+id/tv_diary_content" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="일기일기일기 일기일기일기 일기일기일기 일기일기일기 일기일기일기 일기일기일기 일기일기일기 일기일기일기 일기일기일기" + android:text="" android:textColor="@color/cos_black" android:textSize="14sp" android:fontFamily="@font/arita_medium" @@ -191,7 +193,7 @@ android:id="@+id/tv_written_time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="2025.12.25 17:32" + android:text="" android:textColor="@color/grey" android:textSize="12sp" android:fontFamily="@font/arita_semibold" @@ -230,4 +232,16 @@ app:layout_constraintEnd_toEndOf="@id/gd_right" android:layout_marginBottom="16dp"/> + + \ No newline at end of file From 3eeeb52974eae32f1cbdc5b7dda6217407a5852a Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 02:15:21 +0900 Subject: [PATCH 16/30] =?UTF-8?q?refactor:=20UTC=EC=99=80=20KST=EC=9D=98?= =?UTF-8?q?=20=EC=B0=A8=EC=9D=B4=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EB=B6=80=EB=B6=84=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/diary/request/DiaryCreateRequest.kt | 4 ++-- .../domain/model/diary/mapper/DiaryMapper.kt | 24 +++++++++++++++---- .../app/ui/diary/mapper/DiaryEntityMapper.kt | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt index 4568d8e9..1de8afca 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryCreateRequest.kt @@ -11,6 +11,6 @@ data class DiaryCreateRequest( val emotionLevel: Int?, @SerialName("content") val content: String, - @SerialName("dateTime") - val dateTime: String //LocalDateTime의 String형식 이어야 함. + @SerialName("date") + val date: String //LocalDate의 String형식 이어야 함. ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index 5bc11ae6..da72ed3e 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -10,14 +10,17 @@ import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiaryList import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType +import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime - +import java.time.ZoneId /** * Data Layer ↔ Domain Layer 변환 Mapper */ object DiaryMapper { + + private val KST: ZoneId = ZoneId.of("Asia/Seoul") // ========== Response → Domain Entity ========== @@ -38,13 +41,26 @@ object DiaryMapper { return Diary( diaryId = diaryId, date = LocalDate.parse(date), - writtenAt = LocalDateTime.parse(writtenAt), + writtenAt = parseUtcToKst(writtenAt), types = type.map { DiaryType.from(it) }.toSet(), emotionLevel = emotionLevel, content = content, createdAt = LocalDateTime.parse(createdAt) ) } + + /** + * UTC 시간 문자열을 KST LocalDateTime으로 변환 + * @param utcString ISO 8601 UTC 형식 (예: "2026-02-08T16:03:07.148Z") + * @return KST LocalDateTime + */ + private fun parseUtcToKst(utcString: String): LocalDateTime { + val withZ = if (utcString.endsWith("Z")) utcString else "${utcString}Z" + return Instant.parse(withZ) + .atZone(KST) + .toLocalDateTime() + } + /** * DiarySlice → DiaryList @@ -88,16 +104,14 @@ object DiaryMapper { /** * Diary → DiaryCreateRequest - * writtenAt 시각을 기준으로 생성 요청 변환 */ - //문제! -> date 종속 날짜는 버려지고, writtenAt 작성 시간만 dateTime으로 전송됨. fun Diary.toDiaryCreateRequest(): DiaryCreateRequest { return DiaryCreateRequest( type = types.map { it.value }, emotionLevel = emotionLevel, content = content, - dateTime = writtenAt.toString() + date = date.toString() ) } diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index 48d44dd0..b50ee1c0 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -89,7 +89,7 @@ object DiaryEntityMapper { return Diary( diaryId = 0L, // 새 일기는 임시 ID (서버가 생성), 임시 삽입. date = date, - writtenAt = writtenAt, + writtenAt = writtenAt, // 서버가 실제 값으로 대체. 임시 삽입 types = diaryTypes, emotionLevel = finalEmotionLevel, content = content, From 6c9471818482babd5d741fc5506c7120544eeca3 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 15:28:11 +0900 Subject: [PATCH 17/30] =?UTF-8?q?refactor:=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/repository/diary/DiaryRepositoryImpl.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 664ecdea..855d0643 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -64,7 +64,12 @@ class DiaryRepositoryImpl @Inject constructor( } override suspend fun deleteDiaryById(diaryId: Long): Result { - TODO("Not yet implemented") + return safeApiCall ( + apiCall = { + apiService.deleteDiary(diaryId) + }, + transform = { Unit } + ) } } \ No newline at end of file From e58dd624e3557613600e981c8cc695cfa0c46a52 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 15:46:03 +0900 Subject: [PATCH 18/30] =?UTF-8?q?refactor:=20=EB=B7=B0=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/diary/mapper/DiaryEntityMapper.kt | 2 +- .../app/ui/diary/view/DiaryCheckFragment.kt | 14 ++++- .../ui/diary/viewmodel/DiaryWriteViewModel.kt | 52 +++++++++---------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index b50ee1c0..63fbc3f3 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -62,7 +62,7 @@ object DiaryEntityMapper { } /** - * UI 상태를 Domain Diary 엔티티로 변환 (새 일기 생성용) + * UI 상태를 Domain Diary 엔티티로 변환 (새 일기 생성용, 일기 수정에도 사용가능) * @param selectedTypes UI displayType Set (예: ["감정", "고민"]) * @param content 일기 내용 * @param emotionLevel 감정 레벨 (1~5) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt index e5a43df1..68566639 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt @@ -64,8 +64,18 @@ class DiaryCheckFragment : Fragment() { findNavController().popBackStack() } btnModify.setOnClickListener { - // TODO: 수정 기능은 나중에 구현 - Toast.makeText(requireContext(), "수정 기능은 준비 중입니다.", Toast.LENGTH_SHORT).show() + val diarystate = viewModel.diaryState.value + if(diarystate is UiState.Success) { + val action = DiaryCheckFragmentDirections + .actionDiaryCheckFragmentToDiaryWriteFragment( + selectedDate = currentDiary.date.toString(), + diaryId = currentDiary.diaryId + ) + findNavController().navigate(action) + } else { + Toast.makeText(requireContext(), "일기 정보를 불러올 수 없습니다.", Toast.LENGTH_SHORT).show() + } + //Toast.makeText(requireContext(), "수정 기능은 준비 중입니다.", Toast.LENGTH_SHORT).show() } btnDelete.setOnClickListener { applyScreenBlur(BlurLevel.BASE) diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index 7b4b84e0..a981a108 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -39,7 +39,7 @@ class DiaryWriteViewModel @Inject constructor( init { setupDate() if (isEditMode) { - //loadDiaryForEdit() + loadDiaryForEdit() } } @@ -59,31 +59,31 @@ class DiaryWriteViewModel @Inject constructor( /** * 수정 모드: 기존 일기 데이터 로드 */ -// private fun loadDiaryForEdit() { -// viewModelScope.launch { -// diaryUseCases.getDiary(diaryId) -// .onSuccess { diary -> -// if (diary != null) { -// // 기존 일기 데이터로 UI 상태 업데이트 -// _selectedDate.value = diary.createdAt -// -// // Domain DiaryType을 UI displayType으로 변환 -// val displayTypes = diary.types.map { it.displayType }.toSet() -// -// _contentState.value = _contentState.value.copy( -// content = diary.content, -// selectedTypes = displayTypes, -// selectedEmotionLevel = diary.emotionLevel ?: 3, -// charCount = diary.content.length, -// isSaveButtonEnabled = true -// ) -// } -// } -// .onFailure { -// // 로드 실패 시 에러 처리 (필요시 Toast 등으로 알림) -// } -// } -// } + private fun loadDiaryForEdit() { + viewModelScope.launch { + diaryUseCases.getDiary(diaryId) + .onSuccess { diary -> + if (diary != null) { + // 기존 일기 데이터로 UI 상태 업데이트 + _selectedDate.value = diary.date + + // Domain DiaryType을 UI displayType으로 변환 + val displayTypes = diary.types.map { it.displayType }.toSet() + + _contentState.value = _contentState.value.copy( + content = diary.content, + selectedTypes = displayTypes, + selectedEmotionLevel = diary.emotionLevel ?: 3, + charCount = diary.content.length, + isSaveButtonEnabled = true + ) + } + } + .onFailure { + // 로드 실패 시 에러 처리 (필요시 Toast 등으로 알림) + } + } + } fun onEvent(event: ContentEvent) { when (event) { From 7a475fdfc93794d84a6a119bc7b9a35834ad49fc Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 15:49:41 +0900 Subject: [PATCH 19/30] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EB=A9=B4=20=EC=9D=BC=EA=B8=B0=EC=93=B0=EA=B8=B0=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/ui/diary/view/DiaryCheckFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt index 68566639..e40534ce 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryCheckFragment.kt @@ -64,8 +64,9 @@ class DiaryCheckFragment : Fragment() { findNavController().popBackStack() } btnModify.setOnClickListener { - val diarystate = viewModel.diaryState.value - if(diarystate is UiState.Success) { + val diaryState = viewModel.diaryState.value + if(diaryState is UiState.Success) { + val currentDiary = diaryState.data val action = DiaryCheckFragmentDirections .actionDiaryCheckFragmentToDiaryWriteFragment( selectedDate = currentDiary.date.toString(), @@ -75,7 +76,6 @@ class DiaryCheckFragment : Fragment() { } else { Toast.makeText(requireContext(), "일기 정보를 불러올 수 없습니다.", Toast.LENGTH_SHORT).show() } - //Toast.makeText(requireContext(), "수정 기능은 준비 중입니다.", Toast.LENGTH_SHORT).show() } btnDelete.setOnClickListener { applyScreenBlur(BlurLevel.BASE) From 9b80506eee4e464554ca5e1ce8e9da0019ac8eb7 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 16:20:57 +0900 Subject: [PATCH 20/30] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20api=20=EC=97=B0=EB=8F=99=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=A7=A4=ED=8D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/DiaryApiService.kt | 11 ++++--- .../model/diary/request/DiaryUpdateRequest.kt | 14 +++++++++ .../domain/model/diary/mapper/DiaryMapper.kt | 12 ++++++++ .../app/ui/diary/mapper/DiaryEntityMapper.kt | 30 +++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/diary/request/DiaryUpdateRequest.kt diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt index 94e4485c..33c16b8a 100644 --- a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -2,6 +2,7 @@ package com.egobook.app.data.api import com.egobook.app.data.model.ApiResponse import com.egobook.app.data.model.diary.request.DiaryCreateRequest +import com.egobook.app.data.model.diary.request.DiaryUpdateRequest import com.egobook.app.data.model.diary.response.DiariesResponse import com.egobook.app.data.model.diary.response.DiaryCreateResponse import com.egobook.app.data.model.diary.response.DiaryDeleteResponse @@ -16,7 +17,7 @@ import retrofit2.http.Query interface DiaryApiService { - //일기 목록 불러오기 - 완료 + //일기 목록 불러오기 @GET("/diaries") suspend fun getDiaries( @Query("date") date: String, @@ -31,7 +32,7 @@ interface DiaryApiService { @Path("diaryId") diaryId: Long ): ApiResponse - //일기 추가 - 완료 + //일기 추가 @POST("/diaries") suspend fun addDiary( @Body request: DiaryCreateRequest @@ -47,9 +48,7 @@ interface DiaryApiService { @PATCH("/diaries/{diaryId}") suspend fun updateDiary( @Path("diaryId") diaryId: Long, - @Body request: DiaryCreateRequest - ): ApiResponse - - + @Body request: DiaryUpdateRequest + ): ApiResponse } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryUpdateRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryUpdateRequest.kt new file mode 100644 index 00000000..33375658 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryUpdateRequest.kt @@ -0,0 +1,14 @@ +package com.egobook.app.data.model.diary.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DiaryUpdateRequest ( + @SerialName("type") + val type: List, + @SerialName("emotionLevel") + val emotionLevel: Int?, + @SerialName("content") + val content: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index da72ed3e..a4a4f84c 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -1,6 +1,7 @@ package com.egobook.app.domain.model.diary.mapper import com.egobook.app.data.model.diary.request.DiaryCreateRequest +import com.egobook.app.data.model.diary.request.DiaryUpdateRequest import com.egobook.app.data.model.diary.response.DiariesResponse import com.egobook.app.data.model.diary.response.DiaryEntryResponse import com.egobook.app.data.model.diary.response.DiarySlice @@ -115,4 +116,15 @@ object DiaryMapper { ) } + /** + * Diary → DiaryUpdateRequest + */ + fun Diary.toDiaryUpdateRequest(): DiaryUpdateRequest { + return DiaryUpdateRequest( + type = types.map { it.value }, + emotionLevel = emotionLevel, + content = content + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt index 63fbc3f3..63e7be6a 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/mapper/DiaryEntityMapper.kt @@ -96,4 +96,34 @@ object DiaryEntityMapper { createdAt = writtenAt // 서버가 실제 값으로 대체. 임시 삽입 ) } + + fun createUpdatedDiary( + diaryId: Long, + selectedTypes: Set, + content: String, + emotionLevel: Int?, + writtenAt: LocalDateTime + ): Diary { + // UI displayType을 Domain DiaryType으로 변환 + val diaryTypes = uiDisplayTypesToDomain(selectedTypes) + + // 감정 타입이 선택되지 않았으면 emotionLevel은 null + val finalEmotionLevel = if (selectedTypes.contains("감정")) { + emotionLevel + } else { + null + } + + return Diary( + diaryId = diaryId, + types = diaryTypes, + emotionLevel = finalEmotionLevel, + content = content, + + //어차피 DiaryMapper에서 걸러지는 값들. 그냥 임시 삽입 + createdAt = writtenAt, + date = writtenAt.toLocalDate(), + writtenAt = writtenAt + ) + } } From 2be7c5afeb16f78d1733bfec39f5a783665db2cb Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 16:27:38 +0900 Subject: [PATCH 21/30] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=A0=EC=8A=A4=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/diary/DiaryRepositoryImpl.kt | 15 +++++++++++---- .../domain/repository/diary/DiaryRepository.kt | 4 +--- .../domain/usecase/diaryusecase/DiaryUseCases.kt | 8 +++----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 855d0643..9122d9e8 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -13,6 +13,7 @@ import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryCreateRequest import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryUpdateRequest import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow import java.time.LocalDateTime @@ -56,11 +57,17 @@ class DiaryRepositoryImpl @Inject constructor( override suspend fun updateDiary( diaryId: Long, - types: Set, - emotionLevel: Int?, - content: String + diary: Diary ): Result { - TODO("Not yet implemented") + return safeApiCall( + apiCall = { + apiService.updateDiary( + diaryId = diaryId, + request = diary.toDiaryUpdateRequest() + ) + }, + transform = { Unit } + ) } override suspend fun deleteDiaryById(diaryId: Long): Result { diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt index ea68496f..c9eb24e7 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -30,9 +30,7 @@ interface DiaryRepository { */ suspend fun updateDiary( diaryId: Long, - types: Set, - emotionLevel: Int?, - content: String + diary: Diary ): Result /** diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index bceb3c3f..0190470c 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -50,11 +50,9 @@ class UpdateDiary @Inject constructor( ) { suspend operator fun invoke( diaryId: Long, - types: Set, - emotionLevel: Int?, - content: String - ): Result { - return TODO() + diary: Diary + ): Result { + return repository.updateDiary(diaryId, diary) } } From e92fb76c43818aa2a237ba794056b3d690a3af44 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 16:35:21 +0900 Subject: [PATCH 22/30] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/diary/viewmodel/DiaryWriteViewModel.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt index a981a108..b068a9b2 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiaryWriteViewModel.kt @@ -152,12 +152,19 @@ class DiaryWriteViewModel @Inject constructor( } else { null } + val now = LocalDateTime.now() + + val updatedDiary = DiaryEntityMapper.createUpdatedDiary( + diaryId = diaryId, + selectedTypes = state.selectedTypes, + content = state.content, + emotionLevel = emotionLevel, + writtenAt = now // 실제 현재 시간으로(임시 삽입) + ) diaryUseCases.updateDiary( diaryId = diaryId, - types = diaryTypes, - emotionLevel = emotionLevel, - content = state.content + diary = updatedDiary ) } else { // 생성 모드: UI 상태를 Diary 엔티티로 변환 @@ -168,7 +175,7 @@ class DiaryWriteViewModel @Inject constructor( content = state.content, emotionLevel = state.selectedEmotionLevel, date = _selectedDate.value, // 선택된 날짜의 일기로 - writtenAt = now // 실제 현재 시간으로 + writtenAt = now // 실제 현재 시간으로(임시 삽입) ) // addDiary 호출 From e9fe8e5b16a505dec7e5b64eaf8eefa830f55ef4 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 17:04:02 +0900 Subject: [PATCH 23/30] =?UTF-8?q?design:=20=EC=9D=BC=EA=B8=B0=20=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=ED=9A=9F=EC=88=98=20=EC=B4=88=EA=B3=BC=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/drawable/bg_custom_toast.xml | 6 +++ app/src/main/res/drawable/ic_xcircle.xml | 13 +++++++ app/src/main/res/layout/toast_over_write.xml | 37 +++++++++++++++++++ app/src/main/res/values/strings.xml | 3 ++ 4 files changed, 59 insertions(+) create mode 100644 app/src/main/res/drawable/bg_custom_toast.xml create mode 100644 app/src/main/res/drawable/ic_xcircle.xml create mode 100644 app/src/main/res/layout/toast_over_write.xml diff --git a/app/src/main/res/drawable/bg_custom_toast.xml b/app/src/main/res/drawable/bg_custom_toast.xml new file mode 100644 index 00000000..f1e6d2e5 --- /dev/null +++ b/app/src/main/res/drawable/bg_custom_toast.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_xcircle.xml b/app/src/main/res/drawable/ic_xcircle.xml new file mode 100644 index 00000000..8d6eeca8 --- /dev/null +++ b/app/src/main/res/drawable/ic_xcircle.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/layout/toast_over_write.xml b/app/src/main/res/layout/toast_over_write.xml new file mode 100644 index 00000000..73a16aa1 --- /dev/null +++ b/app/src/main/res/layout/toast_over_write.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ 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 dcdb0692..792f913a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,4 +26,7 @@ Not a valid username Password must be >5 characters "Login failed" + + + 하루에 최대 48번 기록 가능해요 From 56e50d04bf238da64ec0a098f9913b587407c550 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 17:43:00 +0900 Subject: [PATCH 24/30] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/diary/view/DiaryFragment.kt | 31 ++++++++++++++++++- app/src/main/res/layout/toast_over_write.xml | 1 - 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index 29b22767..45349d19 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -1,9 +1,12 @@ package com.egobook.app.ui.diary.view import android.os.Bundle + import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup + import android.widget.Toast + import android.graphics.Color import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle @@ -18,12 +21,12 @@ import com.egobook.app.ui.diary.adapter.DiaryVPAdapter import com.egobook.app.ui.diary.viewmodel.DiariesEvent import com.egobook.app.ui.diary.viewmodel.DiariesViewModel + import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlin.getValue - class DiaryFragment : Fragment() { private var _binding: FragmentDiaryBinding? = null private val binding get() = _binding!! @@ -65,6 +68,9 @@ private fun setupClickListener() { binding.apply { btnAdd.setOnClickListener { + // 커스텀 토스트 메세지 출력 테스트 + showCustomToast() + // 현재 선택된 날짜를 ISO 형식으로 변환하여 전달 val selectedDate = viewModel.state.value.selectedDate.toString() val action = DiaryFragmentDirections.actionDiaryFragmentToDiaryWriteFragment(selectedDate) @@ -177,6 +183,29 @@ } } + private fun showCustomToast() { + val snackbar = Snackbar.make(requireView(), "", Snackbar.LENGTH_LONG) + + val customView = layoutInflater.inflate(R.layout.toast_over_write, null) + + val layout = snackbar.view as Snackbar.SnackbarLayout + layout.setPadding(0, 0, 0, 0) + layout.setBackgroundColor(Color.TRANSPARENT) + + layout.addView(customView, 0) + + // BottomNav에 붙이기 + val bottomNav = requireActivity().findViewById(R.id.bottom_navigation) + snackbar.anchorView = bottomNav //스낵바의 앵커를 bottomNav로 설정 + + // BottomNav로부터 9dp 위로 + val extra = (9 * resources.displayMetrics.density) + snackbar.view.translationY = -extra + + snackbar.show() + } + + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/res/layout/toast_over_write.xml b/app/src/main/res/layout/toast_over_write.xml index 73a16aa1..d999a158 100644 --- a/app/src/main/res/layout/toast_over_write.xml +++ b/app/src/main/res/layout/toast_over_write.xml @@ -31,7 +31,6 @@ android:fontFamily="@font/arita_semibold" android:gravity="center_vertical"/> - \ No newline at end of file From c5a2498bb9e7c2c742132d8fb8c82335e5076544 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 18:40:24 +0900 Subject: [PATCH 25/30] =?UTF-8?q?feat:=20dailyCount=20=EC=BA=90=EC=8B=B1?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/diary/DiaryRepositoryImpl.kt | 52 ++++++++++++++++++- .../diary/paging/DiariesPagingSource.kt | 6 ++- .../com/egobook/app/di/RepositoryModule.kt | 9 ---- .../java/com/egobook/app/di/UseCaseModule.kt | 4 +- .../repository/diary/DiaryRepository.kt | 8 +++ .../usecase/diaryusecase/DiaryUseCases.kt | 14 +++-- .../app/ui/diary/view/DiaryFragment.kt | 31 ++++++----- .../ui/diary/viewmodel/DiariesViewModel.kt | 19 ++++++- 8 files changed, 113 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 9122d9e8..6a603feb 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -14,14 +14,23 @@ import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryCreateRequest import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryUpdateRequest +import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toRequestParams import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject +import javax.inject.Singleton +import timber.log.Timber +@Singleton class DiaryRepositoryImpl @Inject constructor( private val apiService: DiaryApiService ) : DiaryRepository { + + // 날짜별 dailyCount 캐시 + private val dailyCountCache = mutableMapOf() + override fun getDiaries( filter: DiaryFilter, size: Int @@ -31,7 +40,17 @@ class DiaryRepositoryImpl @Inject constructor( pageSize = size, enablePlaceholders = false ), - pagingSourceFactory = { DiariesPagingSource(apiService, filter) } + pagingSourceFactory = { + DiariesPagingSource( + apiService = apiService, + filter = filter, + onDailyCountReceived = { count -> + // API 응답 시 dailyCount 캐시 업데이트 + Timber.d("[DailyCount] PagingSource 캐시 업데이트: date=${filter.date}, count=$count") + dailyCountCache[filter.date] = count + } + ) + } ).flow } @@ -79,4 +98,35 @@ class DiaryRepositoryImpl @Inject constructor( ) } + override suspend fun getDailyCount(date: LocalDate): Result { + // 1. 캐시 먼저 확인 + dailyCountCache[date]?.let { + Timber.d("[DailyCount] 캐시 히트: date=$date, count=$it") + return Result.success(it) + } + + Timber.d("[DailyCount] 캐시 미스: date=$date, API 호출 시작") + + // 2. 캐시 miss면 API 호출 + val filter = DiaryFilter(date, null) + val (dateParam, typesParam) = filter.toRequestParams() + + return safeApiCall( + apiCall = { + apiService.getDiaries( + date = dateParam, + type = typesParam, + page = 1, + size = 1 // dailyCount만 필요하므로 최소 크기로 요청 + ) + }, + transform = { response -> + // API 응답 시 캐시 저장 + dailyCountCache[date] = response.dailyCount + Timber.d("[DailyCount] API 응답 캐시 저장: date=$date, count=${response.dailyCount}") + response.dailyCount + } + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt index 2a9f6624..03726c4d 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt @@ -12,7 +12,8 @@ import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiarySummary class DiariesPagingSource( private val apiService: DiaryApiService, - private val filter: DiaryFilter + private val filter: DiaryFilter, + private val onDailyCountReceived: (Int) -> Unit = {} ): PagingSource() { override fun getRefreshKey(state: PagingState): Int { @@ -33,6 +34,9 @@ class DiariesPagingSource( size = size ) + // dailyCount 캐시 업데이트 콜백 호출 + onDailyCountReceived(response.data.dailyCount) + val diarySlice = response.data.diaries diff --git a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt index ff8bd7f5..3f11fef2 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt @@ -43,15 +43,6 @@ abstract class RepositoryModule { @Singleton abstract fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository - // TODO: 백엔드 API 연동 시: - // 1. DiaryRepository 인터페이스 생성 - // 2. DiaryRepositoryImpl 구현 (ApiService 사용) - // 3. 이 바인딩을 DiaryRepositoryImpl -> DiaryRepository로 변경 - // 4. 모든 UseCase의 FakeDiaryRepository -> DiaryRepository로 변경 -// @Binds -// @Singleton -// abstract fun bindFakeDiaryRepository(impl: FakeDiaryRepositoryImpl): FakeDiaryRepository - @Binds @Singleton abstract fun bindDiaryRepository(impl: DiaryRepositoryImpl): DiaryRepository diff --git a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt index 382fecea..618dc029 100644 --- a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt +++ b/app/src/main/java/com/egobook/app/di/UseCaseModule.kt @@ -12,6 +12,7 @@ import com.egobook.app.domain.usecase.authusecase.GuestReLogin import com.egobook.app.domain.usecase.diaryusecase.AddDiary import com.egobook.app.domain.usecase.diaryusecase.DeleteDiary import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases +import com.egobook.app.domain.usecase.diaryusecase.GetDailyCount import com.egobook.app.domain.usecase.diaryusecase.GetDiaries import com.egobook.app.domain.usecase.diaryusecase.GetDiary import com.egobook.app.domain.usecase.diaryusecase.UpdateDiary @@ -35,7 +36,8 @@ object UseCaseModule { getDiary = GetDiary(repository), addDiary = AddDiary(repository), updateDiary = UpdateDiary(repository), - deleteDiary = DeleteDiary(repository) + deleteDiary = DeleteDiary(repository), + getDailyCount = GetDailyCount(repository) ) } diff --git a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt index c9eb24e7..d5807126 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/diary/DiaryRepository.kt @@ -6,6 +6,7 @@ import com.egobook.app.domain.model.diary.entity.DiaryFilter import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import java.time.LocalDateTime interface DiaryRepository { @@ -37,4 +38,11 @@ interface DiaryRepository { * 일기 삭제 */ suspend fun deleteDiaryById(diaryId: Long): Result + + /** + * 데일리 카운트 가져오기 + */ + suspend fun getDailyCount(date: LocalDate): Result + + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt index 0190470c..76589403 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/diaryusecase/DiaryUseCases.kt @@ -7,6 +7,7 @@ import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.entity.DiaryType import com.egobook.app.domain.repository.diary.DiaryRepository import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject @@ -16,7 +17,8 @@ data class DiaryUseCases @Inject constructor ( val getDiary: GetDiary, val addDiary: AddDiary, val updateDiary: UpdateDiary, - val deleteDiary: DeleteDiary + val deleteDiary: DeleteDiary, + val getDailyCount: GetDailyCount ) // 각 유스케이스들 정의 @@ -64,6 +66,10 @@ class DeleteDiary @Inject constructor( } } - - - +class GetDailyCount @Inject constructor( + private val repository: DiaryRepository +) { + suspend operator fun invoke(date: LocalDate): Result { + return repository.getDailyCount(date) + } +} diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index 45349d19..18848cb1 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -68,13 +68,20 @@ private fun setupClickListener() { binding.apply { btnAdd.setOnClickListener { - // 커스텀 토스트 메세지 출력 테스트 - showCustomToast() - - // 현재 선택된 날짜를 ISO 형식으로 변환하여 전달 - val selectedDate = viewModel.state.value.selectedDate.toString() - val action = DiaryFragmentDirections.actionDiaryFragmentToDiaryWriteFragment(selectedDate) - findNavController().navigate(action) + viewLifecycleOwner.lifecycleScope.launch { + // 캐시 기반 dailyCount 조회 (캐시 없으면 API 호출) + val dailyCount = viewModel.getDailyCountWithCache() + + if (dailyCount >= 48) { + showCustomToast() + return@launch + } + + // 48 미만일 때만 일기 작성 화면으로 이동 + val selectedDate = viewModel.state.value.selectedDate.toString() + val action = DiaryFragmentDirections.actionDiaryFragmentToDiaryWriteFragment(selectedDate) + findNavController().navigate(action) + } } btnCalender.setOnClickListener { findNavController().navigate(R.id.action_diaryFragment_to_calenderFragment) @@ -184,11 +191,11 @@ } private fun showCustomToast() { - val snackbar = Snackbar.make(requireView(), "", Snackbar.LENGTH_LONG) + val snackBar = Snackbar.make(requireView(), "", Snackbar.LENGTH_LONG) val customView = layoutInflater.inflate(R.layout.toast_over_write, null) - val layout = snackbar.view as Snackbar.SnackbarLayout + val layout = snackBar.view as Snackbar.SnackbarLayout layout.setPadding(0, 0, 0, 0) layout.setBackgroundColor(Color.TRANSPARENT) @@ -196,13 +203,13 @@ // BottomNav에 붙이기 val bottomNav = requireActivity().findViewById(R.id.bottom_navigation) - snackbar.anchorView = bottomNav //스낵바의 앵커를 bottomNav로 설정 + snackBar.anchorView = bottomNav //스낵바의 앵커를 bottomNav로 설정 // BottomNav로부터 9dp 위로 val extra = (9 * resources.displayMetrics.density) - snackbar.view.translationY = -extra + snackBar.view.translationY = -extra - snackbar.show() + snackBar.show() } diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt index 9b8429b8..603ce800 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.launch import java.time.LocalDate import javax.inject.Inject @@ -63,8 +64,21 @@ class DiariesViewModel @Inject constructor( val diariesFlow = diaryUseCases .getDiaries(filter) .cachedIn(viewModelScope) - + _state.value = state.value.copy(diaries = diariesFlow) + + // dailyCount도 함께 로드 + //loadDailyCount(selectedDate) + } + + /** + * 캐시 기반 dailyCount 조회 (캐시 없으면 API 호출) + * btnAdd 클릭 시 48 체크용으로 사용 + */ + suspend fun getDailyCountWithCache(): Int { + val currentDate = state.value.selectedDate + return diaryUseCases.getDailyCount(currentDate) + .getOrDefault(state.value.dailyCount) } // 날짜가 바뀌면 UI 표시값까지 자동 변경하는 확장함수 @@ -90,7 +104,8 @@ data class DiariesState( // 내부 로직용 val selectedDate: LocalDate = LocalDate.now(), - + val dailyCount: Int = 0, // 해당 날짜의 일기 개수 + // UI 표시용 val yearText: String = selectedDate.year.toString(), val monthText: String = selectedDate.monthValue.toString(), From 77ae9887ffecda2dc6b02ec1c7ae429a62fd7929 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 19:15:02 +0900 Subject: [PATCH 26/30] =?UTF-8?q?test:=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 8 + .../repository/diary/DiaryRepositoryImpl.kt | 10 +- .../diary/viewmodel/DiariesViewModelTest.kt | 150 ++++++++++++++++++ 3 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModelTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 49797ff9..40866d57 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,7 +113,15 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.0") // JUnit 4 지원 + testImplementation("junit:junit:4.13.2") // JUnit 4 testImplementation("org.assertj:assertj-core:3.27.6") + + // 단위 테스트를 위한 의존성 + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1") + testImplementation("io.mockk:mockk:1.13.8") + testImplementation("androidx.arch.core:core-testing:2.2.0") + androidTestImplementation(libs.androidx.junit) androidTestImplementation("org.assertj:assertj-core:3.27.6") androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt index 6a603feb..56d8900d 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/DiaryRepositoryImpl.kt @@ -23,7 +23,7 @@ import javax.inject.Inject import javax.inject.Singleton import timber.log.Timber -@Singleton +@Singleton //dailyCountCache가 계속 살아있게 하기 위해 추가 class DiaryRepositoryImpl @Inject constructor( private val apiService: DiaryApiService ) : DiaryRepository { @@ -45,10 +45,10 @@ class DiaryRepositoryImpl @Inject constructor( apiService = apiService, filter = filter, onDailyCountReceived = { count -> - // API 응답 시 dailyCount 캐시 업데이트 - Timber.d("[DailyCount] PagingSource 캐시 업데이트: date=${filter.date}, count=$count") - dailyCountCache[filter.date] = count - } + // API 응답 시 dailyCount 캐시 업데이트 + Timber.d("[DailyCount] PagingSource 캐시 업데이트: date=${filter.date}, count=$count") + dailyCountCache[filter.date] = count + } ) } ).flow diff --git a/app/src/test/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModelTest.kt b/app/src/test/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModelTest.kt new file mode 100644 index 00000000..14831407 --- /dev/null +++ b/app/src/test/java/com/egobook/app/ui/diary/viewmodel/DiariesViewModelTest.kt @@ -0,0 +1,150 @@ +package com.egobook.app.ui.diary.viewmodel + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.paging.PagingData +import com.egobook.app.domain.model.diary.entity.DiarySummary +import com.egobook.app.domain.usecase.diaryusecase.DiaryUseCases +import com.egobook.app.domain.usecase.diaryusecase.GetDailyCount +import com.egobook.app.domain.usecase.diaryusecase.GetDiaries +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.time.LocalDate + +@ExperimentalCoroutinesApi +class DiariesViewModelTest { + + // JUnit 테스트에서 LiveData/StateFlow를 동기적으로 처리하기 위한 규칙 + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private lateinit var viewModel: DiariesViewModel + private lateinit var diaryUseCases: DiaryUseCases + private lateinit var getDailyCount: GetDailyCount + private lateinit var getDiaries: GetDiaries + private val testDispatcher = StandardTestDispatcher() + + @Before + fun setup() { + Dispatchers.setMain(testDispatcher) + + // UseCase 모킹 + getDailyCount = mockk() + getDiaries = mockk() + + // GetDiaries 모킹 설정 + every { getDiaries.invoke(any()) } returns flowOf(PagingData.empty()) + + diaryUseCases = DiaryUseCases( + getDiaries = getDiaries, + getDiary = mockk(), + addDiary = mockk(), + updateDiary = mockk(), + deleteDiary = mockk(), + getDailyCount = getDailyCount + ) + + viewModel = DiariesViewModel(diaryUseCases) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `dailyCount가 48 미만이면 해당 값을 반환한다`() = runTest { + // Given + val expectedCount = 47 + coEvery { getDailyCount.invoke(any()) } returns Result.success(expectedCount) + + // When + val result = viewModel.getDailyCountWithCache() + + // Then + assertEquals(expectedCount, result) + assertTrue(result < 48) + } + + @Test + fun `dailyCount가 48이면 48을 반환하고 스낵바 표시 조건이 충족된다`() = runTest { + // Given + val expectedCount = 48 + coEvery { getDailyCount.invoke(any()) } returns Result.success(expectedCount) + + // When + val result = viewModel.getDailyCountWithCache() + + // Then + assertEquals(expectedCount, result) + assertTrue(result >= 48) // 스낵바 표시 조건 + } + + @Test + fun `dailyCount가 48 초과면 해당 값을 반환하고 스낵바 표시 조건이 충족된다`() = runTest { + // Given + val expectedCount = 50 + coEvery { getDailyCount.invoke(any()) } returns Result.success(expectedCount) + + // When + val result = viewModel.getDailyCountWithCache() + + // Then + assertEquals(expectedCount, result) + assertTrue(result >= 48) // 스낵바 표시 조건 + } + + @Test + fun `dailyCount가 0이면 해당 값을 반환한다`() = runTest { + // Given + val expectedCount = 0 + coEvery { getDailyCount.invoke(any()) } returns Result.success(expectedCount) + + // When + val result = viewModel.getDailyCountWithCache() + + // Then + assertEquals(expectedCount, result) + assertTrue(result < 48) + } + + @Test + fun `API 호출 실패 시 현재 state의 dailyCount를 반환한다`() = runTest { + // Given: API 호출 실패, state에는 이미 0이 있음 (init에서 설정) + coEvery { getDailyCount.invoke(any()) } returns Result.failure(Exception("Network error")) + + // When + val result = viewModel.getDailyCountWithCache() + + // Then: getOrDefault로 인해 state의 dailyCount(0) 반환 + assertEquals(0, result) + } + + @Test + fun `날짜 변경 시 새로운 날짜로 dailyCount를 조회한다`() = runTest { + // Given + val expectedCount = 5 + coEvery { getDailyCount.invoke(any()) } returns Result.success(expectedCount) + + // When: 날짜 변경 + viewModel.onEvent(DiariesEvent.ChangeDate(2026, 2, 10)) + testDispatcher.scheduler.advanceUntilIdle() + + // Then + val result = viewModel.getDailyCountWithCache() + assertEquals(expectedCount, result) + } +} From ed069f240ef2e99fef28aa152b8ea23b83b1ab5d Mon Sep 17 00:00:00 2001 From: princehw03 Date: Mon, 9 Feb 2026 20:11:34 +0900 Subject: [PATCH 27/30] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt index 37bb53f9..ad8703ee 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryWriteFragment.kt @@ -184,7 +184,7 @@ class DiaryWriteFragment : Fragment() { // 일기 타입 카드 선택 상태 업데이트 binding.cvEmotion.isSelected = state.selectedTypes.contains("감정") binding.cvWorry.isSelected = state.selectedTypes.contains("고민") - binding.tvPraise.isSelected = state.selectedTypes.contains("칭찬") + binding.cvPraise.isSelected = state.selectedTypes.contains("칭찬") binding.cvThanks.isSelected = state.selectedTypes.contains("감사") // "감정" 타입이 선택되었을 때만 레벨 선택 섹션 표시 From 88d26f3d24c59e5042673fbafbf0845362e7d830 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Tue, 10 Feb 2026 15:22:41 +0900 Subject: [PATCH 28/30] =?UTF-8?q?refactor:=20=EB=B0=B1=EC=97=94=EB=93=9C?= =?UTF-8?q?=20=ED=83=80=EC=9E=84=EC=A1=B4=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20KST=EB=B3=80=ED=99=98=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt index a4a4f84c..188dff63 100644 --- a/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt +++ b/app/src/main/java/com/egobook/app/domain/model/diary/mapper/DiaryMapper.kt @@ -42,7 +42,7 @@ object DiaryMapper { return Diary( diaryId = diaryId, date = LocalDate.parse(date), - writtenAt = parseUtcToKst(writtenAt), + writtenAt = LocalDateTime.parse(writtenAt), types = type.map { DiaryType.from(it) }.toSet(), emotionLevel = emotionLevel, content = content, From 79e43958d9c4f1fcca416f7134f07f4fe4e5752c Mon Sep 17 00:00:00 2001 From: princehw03 Date: Tue, 10 Feb 2026 16:22:02 +0900 Subject: [PATCH 29/30] =?UTF-8?q?design:=20=EC=9D=BC=EA=B8=B0=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20ui=20=EC=9D=BC=EA=B4=80=EC=84=B1=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diary/paging/DiariesPagingSource.kt | 8 +- .../app/ui/diary/view/DiaryFragment.kt | 16 +- app/src/main/res/layout/fragment_diary.xml | 144 +++++++++--------- 3 files changed, 91 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt index 03726c4d..f345a537 100644 --- a/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/diary/paging/DiariesPagingSource.kt @@ -8,6 +8,7 @@ import com.egobook.app.domain.model.diary.entity.DiarySummary import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toRequestParams import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiarySummary +import timber.log.Timber class DiariesPagingSource( @@ -35,7 +36,12 @@ class DiariesPagingSource( ) // dailyCount 캐시 업데이트 콜백 호출 - onDailyCountReceived(response.data.dailyCount) + val actualContentSize = response.data.diaries.content.size + val serverDailyCount = response.data.dailyCount + + Timber.d("[DailyCount 디버그] date=$dateParam, 서버 dailyCount=$serverDailyCount, 실제 content 개수=$actualContentSize") + + onDailyCountReceived(serverDailyCount) val diarySlice = response.data.diaries diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index 18848cb1..78bd58bc 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -125,7 +125,7 @@ repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collectLatest { state -> binding.tvYear.text = state.yearText - binding.tvMonth.text = state.monthText + binding.tvMonth.text = "${state.monthText}월" binding.tvDate.text = state.dayText } } @@ -195,7 +195,8 @@ val customView = layoutInflater.inflate(R.layout.toast_over_write, null) - val layout = snackBar.view as Snackbar.SnackbarLayout + // ❌ SnackbarLayout 쓰지 말 것 + val layout = snackBar.view as ViewGroup layout.setPadding(0, 0, 0, 0) layout.setBackgroundColor(Color.TRANSPARENT) @@ -203,16 +204,19 @@ // BottomNav에 붙이기 val bottomNav = requireActivity().findViewById(R.id.bottom_navigation) - snackBar.anchorView = bottomNav //스낵바의 앵커를 bottomNav로 설정 + snackBar.anchorView = bottomNav - // BottomNav로부터 9dp 위로 - val extra = (9 * resources.displayMetrics.density) - snackBar.view.translationY = -extra + // ⭐ translationY 대신 margin으로 띄우기 + val extra = (9 * resources.displayMetrics.density).toInt() + val params = snackBar.view.layoutParams as ViewGroup.MarginLayoutParams + params.bottomMargin += extra + snackBar.view.layoutParams = params snackBar.show() } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/res/layout/fragment_diary.xml b/app/src/main/res/layout/fragment_diary.xml index f3270948..d477a32b 100644 --- a/app/src/main/res/layout/fragment_diary.xml +++ b/app/src/main/res/layout/fragment_diary.xml @@ -18,85 +18,89 @@ android:background="@drawable/topbar_background" android:paddingBottom="12dp"> - + - - - - - - - - - - - - + + + + - - - - - - - + android:layout_height="21dp" + android:text="12월" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" + android:gravity="center"/> + + + + + + + + + + + + + From 7bab6d1c9ba5709bb536ddc30fa27ebce4908b19 Mon Sep 17 00:00:00 2001 From: princehw03 Date: Tue, 10 Feb 2026 16:26:36 +0900 Subject: [PATCH 30/30] =?UTF-8?q?remove:=20=EC=93=B8=EB=8D=B0=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index 78bd58bc..5b03458c 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -195,7 +195,6 @@ val customView = layoutInflater.inflate(R.layout.toast_over_write, null) - // ❌ SnackbarLayout 쓰지 말 것 val layout = snackBar.view as ViewGroup layout.setPadding(0, 0, 0, 0) layout.setBackgroundColor(Color.TRANSPARENT) @@ -206,7 +205,7 @@ val bottomNav = requireActivity().findViewById(R.id.bottom_navigation) snackBar.anchorView = bottomNav - // ⭐ translationY 대신 margin으로 띄우기 + // translationY 대신 margin으로 띄우기 val extra = (9 * resources.displayMetrics.density).toInt() val params = snackBar.view.layoutParams as ViewGroup.MarginLayoutParams params.bottomMargin += extra