diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e76128b..8bd75f72 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,4 @@ -import org.gradle.kotlin.dsl.testRuntimeOnly + import org.gradle.kotlin.dsl.testRuntimeOnly import java.io.FileInputStream import java.util.Properties @@ -36,12 +36,13 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "com.egobook.app.HiltTestRunner" - val baseUrl = localProperties.getProperty("BACKEND_BASE_URL") - buildConfigField("String", "BACKEND_BASE_URL", "\"$baseUrl\"") - val googleClientId = localProperties.getProperty("GOOGLE_WEB_CLIENT_ID") buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", "\"$googleClientId\"") + val backendBaseUrl = localProperties.getProperty("BACKEND_BASE_URL") + buildConfigField("String", "BACKEND_BASE_URL", "\"$backendBaseUrl\"") + val aiBaseUrl = localProperties.getProperty("AI_BASE_URL") + buildConfigField("String", "AI_BASE_URL", "\"$aiBaseUrl\"") } buildTypes { diff --git a/app/src/main/java/com/egobook/app/data/api/AIApiService.kt b/app/src/main/java/com/egobook/app/data/api/AIApiService.kt new file mode 100644 index 00000000..cf91eb59 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/api/AIApiService.kt @@ -0,0 +1,12 @@ +package com.egobook.app.data.api + +import com.egobook.app.data.model.square.letter.DetectAbusiveContentRequest +import com.egobook.app.data.model.square.letter.DetectAbusiveContentResponse +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + +interface AIApiService { + @POST("/detect") + suspend fun detectAbusiveContent(@Body request: DetectAbusiveContentRequest): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/api/CounselingApiService.kt b/app/src/main/java/com/egobook/app/data/api/CounselingApiService.kt index 61413e30..330569d6 100644 --- a/app/src/main/java/com/egobook/app/data/api/CounselingApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/CounselingApiService.kt @@ -1,27 +1,75 @@ package com.egobook.app.data.api -import com.egobook.app.data.model.counseling.PraiseMessageResponse +import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.counseling.CounselingNotificationRequest +import com.egobook.app.data.model.counseling.DailyAndWeeklyNotificationResponse +import com.egobook.app.data.model.counseling.DailyPraiseResponse +import com.egobook.app.data.model.counseling.DailyPraisesResponse +import com.egobook.app.data.model.counseling.ReportStyleRequest import com.egobook.app.data.model.counseling.StatisticsResponse import com.egobook.app.data.model.counseling.WeeklyReportResponse +import com.egobook.app.data.model.counseling.WeeklyReportsResponse import com.egobook.app.data.model.counseling.WeeklyReportStyleResponse import com.egobook.app.domain.model.ReportStyle +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.PATCH import retrofit2.http.POST +import retrofit2.http.Path import retrofit2.http.Query interface CounselingApiService { - @GET("api/praise/daily") - suspend fun fetchDailyPraise(): Response> + @GET("/ego-room/praise/daily") + suspend fun fetchDailyPraises( + @Query("page") page: Int, + @Query("size") size: Int + ): ApiResponse - @GET("api/reports/weekly") - suspend fun fetchWeeklyReports(): Response> + @GET("/ego-room/praise/daily/{date}") + suspend fun fetchDailyPraiseByDate( + @Path("date") date: String + ): Response - @GET("api/reports/weekly/style") - suspend fun fetchWeeklyReportStyle(): Response + @GET("/ego-room/ai/toggle") + suspend fun fetchDailyAndWeeklyNotification(): ApiResponse - @POST("api/reports/weekly/style") - suspend fun updateWeeklyReportStyle(@Query("style") reportStyle: ReportStyle): Response + @PATCH("/ego-room/praise/daily") + suspend fun updateDailyPraiseNotification( + @Body request: CounselingNotificationRequest + ): ApiResponse + + @PATCH("/ego-room/counsel/weekly") + suspend fun updateWeeklyReportNotification( + @Body request: CounselingNotificationRequest + ): ApiResponse + + @GET("/ego-room/counsel/weekly") + suspend fun fetchWeeklyReports( + @Path("page") page: Int, + @Path("size") size: Int + ): ApiResponse + + @GET("/ego-room/counsel/weekly/{startDate}") + suspend fun fetchWeeklyReportByDate( + @Path("startDate") startDate: String + ): Response + + @PATCH("/ego-room/counsel/weekly/next-tone") + suspend fun updateWeeklyReportStyle( + @Body request: ReportStyleRequest + ): Response + + @POST("/ego-room/counsel/weekly/{startDate}/unlock") + suspend fun unlockWeeklyReport( + @Path("startDate") startDate: String, + @Path("unlockType") unlockType: WeeklyReportUnlockType + ): ApiResponse + + + @GET("/ego-room/counseling-tone") + suspend fun fetchWeeklyReportStyle(): ApiResponse @GET("api/statistics") suspend fun fetchStatistics(): Response diff --git a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt new file mode 100644 index 00000000..aa058c2d --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -0,0 +1,63 @@ +package com.egobook.app.data.api + +import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.square.letter.ArrivedPendingLetterResponse +import com.egobook.app.data.model.square.letter.ReplyLetterRequest +import com.egobook.app.data.model.square.letter.ReplyLetterResponse +import com.egobook.app.data.model.square.letter.ReportLetterRequest +import com.egobook.app.data.model.square.letter.SendLetterRequest +import com.egobook.app.data.model.square.letter.SendLetterResponse +import com.egobook.app.data.model.square.letter.SentLetterResponse +import com.egobook.app.data.model.square.letter.SentLetterWithReplyResponse +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface LetterApiService { + @POST("/plaza/letters") + suspend fun sendLetter(@Body request: SendLetterRequest): ApiResponse + + @GET("/plaza/letters/inbox/next") + suspend fun fetchArrivedPendingLetter(): ApiResponse + + @POST("/plaza/letters/{letterId}/reply") + suspend fun replyLetter( + @Path("letterId") letterId: Long, + @Body request: ReplyLetterRequest + ): ApiResponse + + @POST("/plaza/letters/{letterId}/defer") + suspend fun deferReplyLetter( + @Path("letterId") letterId: Long + ): ApiResponse + + @POST("/plaza/letters/{letterId}/give-up") + suspend fun giveUpReplyLetter( + @Path("letterId") letterId: Long + ): ApiResponse + + @GET("/plaza/letters/sent") + suspend fun fetchSentLetters( + @Query("page") page: Int, + @Query("size") size: Int + ): ApiResponse + + @GET("/plaza/letters/{letterId}") + suspend fun fetchSentLetterWithReply( + @Path("letterId") letterId: Long + ): ApiResponse + + @POST("/plaza/letters/{replyId}/report") + suspend fun reportRepliedLetter( + @Path("replyId") replyId: Long, + @Body request: ReportLetterRequest + ): ApiResponse + + @DELETE("/plaza/letters/threads/{threadId}") + suspend fun deleteLetterThread( + @Path("threadId") threadId: Long + ): ApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/interceptor/TokenAuthenticator.kt b/app/src/main/java/com/egobook/app/data/interceptor/TokenAuthenticator.kt index 39117e9d..b058fe48 100644 --- a/app/src/main/java/com/egobook/app/data/interceptor/TokenAuthenticator.kt +++ b/app/src/main/java/com/egobook/app/data/interceptor/TokenAuthenticator.kt @@ -5,7 +5,7 @@ import android.content.Intent import com.egobook.app.data.api.AuthApiService import com.egobook.app.data.local.UserInfoStorage import com.egobook.app.data.model.auth.AccessTokenRequest -import com.egobook.app.di.AuthRetrofit +import com.egobook.app.di.qualifier.AuthRetrofit import com.egobook.app.ui.login.view.LoginActivity import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.first @@ -20,7 +20,7 @@ import javax.inject.Inject class TokenAuthenticator @Inject constructor( @param:ApplicationContext private val context: Context, private val userInfoStorage: UserInfoStorage, - @param:AuthRetrofit private val authApiService: AuthApiService + private val authApiService: AuthApiService ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/CounselingNotificationRequest.kt b/app/src/main/java/com/egobook/app/data/model/counseling/CounselingNotificationRequest.kt new file mode 100644 index 00000000..0cc85b31 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/CounselingNotificationRequest.kt @@ -0,0 +1,8 @@ +package com.egobook.app.data.model.counseling + +import com.google.gson.annotations.SerializedName + +data class CounselingNotificationRequest( + @SerializedName("enabled") + val isEnabled: Boolean +) diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/DailyAndWeeklyNotificationResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/DailyAndWeeklyNotificationResponse.kt new file mode 100644 index 00000000..0a73f6a1 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/DailyAndWeeklyNotificationResponse.kt @@ -0,0 +1,18 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification +import com.google.gson.annotations.SerializedName + +data class DailyAndWeeklyNotificationResponse( + @SerializedName("dailyPraiseEnabled") + val isDailyPraiseEnabled: Boolean, + @SerializedName("weeklyAnalysisEnabled") + val isWeeklyAnalysisEnabled: Boolean +) + +fun DailyAndWeeklyNotificationResponse.toDomain() = DailyAndWeeklyNotification( + isDailyPraiseEnabled = isDailyPraiseEnabled, + isWeeklyAnalysisEnabled = isWeeklyAnalysisEnabled +) + + diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraiseResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraiseResponse.kt new file mode 100644 index 00000000..0f7a1287 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraiseResponse.kt @@ -0,0 +1,42 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.CounselingReward +import com.egobook.app.domain.model.counseling.CounselingRewardType +import com.egobook.app.domain.model.counseling.DailyPraiseDetail +import com.google.gson.annotations.SerializedName + +data class DailyPraiseResponse( + @SerializedName("diaryDate") + val diaryDate: String, + @SerializedName("content") + val content: String, + @SerializedName("createdAt") + val createdAt: String, + @SerializedName("isRead") + val isRead: Boolean, + @SerializedName("rewards") + val rewards: List? = null +) + +data class CounselingRewardResponse( + @SerializedName("kind") + val kind: CounselingRewardType, + @SerializedName("amount") + val amount: Int, + @SerializedName("toastMessage") + val toastMessage: String +) + +fun CounselingRewardResponse.toDomain() = CounselingReward( + kind = kind, + amount = amount, + toastMessage = toastMessage +) + +fun DailyPraiseResponse.toDomain() = DailyPraiseDetail( + diaryDate = diaryDate, + content = content, + createdAt = createdAt, + isRead = isRead, + rewards = rewards?.map { it.toDomain() } +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt new file mode 100644 index 00000000..ae8607de --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt @@ -0,0 +1,30 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.DailyPraise +import com.google.gson.annotations.SerializedName + +data class DailyPraisesResponse( + @SerializedName("content") + val content: List, + @SerializedName("page") + val page: Int, + @SerializedName("size") + val size: Int, + @SerializedName("hasNext") + val hasNext: Boolean +) + +data class PraiseDailyItemResponse( + @SerializedName("id") + val id: Int, + @SerializedName("diaryDate") + val diaryDate: String, + @SerializedName("isRead") + val isRead: Boolean +) + +fun PraiseDailyItemResponse.toDomain(): DailyPraise = DailyPraise( + id = id, + diaryDate = diaryDate, + isRead = isRead +) diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/PraiseMessageResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/PraiseMessageResponse.kt deleted file mode 100644 index c4f4cc74..00000000 --- a/app/src/main/java/com/egobook/app/data/model/counseling/PraiseMessageResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.egobook.app.data.model.counseling - -import com.egobook.app.domain.model.PraiseMessage - -data class PraiseMessageResponse( - val id: Int, - val message: String, - val createdAt: String -) { - fun toDomain() = PraiseMessage(id = id, content = message, date = createdAt) -} diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/ReportStyleRequest.kt b/app/src/main/java/com/egobook/app/data/model/counseling/ReportStyleRequest.kt new file mode 100644 index 00000000..b0ff7799 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/ReportStyleRequest.kt @@ -0,0 +1,7 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.ReportStyle + +data class ReportStyleRequest( + val toneStyle: ReportStyle +) diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt index 6a2c7293..37576b6a 100644 --- a/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt @@ -1,31 +1,34 @@ package com.egobook.app.data.model.counseling -import com.egobook.app.domain.model.WeeklyReport -import com.egobook.app.domain.model.WeeklyReportContent +import com.egobook.app.domain.model.counseling.WeeklyReportDetail +import com.google.gson.annotations.SerializedName data class WeeklyReportResponse( - val id: Long, - val date: String, - val content: WeeklyReportContentResponse -) - -data class WeeklyReportContentResponse( - val analysis: String, - val praisePoint: String, - val improvement: String, - val management: String, - val encouragement: String -) - -fun WeeklyReportResponse.toDomain(): WeeklyReport = WeeklyReport( - id = id, - date = date, - content = WeeklyReportContent( - analysis = content.analysis, - praisePoint = content.praisePoint, - improvement = content.improvement, - management = content.management, - encouragement = content.encouragement - ) + @SerializedName("startDate") + val startDate: String, + @SerializedName("endDate") + val endDate: String, + @SerializedName("summary") + val summary: String, + @SerializedName("praisePoints") + val praisePoints: String, + @SerializedName("improvementPoints") + val improvementPoints: String, + @SerializedName("managementAdvice") + val managementAdvice: String, + @SerializedName("supportMessage") + val supportMessage: String, + @SerializedName("isRead") + val isRead: Boolean ) +fun WeeklyReportResponse.toDomain() = WeeklyReportDetail( + startDate = startDate, + endDate = endDate, + summary = summary, + praisePoints = praisePoints, + improvementPoints = improvementPoints, + managementAdvice = managementAdvice, + supportMessage = supportMessage, + isRead = isRead +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportsResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportsResponse.kt new file mode 100644 index 00000000..e784b888 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportsResponse.kt @@ -0,0 +1,37 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.WeeklyReport +import com.google.gson.annotations.SerializedName + +data class WeeklyReportsResponse( + @SerializedName("content") + val content: List, + @SerializedName("page") + val page: Int, + @SerializedName("size") + val size: Int, + @SerializedName("hasNext") + val hasNext: Boolean +) + +data class WeeklyReportsContentResponse( + @SerializedName("id") + val id: Long, + @SerializedName("startDate") + val startDate: String, + @SerializedName("endDate") + val endDate: String, + @SerializedName("isRead") + val isRead: Boolean, + @SerializedName("isLocked") + val isLocked: Boolean +) + +fun WeeklyReportsContentResponse.toDomain(): WeeklyReport = WeeklyReport( + id = id, + startDate = startDate, + endDate = endDate, + isRead = isRead, + isLocked = isLocked +) + diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/ArrivedPendingLetterResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/ArrivedPendingLetterResponse.kt new file mode 100644 index 00000000..59cb9973 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/ArrivedPendingLetterResponse.kt @@ -0,0 +1,47 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetter +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetterItem +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.google.gson.annotations.SerializedName + +data class ArrivedPendingLetterResponse( + @SerializedName("letter") + val letter: ArrivedPendingLetterItemResponse? = null +) + +data class ArrivedPendingLetterItemResponse( + @SerializedName("letterId") + val letterId: Long, + @SerializedName("status") + val status: LetterStatus, + @SerializedName("mode") + val mode: LetterMode, + @SerializedName("fromLabel") + val fromLabel: String, // 무슨 필드인지 의미 잘 모르겠음 + @SerializedName("content") + val content: String, + @SerializedName("arrivedAt") + val arrivedAt: String, + @SerializedName("replyDeadlineAt") + val replyDeadlineAt: String, + @SerializedName("letterColor") + val letterColor: LetterBackgroundColor // TODO: 백엔드한테 필드 넣어달라고 하기 +) + +fun ArrivedPendingLetterResponse.toDomain(): ArrivedPendingLetter = ArrivedPendingLetter( + letter = letter?.toDomain() +) + +fun ArrivedPendingLetterItemResponse.toDomain(): ArrivedPendingLetterItem = ArrivedPendingLetterItem( + letterId = letterId, + status = status, + mode = mode, + fromLabel = fromLabel, + content = content, + arrivedAt = arrivedAt, + replyDeadlineAt = replyDeadlineAt, + letterColor = letterColor +) diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentRequest.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentRequest.kt new file mode 100644 index 00000000..5d881607 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentRequest.kt @@ -0,0 +1,8 @@ +package com.egobook.app.data.model.square.letter + +import com.google.gson.annotations.SerializedName + +data class DetectAbusiveContentRequest( + @SerializedName("text") + val text: String +) diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentResponse.kt new file mode 100644 index 00000000..46d0ed28 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentResponse.kt @@ -0,0 +1,25 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis +import com.google.gson.annotations.SerializedName + +data class DetectAbusiveContentResponse( + @SerializedName("text") + val text: String, + @SerializedName("percentage") + val percentage: Double, + @SerializedName("is_harmful") + val isHarmful: Boolean, + @SerializedName("label") + val label: String, + @SerializedName("bad_words") + val badWords: List +) + +fun DetectAbusiveContentResponse.toDomain(): AbusiveContentAnalysis = AbusiveContentAnalysis( + text = text, + riskScore = percentage, + isHarmful = isHarmful, + label = label, + detectedBadWords = badWords +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterRequest.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterRequest.kt new file mode 100644 index 00000000..5a47f9ea --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterRequest.kt @@ -0,0 +1,7 @@ +package com.egobook.app.data.model.square.letter + +import com.google.gson.annotations.SerializedName + +data class ReplyLetterRequest( + @SerializedName("text") val text: String +) diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterResponse.kt new file mode 100644 index 00000000..b0b07496 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterResponse.kt @@ -0,0 +1,42 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.ReplyLetter +import com.egobook.app.domain.model.square.letter.ReplyLetterRewards +import com.egobook.app.domain.model.square.letter.ReplyReward +import com.google.gson.annotations.SerializedName + +data class ReplyLetterResponse( + @SerializedName("letterId") + val letterId: Long, + @SerializedName("status") + val status: LetterStatus, + @SerializedName("repliedAt") + val repliedAt: String, + @SerializedName("rewards") + val rewards: List +) + +data class ReplyLetterRewardsResponse( + @SerializedName("kind") + val kind: ReplyReward, + @SerializedName("amount") + val amount: Int, + @SerializedName("toastMessage") + val toastMessage: String? = null +) + +fun ReplyLetterResponse.toDomain(): ReplyLetter = ReplyLetter( + letterId = letterId, + status = status, + repliedAt = repliedAt, + rewards = rewards.map { it.toDomain() } +) + +fun ReplyLetterRewardsResponse.toDomain(): ReplyLetterRewards = ReplyLetterRewards( + kind = kind, + amount = amount, + toastMessage = toastMessage +) + + diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/ReportLetterRequest.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/ReportLetterRequest.kt new file mode 100644 index 00000000..99b7f11b --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/ReportLetterRequest.kt @@ -0,0 +1,17 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.ReportLetter +import com.egobook.app.domain.model.square.letter.ReportLetterType +import com.google.gson.annotations.SerializedName + +data class ReportLetterRequest( + @SerializedName("reason") + val reason: ReportLetterType, + @SerializedName("description") + val description: String? = null +) + +fun ReportLetter.toData(): ReportLetterRequest = ReportLetterRequest( + reason = reason, + description = description +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterRequest.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterRequest.kt new file mode 100644 index 00000000..b7a9e442 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterRequest.kt @@ -0,0 +1,24 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.SendLetter +import com.google.gson.annotations.SerializedName + +data class SendLetterRequest( + @SerializedName("mode") + val mode: LetterMode, + @SerializedName("toFriendId") + val receiverId: Long? = null, + @SerializedName("text") + val content: String, + @SerializedName("backgroundColor") + val letterColor: LetterBackgroundColor +) + +fun SendLetter.toData(): SendLetterRequest = SendLetterRequest( + mode = mode, + receiverId = receiverId, + content = content, + letterColor = letterColor +) diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterResponse.kt new file mode 100644 index 00000000..160a933d --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterResponse.kt @@ -0,0 +1,18 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.LetterMode +import com.google.gson.annotations.SerializedName + +data class SendLetterResponse( + @SerializedName("letterId") + val letterId: Long, + @SerializedName("threadId") + val threadId: Long, + @SerializedName("status") + val status: LetterStatus, + @SerializedName("mode") + val mode: LetterMode, + @SerializedName("createdAt") + val createdAt: String +) diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterResponse.kt new file mode 100644 index 00000000..5aebac8a --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterResponse.kt @@ -0,0 +1,49 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.SentLetter +import com.egobook.app.domain.model.square.letter.SentLetterItem +import com.google.gson.annotations.SerializedName + +data class SentLetterResponse( + @SerializedName("content") + val content: List, + @SerializedName("page") + val page: Int, + @SerializedName("size") + val size: Int, + @SerializedName("hasNext") + val hasNext: Boolean +) + +data class SentLetterItemResponse( + @SerializedName("letterId") + val letterId: Long, + @SerializedName("mode") + val mode: LetterMode, + @SerializedName("status") + val status: LetterStatus, + @SerializedName("aiReplaceAt") + val aiReplaceAt: String, + @SerializedName("lastMessagePreview") + val content: String, + @SerializedName("createdAt") + val createdAt: String +) + +fun SentLetterItemResponse.toDomain() = SentLetterItem( + letterId = letterId, + mode = mode, + status = status, + aiReplaceAt = aiReplaceAt, + content = content, + createdAt = createdAt +) + +fun SentLetterResponse.toDomain() = SentLetter( + content = content.map { it.toDomain() }, + page = page, + size = size, + hasNext = hasNext +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterWithReplyResponse.kt b/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterWithReplyResponse.kt new file mode 100644 index 00000000..a322330b --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterWithReplyResponse.kt @@ -0,0 +1,62 @@ +package com.egobook.app.data.model.square.letter + +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterReply +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.SentLetterWithReply +import com.google.gson.annotations.SerializedName + +data class SentLetterWithReplyResponse( + @SerializedName("letterId") + val letterId: Long, + @SerializedName("threadId") + val threadId: Long, + @SerializedName("status") + val status: LetterStatus, + @SerializedName("mode") + val mode: LetterMode, + @SerializedName("content") + val sentContent: String, + @SerializedName("backgroundColor") + val backgroundColor: LetterBackgroundColor, + @SerializedName("createdAt") + val createdAt: String, + @SerializedName("arrivedAt") + val arrivedAt: String, + @SerializedName("reply") + val reply: LetterReplyResponse? = null +) + +data class LetterReplyResponse( + @SerializedName("replyId") + val replyId: Long, + @SerializedName("text") + val replyContent: String, + @SerializedName("aiGenerated") + val isAIGenerated: Boolean, + @SerializedName("reported") + val isReported: Boolean, + @SerializedName("createdAt") + val repliedAt: String +) + +fun LetterReplyResponse.toDomain(): LetterReply = LetterReply( + replyId = replyId, + replyContent = replyContent, + isAIGenerated = isAIGenerated, + isReported = isReported, + repliedAt = repliedAt +) + +fun SentLetterWithReplyResponse.toDomain(): SentLetterWithReply = SentLetterWithReply( + letterId = letterId, + threadId = threadId, + status = status, + mode = mode, + sentContent = sentContent, + backgroundColor = backgroundColor, + createdAt = createdAt, + arrivedAt = arrivedAt, + reply = reply?.toDomain() +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt index 122f48fa..d50824ca 100644 --- a/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt @@ -1,109 +1,204 @@ package com.egobook.app.data.repository +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData import com.egobook.app.data.api.CounselingApiService +import com.egobook.app.data.model.counseling.CounselingNotificationRequest +import com.egobook.app.data.model.counseling.ReportStyleRequest +import com.egobook.app.data.model.counseling.toDomain +import com.egobook.app.data.repository.paging.DailyPraisePagingSource +import com.egobook.app.data.repository.paging.WeeklyReportsPagingSource import com.egobook.app.domain.model.DailyData import com.egobook.app.domain.model.EmotionType import com.egobook.app.domain.model.MonthData -import com.egobook.app.domain.model.PraiseMessage import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.model.Statistics import com.egobook.app.domain.model.TimeData -import com.egobook.app.domain.model.WeeklyReport -import com.egobook.app.domain.model.WeeklyReportContent import com.egobook.app.domain.model.WeeklyReportStyle +import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification +import com.egobook.app.domain.model.counseling.DailyPraise +import com.egobook.app.domain.model.counseling.DailyPraiseDetail +import com.egobook.app.domain.model.counseling.WeeklyReport +import com.egobook.app.domain.model.counseling.WeeklyReportDetail +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType import com.egobook.app.domain.repository.CounselingRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import javax.inject.Inject class CounselingRepositoryImpl @Inject constructor(private val apiService: CounselingApiService) : CounselingRepository { - override suspend fun getDailyPraise(): Result> = try { -// val response = apiService.fetchDailyPraise() -// if(response.isSuccessful && response.body() != null) { -// val domainList = response.body()!!.map { it.toDomain() } -// Result.success(domainList) -// } else { -// Result.failure(Exception("Error: ${response.code()}")) -// } - val dummyData = listOf( - PraiseMessage( - 1, - "어제보다 오늘 더 성장한 당신을 정말 칭찬해요! 칭찬서의 내용이 적히는 자리입니다. 이 영역은 긴 문장이 들어왔을 때 UI가 어떻게 반응하는지 확인하기 위해 작성되었습니다. 당신의 성장은 눈에 보이지 않아도 분명히 진행되고 있어요.", - "2025.12.30" - ), - PraiseMessage( - 2, - "꾸준히 노력하는 모습이 정말 아름답습니다. 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리", - "2025.12.31" + override fun getDailyPraise(size: Int): Flow> { + return Pager( + config = PagingConfig( + pageSize = size, + initialLoadSize = size, + enablePlaceholders = false ), - PraiseMessage( - 3, - "작은 일에도 최선을 다하는 당신이 자랑스러워요. 때로는 쉬어가는 것도 용기라는 것을 잊지 마세요. 칭찬서의 내용이 적히는 자리입니다. 충분히 잘하고 있고, 앞으로도 당신의 걸음을 응원하겠습니다.", - "2026.01.01" - ), - PraiseMessage( - 4, - "실패를 두려워하지 않는 용기가 멋집니다. 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리", - "2026.01.02" - ), - PraiseMessage( - 5, - "오늘 하루도 정말 고생 많으셨습니다. 푹 쉬세요! 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리", - "2026.01.03" - ) + pagingSourceFactory = { + DailyPraisePagingSource(apiService = apiService) + } + ).flow + } + + override suspend fun getDailyPraiseByDate(date: String): Result = try { + val response = apiService.fetchDailyPraiseByDate(date = date) + if (response.isSuccessful && response.body() != null) { + Result.success(response.body()!!.toDomain()) + } else { + Result.failure(Exception("Error: ${response.code()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun getDailyAndWeeklyNotification(): Result = try { + val response = apiService.fetchDailyAndWeeklyNotification() + if (response.status == 200) { + Result.success(response.data.toDomain()) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result = try { + val response = apiService.updateDailyPraiseNotification( + request = CounselingNotificationRequest(isEnabled = isEnabled) ) - Result.success(dummyData) + if (response.status == 200) { + Result.success(isEnabled) + } else { + Result.failure(Exception("Error: ${response.status}")) + } } catch (e: Exception) { Result.failure(e) } - override suspend fun getWeeklyReport(): Result> = try { -// val response = apiService.fetchWeeklyReports() -// if(response.isSuccessful && response.body() != null) { -// Result.success(response.body()!!.map { it.toDomain() }) -// } else { -// Result.failure(Exception("Error: ${response.code()}")) -// } - val longDummyText = - "이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + override suspend fun updateWeeklyReportNotification(isEnabled: Boolean): Result = try { + val response = apiService.updateWeeklyReportNotification( + request = CounselingNotificationRequest(isEnabled = isEnabled) + ) + if (response.status == 200) { + Result.success(isEnabled) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override fun getWeeklyReports(size: Int): Flow> { +// return Pager( +// config = PagingConfig( +// pageSize = size, +// initialLoadSize = size, +// enablePlaceholders = false +// ), +// pagingSourceFactory = { +// WeeklyReportsPagingSource(apiService = apiService) +// } +// ).flow val dummyWeeklyReports = listOf( WeeklyReport( id = 1L, - date = "2025.12.22", - content = WeeklyReportContent( - analysis = "이번 주 분석: $longDummyText", - praisePoint = "칭찬 포인트: $longDummyText", - improvement = "개선할 점: $longDummyText", - management = "관리 및 조언: $longDummyText", - encouragement = "응원 및 격려: $longDummyText" - ) + startDate = "2024.02.03", + endDate = "2024.02.09", + isRead = true, + isLocked = false ), WeeklyReport( id = 2L, - date = "2025.12.29", - content = WeeklyReportContent( - analysis = "지난 주 분석: $longDummyText", - praisePoint = "지난 주 칭찬: $longDummyText", - improvement = "지난 주 개선: $longDummyText", - management = "지난 주 조언: $longDummyText", - encouragement = "지난 주 격려: $longDummyText" - ) + startDate = "2024.01.27", + endDate = "2024.02.02", + isRead = false, + isLocked = false + ), + WeeklyReport( + id = 3L, + startDate = "2024.01.20", + endDate = "2024.01.26", + isRead = false, + isLocked = true // 잠긴 상태 테스트 + ), + WeeklyReport( + id = 4L, + startDate = "2024.01.13", + endDate = "2024.01.19", + isRead = false, + isLocked = true ) ) - Result.success(dummyWeeklyReports) - } catch (e: Exception) { - Result.failure(e) + // 2. Pager 대신 flowOf와 PagingData.from을 사용하여 즉시 반환 + return flowOf( + PagingData.from(dummyWeeklyReports) + ) } - override suspend fun getWeeklyReportStyle(): Result = try { -// val response = apiService.fetchWeeklyReportStyle() -// if(response.isSuccessful && response.body() != null) { -// Result.success(response.body()!!.toDomain()) -// } else { -// Result.failure(Exception("Error: ${response.code()}")) -// } - Result.success(WeeklyReportStyle(type = ReportStyle.SOFT)) + override suspend fun getWeeklyReportByDate(startDate: String): Result = + try { +// val response = apiService.fetchWeeklyReportByDate(startDate = startDate) +// if (response.isSuccessful && response.body() != null) { +// Result.success(response.body()!!.toDomain()) +// } else { +// Result.failure(Exception("Error: ${response.code()}")) +// } + val dummyDetail = WeeklyReportDetail( + startDate = startDate, // 클릭한 아이템의 날짜를 그대로 사용 + endDate = "2024.02.09", + summary = """ + 이번 한 주 동안 사용자님은 정말 꾸준한 감정 기록을 보여주셨습니다. + 특히 주 초반에 겪었던 업무적 스트레스를 수요일 이후 스스로 잘 극복해내는 과정이 인상 깊었습니다. + 금요일에는 예상치 못한 즐거운 소식으로 인해 긍정적인 에너지가 최고조에 달했으며, + 이러한 긍정적인 흐름이 주말까지 이어져 안정적인 심리 상태를 유지하셨습니다. + 전반적으로 감정의 기복이 있었으나 스스로를 잘 돌보신 한 주였습니다. + """.trimIndent(), + praisePoints = """ + 가장 칭찬하고 싶은 점은 스트레스 상황에서도 기록을 포기하지 않은 끈기입니다. + 자신의 감정을 외면하지 않고 있는 그대로 마주하려는 태도가 매우 훌륭합니다. + 부정적인 감정이 들 때마다 짧은 명상을 시도한 점이 심리적 회복 탄력성을 높였습니다. + 또한 주변 사람들과 긍정적인 대화를 나누며 에너지를 얻으려 노력한 모습도 보기 좋습니다. + 작은 성취들을 기록하며 자신감을 되찾으려는 모습이 사용자님의 가장 큰 강점입니다. + """.trimIndent(), + improvementPoints = """ + 화요일 밤에 나타난 수면 부족 현상이 수요일 오전 집중력 저하로 이어진 것으로 보입니다. + 감정이 격해질 때 가끔 식사를 거르는 습관이 체력적인 부담을 줄 수 있으니 주의가 필요합니다. + 타인의 시선을 과도하게 신경 써서 본인의 진심을 숨기려는 경향이 간혹 관찰됩니다. + 불안함이 느껴질 때 너무 빠르게 결론을 내리려 하기보다는 시간을 두고 지켜보는 여유가 필요합니다. + 일과 삶의 경계가 모호해지지 않도록 명확한 휴식 시간을 설정하는 것을 추천드립니다. + """.trimIndent(), + managementAdvice = """ + 다음 주에는 하루에 10분이라도 온전히 스마트폰을 멀리하고 혼자만의 시간을 가져보세요. + 특히 감정이 복잡할 때는 글로 직접 써 내려가는 '감정 쓰기'가 큰 도움이 될 것입니다. + 점심시간을 활용한 가벼운 산책이 오후 시간의 집중력과 기분 전환에 효과적일 것입니다. + 불안한 생각이 들 때는 호흡에 집중하며 현재의 감각을 느껴보는 연습을 지속해 보세요. + 스스로에게 너무 엄격한 잣대를 대기보다는 '그럴 수도 있지'라는 마음가짐이 필요합니다. + """.trimIndent(), + supportMessage = """ + 사용자님, 이번 주도 정말 고생 많으셨습니다. 당신은 이미 충분히 잘하고 있습니다. + 때로는 비가 오기도 하지만, 그 비가 땅을 단단하게 만들어 더 아름다운 꽃을 피우게 할 거예요. + 작은 감정의 변화에 일희일비하기보다, 꾸준히 나아가고 있는 자신의 발걸음을 믿어보세요. + 어떤 순간에도 저는 사용자님의 편이 되어 응원하고 기록을 도와드릴 것입니다. + 따뜻한 차 한 잔과 함께 편안한 저녁 보내시길 바라며, 내일도 힘차게 시작해 봐요! + """.trimIndent(), + isRead = true + ) + Result.success(dummyDetail) + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun getWeeklyReportStyle(): Result = try { + val response = apiService.fetchWeeklyReportStyle() + if (response.status == 200) { + Result.success(response.data) + } else { + Result.failure(Exception("Error: ${response.status}")) + } } catch (e: Exception) { Result.failure(e) } @@ -111,17 +206,31 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns override suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result = try { -// val response = apiService.updateWeeklyReportStyle(reportStyle = reportStyle) -// if(response.isSuccessful) { -// Result.success(reportStyle) -// } else { -// Result.failure(Exception("Error: ${response.code()}")) -// } - Result.success(reportStyle) + val response = + apiService.updateWeeklyReportStyle(request = ReportStyleRequest(toneStyle = reportStyle)) + if (response.isSuccessful) { + Result.success(reportStyle) + } else { + Result.failure(Exception("Error: ${response.code()}")) + } } catch (e: Exception) { Result.failure(e) } + override suspend fun unlockWeeklyReport( + startDate: String, + unlockType: WeeklyReportUnlockType + ): Result = try { + val response = apiService.unlockWeeklyReport(startDate = startDate, unlockType = unlockType) + if (response.status == 200) { + Result.success(Unit) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + override suspend fun getStatistics(): Result = try { // val response = apiService.fetchStatistics() // if (response.isSuccessful && response.body() != null) { diff --git a/app/src/main/java/com/egobook/app/data/repository/FriendsRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/FriendsRepositoryImpl.kt index 1838e58a..bbb786d6 100644 --- a/app/src/main/java/com/egobook/app/data/repository/FriendsRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/FriendsRepositoryImpl.kt @@ -20,10 +20,17 @@ class FriendsRepositoryImpl @Inject constructor(private val apiService: FriendsA // Result.failure(Exception("Error: ${response.status}")) // } val dummyData = listOf( - FriendResponse(id = 1, name = "친구1"), - FriendResponse(id = 2, name = "친구2"), - FriendResponse(id = 3, name = "친구3") + FriendResponse(id = 1, name = "소프트웨어마법사"), + FriendResponse(id = 2, name = "야근하는다람쥐"), + FriendResponse(id = 3, name = "커피중독자"), + FriendResponse(id = 4, name = "안드로이드마스터"), + FriendResponse(id = 5, name = "코딩하는고양이"), + FriendResponse(id = 6, name = "말랑카우"), + FriendResponse(id = 7, name = "개발하는진돗개"), + FriendResponse(id = 8, name = "배고픈거북이"), + FriendResponse(id = 9, name = "잠자는사자") ) + val emptyDummyData = listOf() Result.success(dummyData.map { it.toDomain()}) } catch (e: Exception) { Result.failure(e) diff --git a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt new file mode 100644 index 00000000..a6717aa4 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -0,0 +1,188 @@ +package com.egobook.app.data.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.egobook.app.data.api.AIApiService +import com.egobook.app.data.api.LetterApiService +import com.egobook.app.data.model.square.letter.ReplyLetterRequest +import com.egobook.app.data.model.square.letter.toData +import com.egobook.app.data.model.square.letter.toDomain +import com.egobook.app.data.repository.paging.SentLettersPagingSource +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetter +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetterItem +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.ReplyLetter +import com.egobook.app.domain.model.square.letter.ReportLetter +import com.egobook.app.domain.model.square.letter.SendLetter +import com.egobook.app.domain.model.square.letter.SentLetterItem +import com.egobook.app.domain.model.square.letter.SentLetterWithReply +import com.egobook.app.domain.repository.LetterRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class LetterRepositoryImpl @Inject constructor( + private val letterApiService: LetterApiService, + private val aiApiService: AIApiService +) : LetterRepository { + override suspend fun sendLetter(letter: SendLetter): Result = try { + val response = letterApiService.sendLetter(request = letter.toData()) + if (response.status == 200) { + Result.success(Unit) // 나중에 구현하면서 응답 필요할 때 변경 + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun detectAbusiveContent(text: String): Result = try { +// val response = aiApiService.detectAbusiveContent(request = DetectAbusiveContentRequest(text = text)) +// if(response.isSuccessful && response.body() != null) { +// Result.success(response.body()!!.toDomain()) +// } else { +// Result.failure(Exception("Error: ${response.code()}")) +// } + val dummyData = AbusiveContentAnalysis( + text = "테스트 중입니다", + riskScore = 40.0, + isHarmful = true, + label = "응?", + detectedBadWords = emptyList() + ) + Result.success(dummyData) + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun fetchArrivedPendingLetter(): Result = try { +// val response = letterApiService.fetchArrivedPendingLetter() +// if(response.status == 200) { +// Result.success(response.data.toDomain()) +// } else { +// Result.failure(Exception("Error: ${response.status}")) +// } + val mockData = ArrivedPendingLetter( + letter = ArrivedPendingLetterItem( + letterId = 1L, + status = LetterStatus.ARRIVED, + mode = LetterMode.RANDOM, + fromLabel = "익명", + content = "안녕하세요! 요즘 날씨가 부쩍 추워졌는데 잘 지내고 계신가요? 오늘 우연히 당신의 이야기를 듣고 문득 위로의 말을 전하고 싶어 펜을 들었습니다. 누구나 가끔은 마음이 무겁고 모든 게 버겁게 느껴지는 날이 있잖아요. 그럴 때일수록 스스로를 너무 다그치지 말고, 따뜻한 차 한 잔 마시며 쉬어갔으면 좋겠어요. 당신은 충분히 잘해내고 있고, 존재만으로도 소중한 사람이라는 걸 잊지 마세요. 내일은 오늘보다 조금 더 웃을 수 있는 여유가 생기길 진심으로 응원하겠습니다! 답장 기다릴게요.", + arrivedAt = "2026-02-02T10:35:00+09:00", + replyDeadlineAt = "2026-02-04T21:40:00+09:00", + letterColor = LetterBackgroundColor.WHITE + ) + ) + val emptyMockData = ArrivedPendingLetter( + letter = null + ) + Result.success(emptyMockData) + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun replyLetter(letterId: Long, text: String): Result = try { + val response = letterApiService.replyLetter( + letterId = letterId, + request = ReplyLetterRequest(text = text) + ) + if (response.status == 200) { + Result.success(response.data.toDomain()) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun deferReplyLetter(letterId: Long): Result = try { + val response = letterApiService.deferReplyLetter(letterId = letterId) + if (response.status == 200) { + Result.success(Unit) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun giveUpReplyLetter(letterId: Long): Result = try { + val response = letterApiService.giveUpReplyLetter(letterId = letterId) + if (response.status == 200) { + Result.success(Unit) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override fun fetchSentLetters(size: Int): Flow> { + return Pager( + config = PagingConfig( + pageSize = size, + initialLoadSize = size, + enablePlaceholders = false + ), + pagingSourceFactory = { + SentLettersPagingSource(apiService = letterApiService) + } + ).flow + } + + override suspend fun fetchSentLetterWithReply(letterId: Long): Result = try { + val response = letterApiService.fetchSentLetterWithReply(letterId = letterId) + if(response.status == 200) { + Result.success(response.data.toDomain()) + } else { + Result.failure(Exception("Error: ${response.status}")) + } +// val mockSentLetterWithReply = SentLetterWithReply( +// letterId = letterId, +// threadId = letterId, +// status = LetterStatus.ARRIVED, // 답장이 도착함 +// mode = LetterMode.RANDOM, +// sentContent = "안녕하세요, 고민이 있어 편지를 보냅니다. 요즘 업무량이 너무 많아서 번아웃이 온 것 같아요. 어떻게 극복하면 좋을까요?", +// backgroundColor = LetterBackgroundColor.WHITE, +// createdAt = "2026-02-06T03:42:43.162307Z", +// arrivedAt = "2026-02-06T03:42:43.162307Z", +// // 답장 데이터 +// reply = LetterReply( +// replyId = 101L, +// replyContent = "보내주신 편지 잘 읽었습니다. 번아웃 때문에 많이 힘드시겠어요. 저도 비슷한 경험이 있었는데, 그럴 땐 완벽하게 해내려는 마음을 조금 내려놓고 하루에 딱 10분이라도 온전히 자신만을 위해 산책을 하는 게 큰 도움이 되더라고요. 당신은 이미 충분히 잘하고 있습니다. 너무 스스로를 몰아세우지 마세요.", +// isAIGenerated = true, +// isReported = false, +// repliedAt = "2026-02-06T03:42:43.162307Z" +// ) +// ) +// Result.success(mockSentLetterWithReply) + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun reportRepliedLetter(replyId: Long, reportLetter: ReportLetter): Result = try { + val response = letterApiService.reportRepliedLetter(replyId = replyId, request = reportLetter.toData()) + if(response.status == 200) { + Result.success(Unit) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } + + override suspend fun deleteLetterThread(threadId: Long): Result = try { + val response = letterApiService.deleteLetterThread(threadId = threadId) + if(response.status == 200) { + Result.success(Unit) + } else { + Result.failure(Exception("Error: ${response.status}")) + } + } catch (e: Exception) { + Result.failure(e) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/QuestionRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/QuestionRepositoryImpl.kt index 24b0bf44..284dc1b9 100644 --- a/app/src/main/java/com/egobook/app/data/repository/QuestionRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/QuestionRepositoryImpl.kt @@ -5,17 +5,14 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import com.egobook.app.data.api.QuestionApiService import com.egobook.app.data.model.square.question.TodayAnswerRequest -import com.egobook.app.data.model.square.question.TodayQuestionAnswerResponse -import com.egobook.app.data.model.square.question.TodayQuestionResponse import com.egobook.app.data.model.square.question.toDomain import com.egobook.app.data.repository.paging.AllUserRepliesPagingSource import com.egobook.app.data.repository.paging.FriendRepliesPagingSource import com.egobook.app.data.repository.paging.MyRepliesHistoryPagingSource import com.egobook.app.domain.model.TodayQuestion -import com.egobook.app.domain.model.square.question.AnswerVisibility -import com.egobook.app.domain.model.square.question.UserTodayQuestionAnswerItem import com.egobook.app.domain.model.square.question.MyTodayQuestionAnswerItem import com.egobook.app.domain.model.square.question.TodayAnswer +import com.egobook.app.domain.model.square.question.UserTodayQuestionAnswerItem import com.egobook.app.domain.repository.QuestionRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/app/src/main/java/com/egobook/app/data/repository/auth/AuthRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/auth/AuthRepositoryImpl.kt index f92dcce2..709aaabb 100644 --- a/app/src/main/java/com/egobook/app/data/repository/auth/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/auth/AuthRepositoryImpl.kt @@ -3,16 +3,16 @@ package com.egobook.app.data.repository.auth import com.egobook.app.data.api.AuthApiService import com.egobook.app.data.local.UserInfoStorage import com.egobook.app.data.model.auth.AccessTokenRequest -import com.egobook.app.data.model.auth.TokensRequest import com.egobook.app.data.model.auth.TokenRequestByGoogle import com.egobook.app.data.model.auth.TokenRequestByGuest +import com.egobook.app.data.model.auth.TokensRequest import com.egobook.app.data.model.auth.TokensRequestAgainByGuest import com.egobook.app.data.util.safeApiCallWithSuspendTransform import com.egobook.app.domain.repository.auth.AuthRepository -import timber.log.Timber -import javax.inject.Inject import kotlinx.coroutines.flow.first +import timber.log.Timber import java.util.UUID +import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val apiService: AuthApiService, diff --git a/app/src/main/java/com/egobook/app/data/repository/paging/DailyPraisePagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/DailyPraisePagingSource.kt new file mode 100644 index 00000000..b0d4a787 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/paging/DailyPraisePagingSource.kt @@ -0,0 +1,29 @@ +package com.egobook.app.data.repository.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.egobook.app.data.api.CounselingApiService +import com.egobook.app.data.model.counseling.toDomain +import com.egobook.app.domain.model.counseling.DailyPraise + +class DailyPraisePagingSource(private val apiService: CounselingApiService) : + PagingSource() { + override fun getRefreshKey(state: PagingState): Int { + return 1 + } + + override suspend fun load(params: LoadParams): LoadResult { + return try { + val page = params.key ?: 1 + val size = params.loadSize + val result = apiService.fetchDailyPraises(page = page, size = size).data + LoadResult.Page( + data = result.content.map { it.toDomain() }, + prevKey = if(result.page == 1) null else result.page - 1, + nextKey = if(result.hasNext) result.page + 1 else null + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/paging/FriendRepliesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/FriendRepliesPagingSource.kt index 8a23d86b..f86be0f0 100644 --- a/app/src/main/java/com/egobook/app/data/repository/paging/FriendRepliesPagingSource.kt +++ b/app/src/main/java/com/egobook/app/data/repository/paging/FriendRepliesPagingSource.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.delay class FriendRepliesPagingSource(private val apiService: QuestionApiService) : PagingSource() { - override fun getRefreshKey(state: PagingState): Int? { + override fun getRefreshKey(state: PagingState): Int { return 1 } @@ -35,7 +35,7 @@ class FriendRepliesPagingSource(private val apiService: QuestionApiService) : ) } - val hasNext = page < 5 + val hasNext = page < 2 LoadResult.Page( data = mockContent, prevKey = if (page == 1) null else page - 1, diff --git a/app/src/main/java/com/egobook/app/data/repository/paging/SentLettersPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/SentLettersPagingSource.kt new file mode 100644 index 00000000..a10e13df --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/paging/SentLettersPagingSource.kt @@ -0,0 +1,29 @@ +package com.egobook.app.data.repository.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.egobook.app.data.api.LetterApiService +import com.egobook.app.data.model.square.letter.toDomain +import com.egobook.app.domain.model.square.letter.SentLetterItem + +class SentLettersPagingSource(private val apiService: LetterApiService): PagingSource() { + override fun getRefreshKey(state: PagingState): Int { + return 1 + } + + override suspend fun load(params: LoadParams): LoadResult { + return try { + val page = params.key ?: 1 + val size = params.loadSize + val data = apiService.fetchSentLetters(page = page, size = size).data + LoadResult.Page( + data = data.content.map { it.toDomain() }, + prevKey = if(page == 1) null else page - 1, + nextKey = if(data.hasNext) page + 1 else null + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/repository/paging/WeeklyReportsPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/WeeklyReportsPagingSource.kt new file mode 100644 index 00000000..395e4d5b --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/paging/WeeklyReportsPagingSource.kt @@ -0,0 +1,29 @@ +package com.egobook.app.data.repository.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.egobook.app.data.api.CounselingApiService +import com.egobook.app.data.model.counseling.toDomain +import com.egobook.app.domain.model.counseling.WeeklyReport + +class WeeklyReportsPagingSource(private val apiService: CounselingApiService): PagingSource() { + override fun getRefreshKey(state: PagingState): Int { + return 1 + } + + override suspend fun load(params: LoadParams): LoadResult { + return try { + val page = params.key ?: 1 + val size = params.loadSize + val data = apiService.fetchWeeklyReports(page = page, size = size).data + LoadResult.Page( + data = data.content.map { it.toDomain() }, + prevKey = if(page == 1) null else page - 1, + nextKey = if(data.hasNext) page + 1 else null + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/DataStoreModule.kt b/app/src/main/java/com/egobook/app/di/module/DataStoreModule.kt similarity index 93% rename from app/src/main/java/com/egobook/app/di/DataStoreModule.kt rename to app/src/main/java/com/egobook/app/di/module/DataStoreModule.kt index e9f9bbdb..d4f7d528 100644 --- a/app/src/main/java/com/egobook/app/di/DataStoreModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/DataStoreModule.kt @@ -1,4 +1,4 @@ -package com.egobook.app.di +package com.egobook.app.di.module import android.content.Context import com.egobook.app.data.local.UserInfoStorage diff --git a/app/src/main/java/com/egobook/app/di/NetworkModule.kt b/app/src/main/java/com/egobook/app/di/module/NetworkModule.kt similarity index 76% rename from app/src/main/java/com/egobook/app/di/NetworkModule.kt rename to app/src/main/java/com/egobook/app/di/module/NetworkModule.kt index 9a28c379..7cc18f22 100644 --- a/app/src/main/java/com/egobook/app/di/NetworkModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/NetworkModule.kt @@ -1,9 +1,11 @@ package com.egobook.app.di import com.egobook.app.BuildConfig -import com.egobook.app.data.api.AuthApiService import com.egobook.app.data.interceptor.AuthInterceptor import com.egobook.app.data.interceptor.TokenAuthenticator +import com.egobook.app.di.qualifier.AIApi +import com.egobook.app.di.qualifier.AuthRetrofit +import com.egobook.app.di.qualifier.BackendApi import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides @@ -14,20 +16,17 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit -import javax.inject.Qualifier import javax.inject.Singleton -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class AuthRetrofit - @Module @InstallIn(SingletonComponent::class) object NetworkModule { + @Provides @Singleton - fun provideRetrofit( - client: OkHttpClient, + @BackendApi + fun provideBackendRetrofit( + @BackendApi client: OkHttpClient, gsonConverterFactory: GsonConverterFactory ): Retrofit { return Retrofit.Builder() @@ -39,34 +38,16 @@ object NetworkModule { @Provides @Singleton - fun provideGsonConverterFactory(): GsonConverterFactory { - return GsonConverterFactory.create( - GsonBuilder().create() - ) - } - - /** - * 토큰 갱신용 OkHttpClient (Authenticator 없음) - * 순환 의존성 방지를 위해 별도로 제공 - */ - @Provides - @Singleton - @AuthRetrofit - fun provideAuthOkHttpClient(): OkHttpClient { - val loggingInterceptor = HttpLoggingInterceptor().apply { - level = if (BuildConfig.DEBUG) { - HttpLoggingInterceptor.Level.BODY - } else { - HttpLoggingInterceptor.Level.NONE - } - } - - return OkHttpClient.Builder().apply { - connectTimeout(15, TimeUnit.SECONDS) - readTimeout(15, TimeUnit.SECONDS) - writeTimeout(15, TimeUnit.SECONDS) - addInterceptor(loggingInterceptor) - }.build() + @AIApi + fun provideAIRetrofit( + @AIApi client: OkHttpClient, + gsonConverterFactory: GsonConverterFactory + ): Retrofit { + return Retrofit.Builder() + .baseUrl(BuildConfig.AI_BASE_URL) + .addConverterFactory(gsonConverterFactory) + .client(client) + .build() } /** @@ -87,15 +68,27 @@ object NetworkModule { } /** - * 토큰 갱신용 AuthApiService + * 토큰 갱신용 OkHttpClient (Authenticator 없음) + * 순환 의존성 방지를 위해 별도로 제공 */ @Provides @Singleton @AuthRetrofit - fun provideAuthApiService( - @AuthRetrofit retrofit: Retrofit - ): AuthApiService { - return retrofit.create(AuthApiService::class.java) + fun provideAuthOkHttpClient(): OkHttpClient { + val loggingInterceptor = HttpLoggingInterceptor().apply { + level = if (BuildConfig.DEBUG) { + HttpLoggingInterceptor.Level.BODY + } else { + HttpLoggingInterceptor.Level.NONE + } + } + + return OkHttpClient.Builder().apply { + connectTimeout(15, TimeUnit.SECONDS) + readTimeout(15, TimeUnit.SECONDS) + writeTimeout(15, TimeUnit.SECONDS) + addInterceptor(loggingInterceptor) + }.build() } /** @@ -103,7 +96,8 @@ object NetworkModule { */ @Provides @Singleton - fun provideOkHttpClient( + @BackendApi + fun provideBackendOkHttpClient( authInterceptor: AuthInterceptor, tokenAuthenticator: TokenAuthenticator ): OkHttpClient { @@ -114,7 +108,6 @@ object NetworkModule { HttpLoggingInterceptor.Level.NONE } } - return OkHttpClient.Builder().apply { connectTimeout(15, TimeUnit.SECONDS) readTimeout(15, TimeUnit.SECONDS) @@ -125,4 +118,26 @@ object NetworkModule { }.build() } + @Provides + @Singleton + @AIApi + fun provideAIOkHttpClient( + authInterceptor: AuthInterceptor + ): OkHttpClient { + return OkHttpClient.Builder().apply { + connectTimeout(10, TimeUnit.SECONDS) + readTimeout(60, TimeUnit.SECONDS) + writeTimeout(60, TimeUnit.SECONDS) + addInterceptor(authInterceptor) + }.build() + } + + @Provides + @Singleton + fun provideGsonConverterFactory(): GsonConverterFactory { + return GsonConverterFactory.create( + GsonBuilder().create() + ) + } + } diff --git a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt similarity index 92% rename from app/src/main/java/com/egobook/app/di/RepositoryModule.kt rename to app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt index 0bba0d3f..a6a39116 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt @@ -2,6 +2,7 @@ package com.egobook.app.di import com.egobook.app.data.repository.CounselingRepositoryImpl import com.egobook.app.data.repository.FriendsRepositoryImpl +import com.egobook.app.data.repository.LetterRepositoryImpl import com.egobook.app.data.repository.NotificationRepositoryImpl import com.egobook.app.domain.repository.CounselingRepository import com.egobook.app.data.repository.auth.AuthRepositoryImpl @@ -9,6 +10,7 @@ import com.egobook.app.data.repository.QuestionRepositoryImpl import com.egobook.app.data.repository.account.AccountRepositoryImpl import com.egobook.app.data.repository.diary.DiaryRepositoryImpl import com.egobook.app.domain.repository.FriendsRepository +import com.egobook.app.domain.repository.LetterRepository import com.egobook.app.domain.repository.NotificationRepository import com.egobook.app.domain.repository.auth.AuthRepository import com.egobook.app.ui.shop.NetworkStoreRepository @@ -58,6 +60,10 @@ abstract class RepositoryModule { @Singleton abstract fun bindDiaryRepository(impl: DiaryRepositoryImpl): DiaryRepository + @Binds + @Singleton + abstract fun bindLetterRepository(impl: LetterRepositoryImpl): LetterRepository + @Binds @Singleton abstract fun bindStoreRepository(impl: NetworkStoreRepository): StoreRepository diff --git a/app/src/main/java/com/egobook/app/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt similarity index 51% rename from app/src/main/java/com/egobook/app/di/ServiceModule.kt rename to app/src/main/java/com/egobook/app/di/module/ServiceModule.kt index b33ec34a..f33a3e2b 100644 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt @@ -1,12 +1,17 @@ -package com.egobook.app.di +package com.egobook.app.di.module +import com.egobook.app.data.api.AIApiService import com.egobook.app.data.api.AccountApiService 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.LetterApiService import com.egobook.app.data.api.NotificationApiService import com.egobook.app.data.api.QuestionApiService +import com.egobook.app.di.qualifier.AIApi +import com.egobook.app.di.qualifier.AuthRetrofit +import com.egobook.app.di.qualifier.BackendApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -19,42 +24,53 @@ import javax.inject.Singleton object ServiceModule { @Provides @Singleton - fun provideCounselingService(retrofit: Retrofit): CounselingApiService { + fun provideCounselingService(@BackendApi retrofit: Retrofit): CounselingApiService { return retrofit.create(CounselingApiService::class.java) } @Provides @Singleton - fun provideNotificationService(retrofit: Retrofit): NotificationApiService { + fun provideNotificationService(@BackendApi retrofit: Retrofit): NotificationApiService { return retrofit.create(NotificationApiService::class.java) } @Provides @Singleton - fun provideFriendsService(retrofit: Retrofit): FriendsApiService { + fun provideFriendsService(@BackendApi retrofit: Retrofit): FriendsApiService { return retrofit.create(FriendsApiService::class.java) } @Provides @Singleton - fun provideQuestionService(retrofit: Retrofit): QuestionApiService { + fun provideQuestionService(@BackendApi retrofit: Retrofit): QuestionApiService { return retrofit.create(QuestionApiService::class.java) } @Provides @Singleton - fun provideAuthService(retrofit: Retrofit): AuthApiService = + fun provideLetterService(@BackendApi retrofit: Retrofit): LetterApiService { + return retrofit.create(LetterApiService::class.java) + } + + @Provides + @Singleton + fun provideAIService(@AIApi retrofit: Retrofit): AIApiService { + return retrofit.create(AIApiService::class.java) + } + + @Provides + @Singleton + fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthApiService = retrofit.create(AuthApiService::class.java) @Provides @Singleton - fun provideAccountService(retrofit: Retrofit): AccountApiService = + fun provideAccountService(@BackendApi retrofit: Retrofit): AccountApiService = retrofit.create(AccountApiService::class.java) @Provides @Singleton - fun provideDiaryService(retrofit: Retrofit): DiaryApiService = + fun provideDiaryService(@BackendApi retrofit: Retrofit): DiaryApiService = retrofit.create(DiaryApiService::class.java) - } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt b/app/src/main/java/com/egobook/app/di/module/UseCaseModule.kt similarity index 98% rename from app/src/main/java/com/egobook/app/di/UseCaseModule.kt rename to app/src/main/java/com/egobook/app/di/module/UseCaseModule.kt index 618dc029..9895dd13 100644 --- a/app/src/main/java/com/egobook/app/di/UseCaseModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/UseCaseModule.kt @@ -1,4 +1,4 @@ -package com.egobook.app.di +package com.egobook.app.di.module import com.egobook.app.domain.repository.diary.FakeDiaryRepository import com.egobook.app.domain.repository.auth.AuthRepository diff --git a/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt new file mode 100644 index 00000000..15a7de70 --- /dev/null +++ b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt @@ -0,0 +1,14 @@ +package com.egobook.app.di.qualifier + +import javax.inject.Qualifier + +@Qualifier +annotation class BackendApi + +@Qualifier +annotation class AIApi + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class AuthRetrofit + diff --git a/app/src/main/java/com/egobook/app/domain/model/ReportStyle.kt b/app/src/main/java/com/egobook/app/domain/model/ReportStyle.kt index 7b82310c..e2298d74 100644 --- a/app/src/main/java/com/egobook/app/domain/model/ReportStyle.kt +++ b/app/src/main/java/com/egobook/app/domain/model/ReportStyle.kt @@ -1,5 +1,7 @@ package com.egobook.app.domain.model -enum class ReportStyle { - SHARP, SOFT, OBJECTIVE -} \ No newline at end of file +enum class ReportStyle(val value: String) { + SHARP("SHARP"), + SOFT("SOFT"), + OBJECTIVE("OBJECTIVE") +} diff --git a/app/src/main/java/com/egobook/app/domain/model/WeeklyReport.kt b/app/src/main/java/com/egobook/app/domain/model/WeeklyReport.kt deleted file mode 100644 index e038d069..00000000 --- a/app/src/main/java/com/egobook/app/domain/model/WeeklyReport.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.egobook.app.domain.model - -data class WeeklyReport( - val id: Long, - val date: String, - val content: WeeklyReportContent -) - -data class WeeklyReportContent( - val analysis: String, - val praisePoint: String, - val improvement: String, - val management: String, - val encouragement: String -) diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/CounselingRewardType.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/CounselingRewardType.kt new file mode 100644 index 00000000..8d009fab --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/CounselingRewardType.kt @@ -0,0 +1,5 @@ +package com.egobook.app.domain.model.counseling + +enum class CounselingRewardType(val value: String, title: String) { + SELF_ESTEEM("SELF_ESTEEM", "자존감") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/DailyAndWeeklyNotification.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyAndWeeklyNotification.kt new file mode 100644 index 00000000..dc029306 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyAndWeeklyNotification.kt @@ -0,0 +1,6 @@ +package com.egobook.app.domain.model.counseling + +data class DailyAndWeeklyNotification( + val isDailyPraiseEnabled: Boolean, + val isWeeklyAnalysisEnabled: Boolean +) diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt new file mode 100644 index 00000000..307cf768 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt @@ -0,0 +1,7 @@ +package com.egobook.app.domain.model.counseling + +data class DailyPraise( + val id: Int, + val diaryDate: String, + val isRead: Boolean +) diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraiseDetail.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraiseDetail.kt new file mode 100644 index 00000000..cd78ebdb --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraiseDetail.kt @@ -0,0 +1,15 @@ +package com.egobook.app.domain.model.counseling + +data class DailyPraiseDetail( + val diaryDate: String, + val content: String, + val createdAt: String, + val isRead: Boolean, + val rewards: List? = null +) + +data class CounselingReward( + val kind: CounselingRewardType, + val amount: Int, + val toastMessage: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt new file mode 100644 index 00000000..36701d08 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.model.counseling + +data class WeeklyReport( + val id: Long, + val startDate: String, + val endDate: String, + val isRead: Boolean, + val isLocked: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportDetail.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportDetail.kt new file mode 100644 index 00000000..89d84281 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportDetail.kt @@ -0,0 +1,12 @@ +package com.egobook.app.domain.model.counseling + +data class WeeklyReportDetail( + val startDate: String, + val endDate: String, + val summary: String, + val praisePoints: String, + val improvementPoints: String, + val managementAdvice: String, + val supportMessage: String, + val isRead: Boolean +) diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportUnlockType.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportUnlockType.kt new file mode 100644 index 00000000..7e20863a --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportUnlockType.kt @@ -0,0 +1,6 @@ +package com.egobook.app.domain.model.counseling + +enum class WeeklyReportUnlockType(val value: String) { + INK("INK"), + AD("AD") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/AbusiveContentAnalysis.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/AbusiveContentAnalysis.kt new file mode 100644 index 00000000..04c07705 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/AbusiveContentAnalysis.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.model.square.letter + +data class AbusiveContentAnalysis( + val text: String, + val riskScore: Double, + val isHarmful: Boolean, + val label: String, + val detectedBadWords: List +) diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/ArrivedPendingLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/ArrivedPendingLetter.kt new file mode 100644 index 00000000..12bbcbe1 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ArrivedPendingLetter.kt @@ -0,0 +1,16 @@ +package com.egobook.app.domain.model.square.letter + +data class ArrivedPendingLetter( + val letter: ArrivedPendingLetterItem? = null +) + +data class ArrivedPendingLetterItem( + val letterId: Long, + val status: LetterStatus, + val mode: LetterMode, + val fromLabel: String, + val content: String, + val letterColor: LetterBackgroundColor, // TODO: 백엔드한테 필드 넣어달라고 하기 + val arrivedAt: String, + val replyDeadlineAt: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterBackgroundColor.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterBackgroundColor.kt new file mode 100644 index 00000000..5174b2c2 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterBackgroundColor.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.model.square.letter + +enum class LetterBackgroundColor(val value: String) { + WHITE("WHITE"), + PINK("PINK"), + GREEN("GREEN"), + BLUE("BLUE"), + PURPLE("PURPLE") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterMode.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterMode.kt new file mode 100644 index 00000000..2e1dc381 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterMode.kt @@ -0,0 +1,6 @@ +package com.egobook.app.domain.model.square.letter + +enum class LetterMode(val value: String) { + RANDOM("RANDOM"), + FRIEND("FRIEND") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterStatus.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterStatus.kt new file mode 100644 index 00000000..8cedfff0 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/LetterStatus.kt @@ -0,0 +1,10 @@ +package com.egobook.app.domain.model.square.letter + +enum class LetterStatus(val value: String) { + SENT("SENT"), + ARRIVED("ARRIVED"), + REPLIED("REPLIED"), + DEFERRED("DEFERRED"), + GAVE_UP("GAVE_UP"), + AI_REPLIED("AI_REPLIED") +} diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyLetter.kt new file mode 100644 index 00000000..8465e3f3 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyLetter.kt @@ -0,0 +1,16 @@ +package com.egobook.app.domain.model.square.letter + +import com.google.gson.annotations.SerializedName + +data class ReplyLetter( + val letterId: Long, + val status: LetterStatus, + val repliedAt: String, + val rewards: List +) + +data class ReplyLetterRewards( + val kind: ReplyReward, + val amount: Int, + val toastMessage: String? = null +) diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyReward.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyReward.kt new file mode 100644 index 00000000..468d4a88 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyReward.kt @@ -0,0 +1,6 @@ +package com.egobook.app.domain.model.square.letter + +enum class ReplyReward(val value: String, val label: String) { + INK("INK", "잉크"), + EMPATHY("EMPATHY", "공감성") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetter.kt new file mode 100644 index 00000000..a19a23e6 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetter.kt @@ -0,0 +1,6 @@ +package com.egobook.app.domain.model.square.letter + +data class ReportLetter( + val reason: ReportLetterType, + val description: String? = null +) diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetterType.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetterType.kt new file mode 100644 index 00000000..74701c27 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetterType.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.model.square.letter + +enum class ReportLetterType(val value: String) { + ABUSE("ABUSE"), // 비속어/욕설/모욕 + SPAM("SPAM"), // 광고/스팸 + INAPPROPRIATE("INAPPROPRIATE"), // 부적절한 콘텐츠 + OTHER("OTHER") // 기타 +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt new file mode 100644 index 00000000..2929d42f --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.model.square.letter + +data class SendLetter( + val mode: LetterMode, + val receiverId: Long? = null, + val content: String, + val letterColor: LetterBackgroundColor +) diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetter.kt new file mode 100644 index 00000000..5389dc44 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetter.kt @@ -0,0 +1,17 @@ +package com.egobook.app.domain.model.square.letter + +data class SentLetter( + val content: List, + val page: Int, + val size: Int, + val hasNext: Boolean +) + +data class SentLetterItem( + val letterId: Long, + val mode: LetterMode, + val status: LetterStatus, + val aiReplaceAt: String, + val content: String, + val createdAt: String +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetterWithReply.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetterWithReply.kt new file mode 100644 index 00000000..d0ca43a1 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetterWithReply.kt @@ -0,0 +1,21 @@ +package com.egobook.app.domain.model.square.letter + +data class SentLetterWithReply( + val letterId: Long, + val threadId: Long, + val status: LetterStatus, + val mode: LetterMode, + val sentContent: String, + val backgroundColor: LetterBackgroundColor, + val createdAt: String, + val arrivedAt: String, + val reply: LetterReply? = null +) + +data class LetterReply( + val replyId: Long, + val replyContent: String, + val isAIGenerated: Boolean, + val isReported: Boolean, + val repliedAt: String +) diff --git a/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index 265d5981..2be0ffab 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt @@ -1,15 +1,27 @@ package com.egobook.app.domain.repository -import com.egobook.app.domain.model.PraiseMessage -import com.egobook.app.domain.model.WeeklyReport +import androidx.paging.PagingData +import com.egobook.app.domain.model.counseling.WeeklyReport import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.model.Statistics import com.egobook.app.domain.model.WeeklyReportStyle +import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification +import com.egobook.app.domain.model.counseling.DailyPraise +import com.egobook.app.domain.model.counseling.DailyPraiseDetail +import com.egobook.app.domain.model.counseling.WeeklyReportDetail +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType +import kotlinx.coroutines.flow.Flow interface CounselingRepository { - suspend fun getDailyPraise(): Result> - suspend fun getWeeklyReport(): Result> - suspend fun getWeeklyReportStyle(): Result + fun getDailyPraise(size: Int): Flow> + suspend fun getDailyPraiseByDate(date: String): Result + fun getWeeklyReports(size: Int): Flow> + suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result suspend fun getStatistics(): Result + suspend fun getDailyAndWeeklyNotification(): Result + suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result + suspend fun updateWeeklyReportNotification(isEnabled: Boolean): Result + suspend fun getWeeklyReportByDate(startDate: String): Result + suspend fun unlockWeeklyReport(startDate: String, unlockType: WeeklyReportUnlockType): Result } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt new file mode 100644 index 00000000..976623f6 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -0,0 +1,24 @@ +package com.egobook.app.domain.repository + +import androidx.paging.PagingData +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetter +import com.egobook.app.domain.model.square.letter.ReplyLetter +import com.egobook.app.domain.model.square.letter.ReportLetter +import com.egobook.app.domain.model.square.letter.SendLetter +import com.egobook.app.domain.model.square.letter.SentLetterItem +import com.egobook.app.domain.model.square.letter.SentLetterWithReply +import kotlinx.coroutines.flow.Flow + +interface LetterRepository { + suspend fun sendLetter(letter: SendLetter): Result + suspend fun detectAbusiveContent(text: String): Result + suspend fun fetchArrivedPendingLetter(): Result + suspend fun replyLetter(letterId: Long, text: String): Result + suspend fun deferReplyLetter(letterId: Long): Result + suspend fun giveUpReplyLetter(letterId: Long): Result + fun fetchSentLetters(size: Int): Flow> + suspend fun fetchSentLetterWithReply(letterId: Long): Result + suspend fun reportRepliedLetter(replyId: Long, reportLetter: ReportLetter): Result + suspend fun deleteLetterThread(threadId: Long): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt deleted file mode 100644 index 907eb8da..00000000 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.egobook.app.domain.usecase - -import com.egobook.app.domain.model.PraiseMessage -import com.egobook.app.domain.repository.CounselingRepository -import javax.inject.Inject - -class GetDailyPraiseUseCase @Inject constructor( - private val repository: CounselingRepository -) { - suspend operator fun invoke(): Result> = repository.getDailyPraise() -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/GetUserInfoUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/GetUserInfoUseCase.kt new file mode 100644 index 00000000..5e48bc27 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/GetUserInfoUseCase.kt @@ -0,0 +1,16 @@ +package com.egobook.app.domain.usecase + +import com.egobook.app.ui.home.user.User +import com.egobook.app.ui.home.repository.UserRepository +import javax.inject.Inject + +class GetUserInfoUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(): Result = try { + val user = userRepository.load() + Result.success(user) + } catch (e: Exception) { + Result.failure(e) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportUseCase.kt deleted file mode 100644 index dd33b707..00000000 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportUseCase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.egobook.app.domain.usecase - -import com.egobook.app.domain.model.WeeklyReport -import com.egobook.app.domain.repository.CounselingRepository -import javax.inject.Inject - -class GetWeeklyReportUseCase @Inject constructor(private val repository: CounselingRepository) { - suspend operator fun invoke(): Result> = repository.getWeeklyReport() -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt new file mode 100644 index 00000000..0336f541 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt @@ -0,0 +1,11 @@ +package com.egobook.app.domain.usecase + +import androidx.paging.PagingData +import com.egobook.app.domain.model.counseling.WeeklyReport +import com.egobook.app.domain.repository.CounselingRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetWeeklyReportsUseCase @Inject constructor(private val repository: CounselingRepository) { + operator fun invoke(size: Int): Flow> = repository.getWeeklyReports(size = size) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyAndWeeklyNotificationUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyAndWeeklyNotificationUseCase.kt new file mode 100644 index 00000000..4375f434 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyAndWeeklyNotificationUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class GetDailyAndWeeklyNotificationUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke() = repository.getDailyAndWeeklyNotification() +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseByDateUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseByDateUseCase.kt new file mode 100644 index 00000000..dd8a75f1 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseByDateUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class GetDailyPraiseByDateUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke(date: String) = repository.getDailyPraiseByDate(date = date) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt new file mode 100644 index 00000000..82b627ec --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt @@ -0,0 +1,13 @@ +package com.egobook.app.domain.usecase.egoroom + +import androidx.paging.PagingData +import com.egobook.app.domain.model.counseling.DailyPraise +import com.egobook.app.domain.repository.CounselingRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetDailyPraiseUseCase @Inject constructor( + private val repository: CounselingRepository +) { + operator fun invoke(size: Int): Flow> = repository.getDailyPraise(size = size) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportByDateUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportByDateUseCase.kt new file mode 100644 index 00000000..ec970214 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportByDateUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class GetWeeklyReportByDateUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke(startDate: String) = repository.getWeeklyReportByDate(startDate = startDate) +} diff --git a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportStyleUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportStyleUseCase.kt similarity index 57% rename from app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportStyleUseCase.kt rename to app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportStyleUseCase.kt index 03f95dcb..0276282b 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportStyleUseCase.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportStyleUseCase.kt @@ -1,5 +1,6 @@ -package com.egobook.app.domain.usecase +package com.egobook.app.domain.usecase.egoroom +import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.model.WeeklyReportStyle import com.egobook.app.domain.repository.CounselingRepository import javax.inject.Inject @@ -7,6 +8,6 @@ import javax.inject.Inject class GetWeeklyReportStyleUseCase @Inject constructor( private val repository: CounselingRepository ) { - suspend operator fun invoke(): Result = repository.getWeeklyReportStyle() + suspend operator fun invoke(): Result = repository.getWeeklyReportStyle() } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UnlockWeeklyReportUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UnlockWeeklyReportUseCase.kt new file mode 100644 index 00000000..afe5e977 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UnlockWeeklyReportUseCase.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class UnlockWeeklyReportUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke(startDate: String, unlockType: WeeklyReportUnlockType) = repository.unlockWeeklyReport(startDate, unlockType) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateDailyPraiseNotificationUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateDailyPraiseNotificationUseCase.kt new file mode 100644 index 00000000..964f243c --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateDailyPraiseNotificationUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class UpdateDailyPraiseNotificationUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke(isEnabled: Boolean): Result = repository.updateDailyPraiseNotification(isEnabled = isEnabled) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportNotificationUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportNotificationUseCase.kt new file mode 100644 index 00000000..f81d8c9e --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportNotificationUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.egoroom + +import com.egobook.app.domain.repository.CounselingRepository +import javax.inject.Inject + +class UpdateWeeklyReportNotificationUseCase @Inject constructor(private val repository: CounselingRepository) { + suspend operator fun invoke(isEnabled: Boolean) = repository.updateWeeklyReportNotification(isEnabled = isEnabled) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/UpdateWeeklyReportStyleUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportStyleUseCase.kt similarity index 89% rename from app/src/main/java/com/egobook/app/domain/usecase/UpdateWeeklyReportStyleUseCase.kt rename to app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportStyleUseCase.kt index 5f5a3357..6d05903a 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/UpdateWeeklyReportStyleUseCase.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportStyleUseCase.kt @@ -1,4 +1,4 @@ -package com.egobook.app.domain.usecase +package com.egobook.app.domain.usecase.egoroom import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.repository.CounselingRepository diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/DeferReplyLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/DeferReplyLetterUseCase.kt new file mode 100644 index 00000000..4e3d403b --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/DeferReplyLetterUseCase.kt @@ -0,0 +1,10 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class DeferReplyLetterUseCase @Inject constructor( + private val repository: LetterRepository +) { + suspend operator fun invoke(letterId: Long): Result = repository.deferReplyLetter(letterId = letterId) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/DeleteLetterThreadUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/DeleteLetterThreadUseCase.kt new file mode 100644 index 00000000..cbdc1e49 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/DeleteLetterThreadUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class DeleteLetterThreadUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(threadId: Long): Result = repository.deleteLetterThread(threadId = threadId) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/DetectAbusiveContentUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/DetectAbusiveContentUseCase.kt new file mode 100644 index 00000000..2c679827 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/DetectAbusiveContentUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class DetectAbusiveContentUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(text: String) = repository.detectAbusiveContent(text = text) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/GetArrivedPendingLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetArrivedPendingLetterUseCase.kt new file mode 100644 index 00000000..9cf87b76 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetArrivedPendingLetterUseCase.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetter +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class GetArrivedPendingLetterUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke():Result = repository.fetchArrivedPendingLetter() +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLetterWithReplyUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLetterWithReplyUseCase.kt new file mode 100644 index 00000000..d1ff3719 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLetterWithReplyUseCase.kt @@ -0,0 +1,8 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class GetSentLetterWithReplyUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(letterId: Long) = repository.fetchSentLetterWithReply(letterId) +} diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLettersUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLettersUseCase.kt new file mode 100644 index 00000000..8dc6d9e0 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLettersUseCase.kt @@ -0,0 +1,13 @@ +package com.egobook.app.domain.usecase.letter + +import androidx.paging.PagingData +import com.egobook.app.domain.model.square.letter.SentLetterItem +import com.egobook.app.domain.repository.LetterRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetSentLettersUseCase @Inject constructor( + private val repository: LetterRepository +) { + operator fun invoke(size: Int): Flow> = repository.fetchSentLetters(size = size) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/GiveUpReplyLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/GiveUpReplyLetterUseCase.kt new file mode 100644 index 00000000..726759bc --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/GiveUpReplyLetterUseCase.kt @@ -0,0 +1,10 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class GiveUpReplyLetterUseCase @Inject constructor( + private val repository: LetterRepository +) { + suspend operator fun invoke(letterId: Long): Result = repository.giveUpReplyLetter(letterId = letterId) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/ReplyLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/ReplyLetterUseCase.kt new file mode 100644 index 00000000..1791c93b --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/ReplyLetterUseCase.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.model.square.letter.ReplyLetter +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class ReplyLetterUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(letterId: Long, text: String): Result = repository.replyLetter(letterId, text) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/ReportRepliedLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/ReportRepliedLetterUseCase.kt new file mode 100644 index 00000000..7ca6768e --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/ReportRepliedLetterUseCase.kt @@ -0,0 +1,11 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.model.square.letter.ReportLetter +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class ReportRepliedLetterUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(replyId: Long, reportLetter: ReportLetter): Result = + repository.reportRepliedLetter(replyId = replyId, reportLetter = reportLetter) +} + diff --git a/app/src/main/java/com/egobook/app/domain/usecase/letter/SendLetterUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/letter/SendLetterUseCase.kt new file mode 100644 index 00000000..c5d3ad8e --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/usecase/letter/SendLetterUseCase.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.usecase.letter + +import com.egobook.app.domain.model.square.letter.SendLetter +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class SendLetterUseCase @Inject constructor(private val repository: LetterRepository) { + suspend operator fun invoke(letter: SendLetter): Result = repository.sendLetter(letter = letter) +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt index a4a86e62..6cee645e 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt @@ -2,24 +2,50 @@ package com.egobook.app.ui.counseling.adapter import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.Toast 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.ItemCounselingDailyPraiseBinding -import com.egobook.app.ui.counseling.model.PraiseMessageModel +import com.egobook.app.ui.counseling.model.DailyPraiseDetailModel +import com.egobook.app.ui.counseling.model.PraiseDailyModel -class CounselingDailyPraiseAdapter: ListAdapter(diffUtil) { +class CounselingDailyPraiseAdapter(private val onItemClicked: (String) -> Unit): PagingDataAdapter(diffUtil) { + + // 날짜별 상세 데이터를 저장할 별도 공간 + private val detailsMap = mutableMapOf() inner class PraiseViewHolder(private val binding: ItemCounselingDailyPraiseBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: PraiseMessageModel) = with(binding) { - tvCounselingDailyPraiseDatetime.text = item.formattedDate - tvCounselingDailyPraiseContent.text = item.messageText + fun bind(item: PraiseDailyModel) = with(binding) { + tvCounselingDailyPraiseDatetime.text = item.diaryDate + + val detail = detailsMap[item.diaryDate] + if(detail != null) { + // 클릭한 경우 + cvCounselingDailyPraiseContent.isVisible = true + tvCounselingDailyPraiseContent.text = detail.content + ivCounselingDailyPraiseToggle.setImageResource(R.drawable.ic_chevron_up) + if(!detail.isRead) { + Toast.makeText(root.context, detail.rewards?.first()?.toastMessage, Toast.LENGTH_SHORT).show() + // TODO: 실제 유저의 자존감 능력치 올리기 + } + } else { + // 아직 클릭하지 않은 경우 + cvCounselingDailyPraiseContent.isVisible = false + ivCounselingDailyPraiseToggle.setImageResource(R.drawable.ic_chevron_down) + } + root.setOnClickListener { - cvCounselingDailyPraiseContent.isVisible = !cvCounselingDailyPraiseContent.isVisible - ivCounselingDailyPraiseToggle.setImageResource(if(cvCounselingDailyPraiseContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down) + if(detailsMap.containsKey(item.diaryDate)) { + // 열린 상태 → 닫아야 함 + detailsMap.remove(item.diaryDate) + notifyItemChanged(bindingAdapterPosition) + } else { + onItemClicked(item.diaryDate) + } } } @@ -35,16 +61,28 @@ class CounselingDailyPraiseAdapter: ListAdapter() { - override fun areItemsTheSame(oldItem: PraiseMessageModel, newItem: PraiseMessageModel): Boolean { + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: PraiseDailyModel, newItem: PraiseDailyModel): Boolean { return oldItem.id == newItem.id } - override fun areContentsTheSame(oldItem: PraiseMessageModel, newItem: PraiseMessageModel): Boolean { + override fun areContentsTheSame(oldItem: PraiseDailyModel, newItem: PraiseDailyModel): Boolean { return oldItem == newItem } } diff --git a/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingWeeklyReportAdapter.kt b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingWeeklyReportAdapter.kt index 4c4fa975..8f7b2933 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingWeeklyReportAdapter.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingWeeklyReportAdapter.kt @@ -2,18 +2,22 @@ package com.egobook.app.ui.counseling.adapter import android.view.LayoutInflater import android.view.ViewGroup +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.ItemCounselingWeeklyReportBinding import com.egobook.app.ui.counseling.model.WeeklyReportModel -class CounselingWeeklyReportAdapter(private val onItemClick: (WeeklyReportModel) -> Unit) : ListAdapter(diffUtil) { +class CounselingWeeklyReportAdapter(private val onItemClick: (WeeklyReportModel) -> Unit) : PagingDataAdapter (diffUtil) { inner class WeeklyReportViewHolder(private val binding: ItemCounselingWeeklyReportBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: WeeklyReportModel) = with(binding) { - tvCounselingWeeklyReportDatetime.text = item.date + tvCounselingWeeklyReportDatetime.text = "${item.startDate} ~ ${item.endDate}" + val showMoreIcon = if(item.isLocked) R.drawable.ic_lock_key else R.drawable.ic_chevron_right + ivCounselingWeeklyReportViewMore.setImageResource(showMoreIcon) root.setOnClickListener { onItemClick(item) } @@ -31,7 +35,8 @@ class CounselingWeeklyReportAdapter(private val onItemClick: (WeeklyReportModel) } override fun onBindViewHolder(holder: WeeklyReportViewHolder, position: Int) { - holder.bind(getItem(position)) + val item = getItem(position) + if(item != null) (holder.bind(item)) } companion object { diff --git a/app/src/main/java/com/egobook/app/ui/counseling/model/DailyAndWeeklyNotificationModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/DailyAndWeeklyNotificationModel.kt new file mode 100644 index 00000000..d930f1b3 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/model/DailyAndWeeklyNotificationModel.kt @@ -0,0 +1,13 @@ +package com.egobook.app.ui.counseling.model + +import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification + +data class DailyAndWeeklyNotificationModel( + val isDailyPraiseEnabled: Boolean, + val isWeeklyAnalysisEnabled: Boolean +) + +fun DailyAndWeeklyNotification.toPresentation() = DailyAndWeeklyNotificationModel( + isDailyPraiseEnabled = isDailyPraiseEnabled, + isWeeklyAnalysisEnabled = isWeeklyAnalysisEnabled +) diff --git a/app/src/main/java/com/egobook/app/ui/counseling/model/DailyPraiseDetailModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/DailyPraiseDetailModel.kt new file mode 100644 index 00000000..ac2a014d --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/model/DailyPraiseDetailModel.kt @@ -0,0 +1,33 @@ +package com.egobook.app.ui.counseling.model + +import com.egobook.app.domain.model.counseling.CounselingReward +import com.egobook.app.domain.model.counseling.CounselingRewardType +import com.egobook.app.domain.model.counseling.DailyPraiseDetail + +data class DailyPraiseDetailModel( + val diaryDate: String, + val content: String, + val createdAt: String, + val isRead: Boolean, + val rewards: List? = null +) + +data class CounselingRewardModel( + val kind: CounselingRewardType, + val amount: Int, + val toastMessage: String +) + +fun CounselingReward.toPresentation() = CounselingRewardModel( + kind = kind, + amount = amount, + toastMessage = toastMessage +) + +fun DailyPraiseDetail.toPresentation() = DailyPraiseDetailModel( + diaryDate = diaryDate, + content = content, + createdAt = createdAt, + isRead = isRead, + rewards = rewards?.map { it.toPresentation() } +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/model/PraiseDailyModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/PraiseDailyModel.kt new file mode 100644 index 00000000..5f062cb8 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/model/PraiseDailyModel.kt @@ -0,0 +1,15 @@ +package com.egobook.app.ui.counseling.model + +import com.egobook.app.domain.model.counseling.DailyPraise + +data class PraiseDailyModel( + val id: Int, + val diaryDate: String, + val isRead: Boolean +) + +fun DailyPraise.toPresentation(): PraiseDailyModel = PraiseDailyModel( + id = id, + diaryDate = diaryDate, + isRead = isRead +) diff --git a/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportDetailModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportDetailModel.kt new file mode 100644 index 00000000..0a690b87 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportDetailModel.kt @@ -0,0 +1,25 @@ +package com.egobook.app.ui.counseling.model + +import com.egobook.app.domain.model.counseling.WeeklyReportDetail + +data class WeeklyReportDetailModel( + val startDate: String, + val endDate: String, + val summary: String, + val praisePoints: String, + val improvementPoints: String, + val managementAdvice: String, + val supportMessage: String, + val isRead: Boolean +) + +fun WeeklyReportDetail.toPresentation() = WeeklyReportDetailModel( + startDate = startDate, + endDate = endDate, + summary = summary, + praisePoints = praisePoints, + improvementPoints = improvementPoints, + managementAdvice = managementAdvice, + supportMessage = supportMessage, + isRead = isRead +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportModel.kt index c70d7e72..db47fb20 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportModel.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportModel.kt @@ -1,33 +1,22 @@ package com.egobook.app.ui.counseling.model import android.os.Parcelable -import com.egobook.app.domain.model.WeeklyReport +import com.egobook.app.domain.model.counseling.WeeklyReport import kotlinx.parcelize.Parcelize @Parcelize data class WeeklyReportModel( val id: Long, - val date: String, - val content: WeeklyReportContentModel -): Parcelable - -@Parcelize -data class WeeklyReportContentModel( - val analysis: String, - val praisePoint: String, - val improvement: String, - val management: String, - val encouragement: String + val startDate: String, // 사용 완료 + val endDate: String, // 사용 완료 + val isRead: Boolean, + val isLocked: Boolean // 사용 완료 ): Parcelable fun WeeklyReport.toPresentation(): WeeklyReportModel = WeeklyReportModel( id = id, - date = date, - content = WeeklyReportContentModel( - analysis = content.analysis, - praisePoint = content.praisePoint, - improvement = content.improvement, - management = content.management, - encouragement = content.encouragement - ) + startDate = startDate, + endDate = endDate, + isRead = isRead, + isLocked = isLocked ) diff --git a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt index dd87a4e2..ce2d77ae 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt @@ -6,16 +6,20 @@ import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.paging.LoadState import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.egobook.app.R import com.egobook.app.databinding.FragmentEgoRoomDailyPraiseBinding import com.egobook.app.domain.model.NotificationType import com.egobook.app.ui.counseling.adapter.CounselingDailyPraiseAdapter +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel +import com.egobook.app.ui.counseling.model.DailyPraiseDetailModel import com.egobook.app.ui.counseling.viewmodel.DailyPraiseViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState @@ -27,7 +31,9 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private lateinit var binding: FragmentEgoRoomDailyPraiseBinding private val viewModel: DailyPraiseViewModel by viewModels() - private val counselingDailyPraiseAdapter = CounselingDailyPraiseAdapter() + private val counselingDailyPraiseAdapter = CounselingDailyPraiseAdapter { diaryDate -> + viewModel.fetchDailyPraiseByData(date = diaryDate) + } private var isNotificationEnabled: Boolean? = null @@ -53,10 +59,14 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private fun initListeners() = with(binding) { ivCounselingDailyPraiseNotification.setOnClickListener { - if(isNotificationEnabled == true) { - viewModel.updateNotificationStatus(type = NotificationType.DAILY_PRAISE, isEnabled = false) + if (isNotificationEnabled == true) { + viewModel.updateDailyPraiseNotification( + isEnabled = false + ) } else { - viewModel.updateNotificationStatus(type = NotificationType.DAILY_PRAISE, isEnabled = true) + viewModel.updateDailyPraiseNotification( + isEnabled = true + ) } } } @@ -65,67 +75,90 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.dailyPraise.collect { state -> - when(state) { - UiState.Loading -> { /* 프로그래스바 표시 */ } - is UiState.Success -> { - val messageList = state.data - counselingDailyPraiseAdapter.submitList(messageList) - recyclerviewCounselingDailyPraise.isVisible = !messageList.isEmpty() - llCounselingDailyPraisePlaceholder.isVisible = messageList.isEmpty() - } - is UiState.Failure -> { - Toast.makeText(context, state.message, Toast.LENGTH_SHORT).show() - } - else -> {} - } + viewModel.dailyPraiseList.collect { pagingData -> + counselingDailyPraiseAdapter.submitData(lifecycle, pagingData) } } + /** + * isListEmpty 변수에 대한 설명 추가하기 + */ launch { - viewModel.notificationStatus.collect { state -> - when(state) { + counselingDailyPraiseAdapter.loadStateFlow.collect { loadStates -> + val isListEmpty = loadStates.source.refresh is LoadState.NotLoading && loadStates.append.endOfPaginationReached && counselingDailyPraiseAdapter.itemCount == 0 + recyclerviewCounselingDailyPraise.isVisible = !isListEmpty + llCounselingDailyPraisePlaceholder.isVisible = isListEmpty + } + } + launch { + viewModel.dailyAndWeeklyNotification.collect { state -> + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} - is UiState.Success -> { - val notificationStatus: NotificationModel = state.data + is UiState.Success -> { + val notificationStatus: DailyAndWeeklyNotificationModel = state.data updateNotificationUi(notificationStatus.isDailyPraiseEnabled) } } } } launch { - viewModel.updateNotificationResult.collect { state -> - when(state) { + viewModel.updateNotificationStatus.collect { state -> + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} is UiState.Success -> { val isEnabled = state.data updateNotificationUi(isEnabled) - val toastMessage = if(isEnabled) R.string.notification_on else R.string.notification_off + val toastMessage = if (isEnabled) R.string.notification_on else R.string.notification_off Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show() } } } } + launch { + viewModel.dailyPraiseByDate.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val detailInfo = state.data + counselingDailyPraiseAdapter.updateItem(item = detailInfo) + } + } + } + } } } } private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { this@EgoRoomDailyPraiseFragment.isNotificationEnabled = isEnabled - if(isEnabled) { - tvCounselingDailyPraiseNotification.text = getString(R.string.counseling_daily_praise_notification_on) - ivCounselingDailyPraiseNotification.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_on)) + if (isEnabled) { + tvCounselingDailyPraiseNotification.text = + getString(R.string.counseling_daily_praise_notification_on) + ivCounselingDailyPraiseNotification.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_notification_on + ) + ) } else { - tvCounselingDailyPraiseNotification.text = getString(R.string.counseling_daily_praise_notification_off) - ivCounselingDailyPraiseNotification.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_off)) + tvCounselingDailyPraiseNotification.text = + getString(R.string.counseling_daily_praise_notification_off) + ivCounselingDailyPraiseNotification.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_notification_off + ) + ) } } private fun fetchData() { - viewModel.fetchDailyPraise() - viewModel.fetchNotificationStatus() + viewModel.fetchDailyPraises(size = 10) + viewModel.getDailyAndWeeklyNotification() } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportDetailFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportDetailFragment.kt index 004fb25e..07ef9fed 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportDetailFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportDetailFragment.kt @@ -3,37 +3,67 @@ package com.egobook.app.ui.counseling.view import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.egobook.app.R import com.egobook.app.databinding.FragmentEgoRoomWeeklyReportDetailBinding -import com.egobook.app.ui.counseling.model.WeeklyReportModel +import com.egobook.app.ui.counseling.model.WeeklyReportDetailModel +import com.egobook.app.ui.counseling.viewmodel.WeeklyReportViewModel +import com.egobook.app.util.UiState import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint class EgoRoomWeeklyReportDetailFragment : Fragment(R.layout.fragment_ego_room_weekly_report_detail) { private lateinit var binding: FragmentEgoRoomWeeklyReportDetailBinding - private val args: EgoRoomWeeklyReportDetailFragmentArgs by navArgs() + private val startDate: String by lazy { + val args: EgoRoomWeeklyReportDetailFragmentArgs by navArgs() + args.startDate + } + private val viewModel: WeeklyReportViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentEgoRoomWeeklyReportDetailBinding.bind(view) - val weeklyReportItem: WeeklyReportModel = args.weeklyReportItem - initViews(weeklyReportItem) + fetchData() initListeners() + initObservers() } - private fun initViews(item: WeeklyReportModel) = with(binding) { - tvCounselingWeeklyReportDetailDatetime.text = item.date - tvCounselingWeeklyReportDetailAnalysis.text = item.content.analysis - tvCounselingWeeklyReportDetailPraisePoint.text = item.content.praisePoint - tvCounselingWeeklyReportDetailImprovement.text = item.content.improvement - tvCounselingWeeklyReportDetailManagement.text = item.content.management - tvCounselingWeeklyReportDetailEncouragement.text = item.content.encouragement + private fun fetchData() { + viewModel.getWeeklyReportByDate(startDate = startDate) } + private fun initListeners() = with(binding) { ivCounselingWeeklyReportDetailBack.setOnClickListener { findNavController().popBackStack() } } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.weeklyReportByDate.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val reportDetailItem = state.data + tvCounselingWeeklyReportDetailDatetime.text = "${reportDetailItem.startDate} ~ ${reportDetailItem.endDate}" + tvCounselingWeeklyReportDetailSummary.text = reportDetailItem.summary + tvCounselingWeeklyReportDetailPraisePoint.text = reportDetailItem.praisePoints + tvCounselingWeeklyReportDetailImprovement.text = reportDetailItem.improvementPoints + tvCounselingWeeklyReportDetailManagement.text = reportDetailItem.managementAdvice + tvCounselingWeeklyReportDetailEncouragement.text = reportDetailItem.supportMessage + } + } + } + } + } + } } diff --git a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt index 042d3fd4..fa2b67dc 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt @@ -6,11 +6,13 @@ import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels 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.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.egobook.app.R @@ -18,20 +20,28 @@ import com.egobook.app.databinding.FragmentEgoRoomWeeklyReportBinding import com.egobook.app.domain.model.NotificationType import com.egobook.app.domain.model.ReportStyle import com.egobook.app.ui.counseling.adapter.CounselingWeeklyReportAdapter +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel import com.egobook.app.ui.counseling.model.WeeklyReportStyleModel import com.egobook.app.ui.counseling.viewmodel.WeeklyReportViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlin.getValue @AndroidEntryPoint class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_report) { private lateinit var binding: FragmentEgoRoomWeeklyReportBinding private val viewModel: WeeklyReportViewModel by viewModels() private val counselingWeeklyReportAdapter = CounselingWeeklyReportAdapter { item -> - val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(weeklyReportItem = item) - findNavController().navigate(action) + if(item.isLocked) { + val dialog = WeeklyReportUnlockDialog(startDate = item.startDate) + dialog.show(childFragmentManager, WeeklyReportUnlockDialog.TAG) + } else { + val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(startDate = item.startDate) + findNavController().navigate(action) + } } private var isNotificationEnabled: Boolean? = null @@ -67,15 +77,9 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } ivCounselingWeeklyReportNotification.setOnClickListener { if (isNotificationEnabled == true) { - viewModel.updateNotificationStatus( - type = NotificationType.WEEKLY_REPORT, - isEnabled = false - ) + viewModel.updateWeeklyReportNotification(isEnabled = false) } else { - viewModel.updateNotificationStatus( - type = NotificationType.WEEKLY_REPORT, - isEnabled = true - ) + viewModel.updateWeeklyReportNotification(isEnabled = true) } } } @@ -84,45 +88,41 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.weeklyReportList.collect { state -> - when(state) { - UiState.Loading -> {} - is UiState.Success -> { - val weeklyReportList = state.data - counselingWeeklyReportAdapter.submitList(weeklyReportList) - recyclerviewCounselingWeeklyReport.isVisible = !weeklyReportList.isEmpty() - llCounselingWeeklyReportPlaceholder.isVisible = weeklyReportList.isEmpty() - } - is UiState.Failure -> { - Toast.makeText(context, state.message, Toast.LENGTH_SHORT).show() - } - else -> {} - } + viewModel.weeklyReportList.collectLatest { pagingData -> + counselingWeeklyReportAdapter.submitData(lifecycle, pagingData) } } launch { - viewModel.notificationStatus.collect { state -> - when(state) { + counselingWeeklyReportAdapter.loadStateFlow.collect { loadStates -> + val isListEmpty = loadStates.source.refresh is LoadState.NotLoading && loadStates.append.endOfPaginationReached && counselingWeeklyReportAdapter.itemCount == 0 + recyclerviewCounselingWeeklyReport.isVisible = !isListEmpty + llCounselingWeeklyReportPlaceholder.isVisible = isListEmpty + } + } + launch { + viewModel.dailyAndWeeklyNotification.collect { state -> + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} - is UiState.Success -> { + is UiState.Success -> { val notificationStatus = state.data - updateNotificationUi(notificationStatus.isWeeklyReportEnabled) + updateNotificationUi(notificationStatus.isWeeklyAnalysisEnabled) } } } } launch { - viewModel.updateNotificationResult.collect { state -> - when(state) { + viewModel.updateNotificationStatus.collect { state -> + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} is UiState.Success -> { val isEnabled = state.data updateNotificationUi(isEnabled) - val toastMessage = if(isEnabled) R.string.notification_on else R.string.notification_off + val toastMessage = + if (isEnabled) R.string.notification_on else R.string.notification_off Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show() } } @@ -130,12 +130,12 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } launch { viewModel.weeklyReportStyle.collect { state -> - when(state) { + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} - is UiState.Success -> { - val reportStyle = state.data.type + is UiState.Success -> { + val reportStyle = state.data updateReportStyleUi(reportStyle = reportStyle) } } @@ -143,7 +143,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } launch { viewModel.updateReportStyleResult.collect { state -> - when(state) { + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} @@ -161,12 +161,24 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { this@EgoRoomWeeklyReportFragment.isNotificationEnabled = isEnabled - if(isEnabled) { - tvCounselingWeeklyReportNotification.text = getString(R.string.counseling_weekly_report_notification_on) - ivCounselingWeeklyReportNotification.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_on)) + if (isEnabled) { + tvCounselingWeeklyReportNotification.text = + getString(R.string.counseling_weekly_report_notification_on) + ivCounselingWeeklyReportNotification.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_notification_on + ) + ) } else { - tvCounselingWeeklyReportNotification.text = getString(R.string.counseling_weekly_report_notification_off) - ivCounselingWeeklyReportNotification.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_off)) + tvCounselingWeeklyReportNotification.text = + getString(R.string.counseling_weekly_report_notification_off) + ivCounselingWeeklyReportNotification.setImageDrawable( + ContextCompat.getDrawable( + requireContext(), + R.drawable.ic_notification_off + ) + ) } } @@ -177,8 +189,8 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } private fun fetchData() { - viewModel.fetchWeeklyReport() - viewModel.fetchNotificationStatus() + viewModel.getDailyAndWeeklyNotification() + viewModel.fetchWeeklyReport(size = 10) viewModel.fetchWeeklyReportStyle() } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/view/WeeklyReportUnlockDialog.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/WeeklyReportUnlockDialog.kt new file mode 100644 index 00000000..d89793d9 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/view/WeeklyReportUnlockDialog.kt @@ -0,0 +1,92 @@ +package com.egobook.app.ui.counseling.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import com.egobook.app.R +import com.egobook.app.databinding.DialogWeeklyReportUnlockBinding +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType +import com.egobook.app.ui.counseling.viewmodel.WeeklyReportViewModel +import com.egobook.app.ui.home.user.User +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch + +class WeeklyReportUnlockDialog(private val startDate: String): DialogFragment(R.layout.dialog_weekly_report_unlock) { + private lateinit var binding: DialogWeeklyReportUnlockBinding + private val viewModel: WeeklyReportViewModel by activityViewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogWeeklyReportUnlockBinding.bind(view) + initListeners() + initObservers() + } + + private fun initListeners() = with(binding) { + btnWeeklyReportUnlockUseInk.setOnClickListener { + viewModel.getUserInfo() + } + btnWeeklyReportUnlockWatchAdd.setOnClickListener { + // TODO: 에드몹 연결 + 주간 리포트 열기 + } + } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.userInfo.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val userInfo = state.data + val currentInk = userInfo.ink.value + if (currentInk >= INK_PRICE) { + viewModel.unlockWeeklyReport(startDate = startDate, unlockType = WeeklyReportUnlockType.INK) + } else { + Toast.makeText(context, "현재 잉크가 부족합니다!", Toast.LENGTH_SHORT).show() + } + } + } + } + } + launch { + viewModel.unlockWeeklyReportResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + Toast.makeText(context, "주간 보고서 잠금이 해제 되었습니다!", Toast.LENGTH_SHORT).show() + val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(startDate = startDate) + findNavController().navigate(action) + } + } + } + } + } + } + } + + companion object { + const val TAG = "WeeklyReportUnlockDialog" + const val INK_PRICE = 10 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/DailyPraiseViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/DailyPraiseViewModel.kt index 0fd487f9..feb5a050 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/DailyPraiseViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/DailyPraiseViewModel.kt @@ -2,55 +2,85 @@ package com.egobook.app.ui.counseling.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.NotificationType -import com.egobook.app.domain.usecase.GetDailyPraiseUseCase -import com.egobook.app.ui.counseling.model.PraiseMessageModel +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map +import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase +import com.egobook.app.domain.usecase.egoroom.GetDailyPraiseByDateUseCase +import com.egobook.app.domain.usecase.egoroom.GetDailyPraiseUseCase +import com.egobook.app.domain.usecase.egoroom.UpdateDailyPraiseNotificationUseCase +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel +import com.egobook.app.ui.counseling.model.DailyPraiseDetailModel +import com.egobook.app.ui.counseling.model.PraiseDailyModel import com.egobook.app.ui.counseling.model.toPresentation -import com.egobook.app.ui.notification.delegate.NotificationDelegate -import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class DailyPraiseViewModel @Inject constructor( private val getDailyPraiseUseCase: GetDailyPraiseUseCase, - private val notificationDelegate: NotificationDelegate + private val getDailyPraiseByDateUseCase: GetDailyPraiseByDateUseCase, + private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, + private val updateDailyPraiseNotificationUseCase: UpdateDailyPraiseNotificationUseCase ): ViewModel() { - private val _dailyPraiseList = MutableStateFlow>>(UiState.Idle) - val dailyPraise = _dailyPraiseList.asStateFlow() + private val _dailyPraiseList = MutableStateFlow>(PagingData.empty()) + val dailyPraiseList = _dailyPraiseList.asStateFlow() - fun fetchDailyPraise() { + fun fetchDailyPraises(size: Int) { viewModelScope.launch { - _dailyPraiseList.value = UiState.Loading - getDailyPraiseUseCase().onSuccess { domainList -> - val uiList = domainList.map { it.toPresentation() } - _dailyPraiseList.value = UiState.Success(uiList) + getDailyPraiseUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> + _dailyPraiseList.value = pagingData.map { it.toPresentation() } + } + } + } + + private val _dailyPraiseByDate = MutableSharedFlow>() + val dailyPraiseByDate = _dailyPraiseByDate.asSharedFlow() + + fun fetchDailyPraiseByData(date: String) { + viewModelScope.launch { + _dailyPraiseByDate.emit(UiState.Loading) + getDailyPraiseByDateUseCase(date = date).onSuccess { domain -> + _dailyPraiseByDate.emit(UiState.Success(domain.toPresentation())) }.onFailure { error -> - _dailyPraiseList.value = UiState.Failure(error.message) + _dailyPraiseByDate.emit(UiState.Failure(error.message)) } } } - val notificationStatus: StateFlow> = notificationDelegate.notificationStatus + private val _dailyAndWeeklyNotification = MutableStateFlow>(UiState.Idle) + val dailyAndWeeklyNotification = _dailyAndWeeklyNotification.asStateFlow() - fun fetchNotificationStatus() { + fun getDailyAndWeeklyNotification() { viewModelScope.launch { - notificationDelegate.fetchNotificationStatus() + _dailyAndWeeklyNotification.value = UiState.Loading + getDailyAndWeeklyNotificationUseCase().onSuccess { domain -> + _dailyAndWeeklyNotification.value = UiState.Success(domain.toPresentation()) + }.onFailure { error -> + _dailyAndWeeklyNotification.value = UiState.Failure(error.message) + } } } - val updateNotificationResult: SharedFlow> = notificationDelegate.updateNotificationResult + private val _updateNotificationStatus = MutableSharedFlow>() + val updateNotificationStatus = _updateNotificationStatus.asSharedFlow() - fun updateNotificationStatus(type: NotificationType, isEnabled: Boolean) { + fun updateDailyPraiseNotification(isEnabled: Boolean) { viewModelScope.launch { - notificationDelegate.updateNotificationStatus(type = type, isEnabled = isEnabled) + _updateNotificationStatus.emit(UiState.Loading) + updateDailyPraiseNotificationUseCase(isEnabled = isEnabled).onSuccess { isEnabled -> + _updateNotificationStatus.emit(UiState.Success(isEnabled)) + }.onFailure { error -> + _updateNotificationStatus.emit(UiState.Failure(error.message)) + } } } diff --git a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt index 060fd080..ccc8fb88 100644 --- a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt @@ -2,56 +2,106 @@ package com.egobook.app.ui.counseling.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.NotificationType +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map import com.egobook.app.domain.model.ReportStyle -import com.egobook.app.domain.usecase.GetWeeklyReportStyleUseCase -import com.egobook.app.domain.usecase.GetWeeklyReportUseCase -import com.egobook.app.domain.usecase.UpdateWeeklyReportStyleUseCase +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType +import com.egobook.app.domain.usecase.GetUserInfoUseCase +import com.egobook.app.domain.usecase.egoroom.GetWeeklyReportStyleUseCase +import com.egobook.app.domain.usecase.GetWeeklyReportsUseCase +import com.egobook.app.domain.usecase.egoroom.UpdateWeeklyReportStyleUseCase +import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase + import com.egobook.app.domain.usecase.egoroom.GetWeeklyReportByDateUseCase +import com.egobook.app.domain.usecase.egoroom.UnlockWeeklyReportUseCase +import com.egobook.app.domain.usecase.egoroom.UpdateWeeklyReportNotificationUseCase +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel +import com.egobook.app.ui.counseling.model.WeeklyReportDetailModel import com.egobook.app.ui.counseling.model.WeeklyReportModel import com.egobook.app.ui.counseling.model.WeeklyReportStyleModel import com.egobook.app.ui.counseling.model.toPresentation -import com.egobook.app.ui.notification.delegate.NotificationDelegate -import com.egobook.app.ui.notification.model.NotificationModel +import com.egobook.app.ui.home.user.User import com.egobook.app.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class WeeklyReportViewModel @Inject constructor( - private val getWeeklyReportUseCase: GetWeeklyReportUseCase, + private val getWeeklyReportsUseCase: GetWeeklyReportsUseCase, private val getWeeklyReportStyleUseCase: GetWeeklyReportStyleUseCase, + private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, private val updateWeeklyReportStyleUseCase: UpdateWeeklyReportStyleUseCase, - private val notificationDelegate: NotificationDelegate + private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase, + private val getWeeklyReportByDateUseCase: GetWeeklyReportByDateUseCase, + private val unlockWeeklyReportUseCase: UnlockWeeklyReportUseCase, + private val getUserInfoUseCase: GetUserInfoUseCase ): ViewModel() { - private val _weeklyReportList = MutableStateFlow>>(UiState.Idle) + private val _weeklyReportList = MutableStateFlow>(PagingData.empty()) val weeklyReportList = _weeklyReportList.asStateFlow() - fun fetchWeeklyReport() { + fun fetchWeeklyReport(size: Int) { viewModelScope.launch { - _weeklyReportList.value = UiState.Loading - getWeeklyReportUseCase().onSuccess { domainList -> - _weeklyReportList.value = UiState.Success(domainList.map { it.toPresentation() }) + getWeeklyReportsUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> + _weeklyReportList.value = pagingData.map { it.toPresentation() } + } + } + } + + private val _dailyAndWeeklyNotification = MutableStateFlow>(UiState.Idle) + val dailyAndWeeklyNotification = _dailyAndWeeklyNotification.asStateFlow() + + fun getDailyAndWeeklyNotification() { + viewModelScope.launch { + _dailyAndWeeklyNotification.value = UiState.Loading + getDailyAndWeeklyNotificationUseCase().onSuccess { domain -> + _dailyAndWeeklyNotification.value = UiState.Success(domain.toPresentation()) + }.onFailure { error -> + _dailyAndWeeklyNotification.value = UiState.Failure(error.message) + } + } + } + + private val _updateNotificationStatus = MutableSharedFlow>() + val updateNotificationStatus = _updateNotificationStatus.asSharedFlow() + + fun updateWeeklyReportNotification(isEnabled: Boolean) { + viewModelScope.launch { + updateWeeklyReportNotificationUseCase(isEnabled = isEnabled).onSuccess { isEnabled -> + _updateNotificationStatus.emit(UiState.Success(isEnabled)) }.onFailure { error -> - _weeklyReportList.value = UiState.Failure(error.message) + _updateNotificationStatus.emit(UiState.Failure(error.message)) } } } - private val _weeklyReportStyle = MutableStateFlow>(UiState.Idle) + private val _weeklyReportByDate = MutableStateFlow>(UiState.Idle) + val weeklyReportByDate = _weeklyReportByDate.asStateFlow() + + fun getWeeklyReportByDate(startDate: String) { + viewModelScope.launch { + _weeklyReportByDate.value = UiState.Loading + getWeeklyReportByDateUseCase(startDate = startDate).onSuccess { domain -> + _weeklyReportByDate.value = UiState.Success(domain.toPresentation()) + }.onFailure { error -> + _weeklyReportByDate.value = UiState.Failure(error.message) + } + } + } + + private val _weeklyReportStyle = MutableStateFlow>(UiState.Idle) val weeklyReportStyle = _weeklyReportStyle.asStateFlow() fun fetchWeeklyReportStyle() { viewModelScope.launch { - getWeeklyReportStyleUseCase().onSuccess { domainStyle -> - _weeklyReportStyle.value = UiState.Success(domainStyle.toPresentation()) + getWeeklyReportStyleUseCase().onSuccess { reportStyle -> + _weeklyReportStyle.value = UiState.Success(reportStyle) }.onFailure { error -> _weeklyReportStyle.value = UiState.Failure(error.message) } @@ -72,20 +122,31 @@ class WeeklyReportViewModel @Inject constructor( } } + private val _unlockWeeklyReportResult = MutableSharedFlow>() + val unlockWeeklyReportResult = _unlockWeeklyReportResult.asSharedFlow() - val notificationStatus: StateFlow> = notificationDelegate.notificationStatus - - fun fetchNotificationStatus() { + fun unlockWeeklyReport(startDate: String, unlockType: WeeklyReportUnlockType) { viewModelScope.launch { - notificationDelegate.fetchNotificationStatus() + _unlockWeeklyReportResult.emit(UiState.Loading) + unlockWeeklyReportUseCase(startDate = startDate, unlockType = unlockType).onSuccess { + _unlockWeeklyReportResult.emit(UiState.Success(it)) + }.onFailure { error -> + _unlockWeeklyReportResult.emit(UiState.Failure(error.message)) + } } } - val updateNotificationResult: SharedFlow> = notificationDelegate.updateNotificationResult + private val _userInfo = MutableStateFlow>(UiState.Idle) + val userInfo = _userInfo.asStateFlow() - fun updateNotificationStatus(type: NotificationType, isEnabled: Boolean) { + fun getUserInfo() { viewModelScope.launch { - notificationDelegate.updateNotificationStatus(type = type, isEnabled = isEnabled) + _userInfo.value = UiState.Loading + getUserInfoUseCase().onSuccess { domain -> + _userInfo.value = UiState.Success(domain) + }.onFailure { error -> + _userInfo.value = UiState.Failure(error.message) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt b/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt index 067c6ad3..397d4765 100644 --- a/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt +++ b/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt @@ -1,5 +1,6 @@ package com.egobook.app.ui.home.repository +import com.egobook.app.di.qualifier.BackendApi import com.egobook.app.ui.home.user.Tendency import com.egobook.app.ui.home.user.User import retrofit2.Retrofit @@ -27,7 +28,7 @@ interface NetworkTendencyLevelService { @Singleton class NetworkUserRepository @Inject constructor( - private val retrofit: Retrofit + @BackendApi private val retrofit: Retrofit ) : UserRepository, UserTendencyRepository, UserActivityRepository { private val userService by lazy { retrofit.create(NetworkUserService::class.java) } private val tendencyLevelService by lazy { retrofit.create(NetworkTendencyLevelService::class.java) } diff --git a/app/src/main/java/com/egobook/app/ui/notification/delegate/NotificationDelegate.kt b/app/src/main/java/com/egobook/app/ui/notification/delegate/NotificationDelegate.kt deleted file mode 100644 index 279ee60f..00000000 --- a/app/src/main/java/com/egobook/app/ui/notification/delegate/NotificationDelegate.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.egobook.app.ui.notification.delegate - -import com.egobook.app.domain.model.NotificationType -import com.egobook.app.domain.usecase.GetNotificationStatusUseCase -import com.egobook.app.domain.usecase.UpdateNotificationUseCase -import com.egobook.app.ui.notification.model.NotificationModel -import com.egobook.app.ui.notification.model.toPresentation -import com.egobook.app.util.UiState -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import javax.inject.Inject - -class NotificationDelegate @Inject constructor( - private val getNotificationStatusUseCase: GetNotificationStatusUseCase, - private val updateNotificationUseCase: UpdateNotificationUseCase -) { - private val _notificationStatus = MutableStateFlow>(UiState.Idle) - val notificationStatus = _notificationStatus.asStateFlow() - - suspend fun fetchNotificationStatus() { - _notificationStatus.value = UiState.Loading - getNotificationStatusUseCase().onSuccess { domain -> - _notificationStatus.value = UiState.Success(domain.toPresentation()) - }.onFailure { error -> - _notificationStatus.value = UiState.Failure(error.message) - } - } - - private val _updateNotificationResult = MutableSharedFlow>() - val updateNotificationResult = _updateNotificationResult.asSharedFlow() - - suspend fun updateNotificationStatus(type: NotificationType, isEnabled: Boolean) { - _updateNotificationResult.emit(UiState.Loading) - updateNotificationUseCase(type = type, isEnabled = isEnabled).onSuccess { updateStatus -> - _updateNotificationResult.emit(UiState.Success(updateStatus)) - }.onFailure { error -> - _updateNotificationResult.emit(UiState.Failure(error.message)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt b/app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt index 3b4fdf08..33a22c2f 100644 --- a/app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt +++ b/app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt @@ -1,5 +1,6 @@ package com.egobook.app.ui.shop +import com.egobook.app.di.qualifier.BackendApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import retrofit2.Retrofit @@ -125,7 +126,7 @@ data class CustomItemGroupDto( @Singleton class NetworkStoreRepository @Inject constructor( - private val retrofit: Retrofit + @BackendApi private val retrofit: Retrofit ) : StoreRepository { private val storeService by lazy { diff --git a/app/src/main/java/com/egobook/app/ui/square/adapter/FriendPopupListAdapter.kt b/app/src/main/java/com/egobook/app/ui/square/adapter/FriendPopupListAdapter.kt new file mode 100644 index 00000000..d15efb07 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/adapter/FriendPopupListAdapter.kt @@ -0,0 +1,55 @@ +package com.egobook.app.ui.square.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.egobook.app.databinding.ItemFriendPopupListBinding +import com.egobook.app.ui.square.model.friend.FriendModel + +class FriendPopupListAdapter(private val onClicked: (FriendModel) -> Unit): ListAdapter(diffUtil) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): FriendPopupListViewHolder { + val binding = ItemFriendPopupListBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return FriendPopupListViewHolder(binding, onClicked) + } + + override fun onBindViewHolder( + holder: FriendPopupListViewHolder, + position: Int + ) { + holder.bind(getItem(position)) + } + + class FriendPopupListViewHolder( + private val binding: ItemFriendPopupListBinding, + private val onClicked: (FriendModel) -> Unit + ): RecyclerView.ViewHolder(binding.root){ + fun bind(item: FriendModel) = with(binding) { + tvItemFriendListName.text = item.name + root.setOnClickListener { onClicked(item) } + } + } + + companion object { + val diffUtil = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: FriendModel, + newItem: FriendModel + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: FriendModel, + newItem: FriendModel + ): Boolean { + return oldItem == newItem + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/adapter/MyLettersAdapter.kt b/app/src/main/java/com/egobook/app/ui/square/adapter/MyLettersAdapter.kt deleted file mode 100644 index f04b44d7..00000000 --- a/app/src/main/java/com/egobook/app/ui/square/adapter/MyLettersAdapter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.egobook.app.ui.square.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.egobook.app.databinding.ItemSquareLetterBinding -import com.egobook.app.ui.square.model.friend.LetterModel - -class MyLettersAdapter(private val onClicked: (LetterModel) -> Unit): ListAdapter(diffUtil) { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): MyLetterViewHolder { - return MyLetterViewHolder(ItemSquareLetterBinding.inflate(LayoutInflater.from(parent.context), parent, false)) - } - - override fun onBindViewHolder( - holder: MyLetterViewHolder, - position: Int - ) { - return holder.bind(getItem(position)) - } - - inner class MyLetterViewHolder(private val binding: ItemSquareLetterBinding): RecyclerView.ViewHolder(binding.root) { - fun bind(item: LetterModel) = with(binding) { - tvItemSquareLetterDatetime.text = item.dateTime - root.setOnClickListener { - onClicked(item) - } - } - } - - companion object { - val diffUtil = object: DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: LetterModel, - newItem: LetterModel - ): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame( - oldItem: LetterModel, - newItem: LetterModel - ): Boolean { - return oldItem == newItem - } - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/adapter/MySentLettersAdapter.kt b/app/src/main/java/com/egobook/app/ui/square/adapter/MySentLettersAdapter.kt new file mode 100644 index 00000000..c6268d9e --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/adapter/MySentLettersAdapter.kt @@ -0,0 +1,64 @@ +package com.egobook.app.ui.square.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.egobook.app.databinding.ItemSquareLetterBinding +import com.egobook.app.ui.square.model.letter.SentLetterModel +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +class MySentLettersAdapter(private val onClicked: (Long) -> Unit): PagingDataAdapter(diffUtil) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): MyLetterViewHolder { + return MyLetterViewHolder(ItemSquareLetterBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun onBindViewHolder( + holder: MyLetterViewHolder, + position: Int + ) { + val item = getItem(position) + if(item != null) holder.bind(item) + } + + inner class MyLetterViewHolder(private val binding: ItemSquareLetterBinding): RecyclerView.ViewHolder(binding.root) { + fun bind(item: SentLetterModel) = with(binding) { + tvItemSquareLetterDatetime.text = formatDate(createdDateTime = item.createdAt) + root.setOnClickListener { + onClicked(item.letterId) + } + } + } + + companion object { + val diffUtil = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SentLetterModel, + newItem: SentLetterModel + ): Boolean { + return oldItem.letterId == newItem.letterId + } + + override fun areContentsTheSame( + oldItem: SentLetterModel, + newItem: SentLetterModel + ): Boolean { + return oldItem == newItem + } + + } + } + + private fun formatDate(createdDateTime: String): String { + val instant = Instant.parse(createdDateTime) + val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") + .withZone(ZoneId.systemDefault()) + return formatter.format(instant) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/AbusiveContentModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/AbusiveContentModel.kt new file mode 100644 index 00000000..5ceb81c0 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/AbusiveContentModel.kt @@ -0,0 +1,19 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis + +data class AbusiveContentModel( + val text: String, + val riskScore: Double, + val isHarmful: Boolean, + val label: String, + val detectedBadWords: List +) + +fun AbusiveContentAnalysis.toPresentation(): AbusiveContentModel = AbusiveContentModel( + text = text, + riskScore = riskScore, + isHarmful = isHarmful, + label = label, + detectedBadWords = detectedBadWords +) diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt new file mode 100644 index 00000000..18987347 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt @@ -0,0 +1,41 @@ +package com.egobook.app.ui.square.model.letter + +import android.os.Parcelable +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetter +import com.egobook.app.domain.model.square.letter.ArrivedPendingLetterItem +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus +import kotlinx.parcelize.Parcelize + +data class ArrivedPendingLetterModel( + val letter: ArrivedPendingLetterItemModel? = null +) + +@Parcelize +data class ArrivedPendingLetterItemModel( + val letterId: Long, + val status: LetterStatus, + val mode: LetterMode, + val fromLabel: String, + val content: String, + val letterColor: LetterBackgroundColor, + val arrivedAt: String, + val replyDeadlineAt: String +): Parcelable + +fun ArrivedPendingLetter.toPresentation(): ArrivedPendingLetterModel = ArrivedPendingLetterModel( + letter = letter?.toPresentation() +) + +fun ArrivedPendingLetterItem.toPresentation(): ArrivedPendingLetterItemModel = + ArrivedPendingLetterItemModel( + letterId = letterId, + status = status, + mode = mode, + fromLabel = fromLabel, + content = content, + arrivedAt = arrivedAt, + replyDeadlineAt = replyDeadlineAt, + letterColor = letterColor + ) diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/ReplyLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/ReplyLetterModel.kt new file mode 100644 index 00000000..d879caf2 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/ReplyLetterModel.kt @@ -0,0 +1,32 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.ReplyLetter +import com.egobook.app.domain.model.square.letter.ReplyLetterRewards +import com.egobook.app.domain.model.square.letter.ReplyReward + +data class ReplyLetterModel( + val letterId: Long, + val status: LetterStatus, + val repliedAt: String, + val rewards: List +) + +data class ReplyLetterRewardsModel( + val kind: ReplyReward, + val amount: Int, + val toastMessage: String? = null +) + +fun ReplyLetterRewards.toPresentation(): ReplyLetterRewardsModel = ReplyLetterRewardsModel( + kind = kind, + amount = amount, + toastMessage = toastMessage +) + +fun ReplyLetter.toPresentation(): ReplyLetterModel = ReplyLetterModel( + letterId = letterId, + status = status, + repliedAt = repliedAt, + rewards = rewards.map { it.toPresentation() } +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/ReportLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/ReportLetterModel.kt new file mode 100644 index 00000000..32a2fc45 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/ReportLetterModel.kt @@ -0,0 +1,16 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.ReportLetter +import com.egobook.app.domain.model.square.letter.ReportLetterType + +data class ReportLetterModel( + val reason: ReportLetterType, + val description: String? = null +) + +fun ReportLetterModel.toDomain(): ReportLetter = ReportLetter( + reason = reason, + description = description +) + + diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/SendLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/SendLetterModel.kt new file mode 100644 index 00000000..953fe401 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/SendLetterModel.kt @@ -0,0 +1,19 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.SendLetter + +data class SendLetterModel( + val mode: LetterMode, + val receiverId: Long? = null, + val content: String, + val letterColor: LetterBackgroundColor +) + +fun SendLetterModel.toDomain(): SendLetter = SendLetter( + mode = mode, + receiverId = receiverId, + content = content, + letterColor = letterColor +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterModel.kt new file mode 100644 index 00000000..fab1426e --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterModel.kt @@ -0,0 +1,23 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.SentLetterItem + +data class SentLetterModel( + val letterId: Long, + val mode: LetterMode, + val status: LetterStatus, + val aiReplaceAt: String, + val content: String, + val createdAt: String +) + +fun SentLetterItem.toPresentation() = SentLetterModel( + letterId = letterId, + mode = mode, + status = status, + aiReplaceAt = aiReplaceAt, + content = content, + createdAt = createdAt +) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterWithReplyModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterWithReplyModel.kt new file mode 100644 index 00000000..157c2070 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterWithReplyModel.kt @@ -0,0 +1,48 @@ +package com.egobook.app.ui.square.model.letter + +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.domain.model.square.letter.LetterReply +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.SentLetterWithReply + +data class SentLetterWithReplyModel( + val letterId: Long, + val threadId: Long, + val status: LetterStatus, + val mode: LetterMode, + val sentContent: String, + val backgroundColor: LetterBackgroundColor, + val createdAt: String, + val arrivedAt: String, + val reply: LetterReplyModel? = null +) + +data class LetterReplyModel( + val replyId: Long, + val replyContent: String, + val isAIGenerated: Boolean, + val isReported: Boolean, + val repliedAt: String +) + +fun SentLetterWithReply.toPresentation() = SentLetterWithReplyModel( + letterId = letterId, + threadId = threadId, + status = status, + mode = mode, + sentContent = sentContent, + backgroundColor = backgroundColor, + createdAt = createdAt, + arrivedAt = arrivedAt, + reply = reply?.toPresentation() +) + +fun LetterReply.toPresentation() = LetterReplyModel( + replyId = replyId, + replyContent = replyContent, + isAIGenerated = isAIGenerated, + isReported = isReported, + repliedAt = repliedAt +) + diff --git a/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt new file mode 100644 index 00000000..1f5e91cf --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt @@ -0,0 +1,109 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.os.Bundle +import android.graphics.Color +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import com.egobook.app.R +import com.egobook.app.databinding.DialogArrivedPendingLetterBinding +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterItemModel +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + +class ArrivedPendingLetterDialog( + private val letterInfo: ArrivedPendingLetterItemModel +): DialogFragment(R.layout.dialog_arrived_pending_letter) { + private lateinit var binding: DialogArrivedPendingLetterBinding + private val viewModel: LetterViewModel by activityViewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogArrivedPendingLetterBinding.bind(view) + initViews() + initListeners() + initObservers() + } + + private fun initViews() = with(binding) { + tvArrivedPendingLetterArrivedAt.text = formatArrivedDateTime(dateTimeStr = letterInfo.arrivedAt) + tvArrivedPendingLetterContent.text = letterInfo.content + tvArrivedPendingLetterFromLabel.text = "From ${letterInfo.fromLabel}" + cvArrivedPendingLetterContainer.backgroundTintList = + when(letterInfo.letterColor) { + LetterBackgroundColor.WHITE -> resources.getColorStateList(R.color.letter_bg_beige, null) + LetterBackgroundColor.PINK -> resources.getColorStateList(R.color.letter_bg_pink, null) + LetterBackgroundColor.GREEN -> resources.getColorStateList(R.color.letter_bg_green, null) + LetterBackgroundColor.BLUE -> resources.getColorStateList(R.color.letter_bg_blue, null) + LetterBackgroundColor.PURPLE -> resources.getColorStateList(R.color.letter_bg_purple, null) + } + } + + private fun initListeners() = with(binding) { + ivArrivedPendingLetterDefer.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterInfo.letterId) + } + ivArrivedPendingLetterReport.setOnClickListener { + + } + btnArrivedPendingLetterGiveUp.setOnClickListener { + dismiss() + val dialog = GiveUpReplyLetterPopupDialog(letterInfo = letterInfo).apply { isCancelable = false } + dialog.show(parentFragmentManager, GiveUpReplyLetterPopupDialog.TAG) + } + btnArrivedPendingLetterReply.setOnClickListener { + dismiss() + removeScreenBlur() + val action = SquareFragmentDirections.actionMenuSquareToLetterReplyFragment(letterItem = letterInfo) + findNavController().navigate(action) + } + } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.deferReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + removeScreenBlur() + dismiss() + } + } + } + } + } + } + + /** + * "arrivedAt": "2026-01-04T10:00:00+09:00" → 2026.01.04 로 변환해주는 메소드 + */ + private fun formatArrivedDateTime(dateTimeStr: String): String { + val parsedDateTime: OffsetDateTime = OffsetDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") + return parsedDateTime.format(formatter) + } + + companion object { + const val TAG = "ArrivedPendingLetterDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterPopupDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterPopupDialog.kt new file mode 100644 index 00000000..0d8cb416 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterPopupDialog.kt @@ -0,0 +1,100 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.egobook.app.BlurLevel +import com.egobook.app.R +import com.egobook.app.applyScreenBlur +import com.egobook.app.databinding.DialogArrivedPendingLetterPopupBinding +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterItemModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch +import java.time.Duration +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +class ArrivedPendingLetterPopupDialog(private val letterInfo: ArrivedPendingLetterItemModel): DialogFragment(R.layout.dialog_arrived_pending_letter_popup) { + private lateinit var binding: DialogArrivedPendingLetterPopupBinding + private val viewModel: LetterViewModel by activityViewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogArrivedPendingLetterPopupBinding.bind(view) + initViews() + initListeners() + initObservers() + } + + private fun initViews() = with(binding) { + tvArrivedPendingLetterTitle.text = "낯선 고북이의\n편지가 도착했어요" // TODO: 친구인지 익명인지 구분 필요 + tvArrivedPendingLetterReplyDeadline.text = getRemainingTime(replyDeadlineAt = letterInfo.replyDeadlineAt) + } + + private fun initListeners() = with(binding) { + btnArrivedPendingLetterReplyLater.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterInfo.letterId) + } + btnArrivedPendingLetterConfirm.setOnClickListener { + val dialog = ArrivedPendingLetterDialog(letterInfo = letterInfo).apply { isCancelable = false } + dialog.show(parentFragmentManager, ArrivedPendingLetterDialog.TAG) + dismiss() + } + } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.deferReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + removeScreenBlur() + dismiss() + } + } + } + } + } + } + + /** + * 2026-01-05T10:00:00+09:00 → 표준 ISO 형식 + * +09:00 = 타임존 오프셋 정보 + * isNegative: 현재 시간이 기한을 지났을 경우 + * ★ 문서 보충 필요 ★ + */ + private fun getRemainingTime(replyDeadlineAt: String): String { + val deadline: OffsetDateTime = OffsetDateTime.parse(replyDeadlineAt, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + val now: OffsetDateTime = OffsetDateTime.now(ZoneId.systemDefault()) + val duration: Duration = Duration.between(now, deadline) + return when { + duration.isNegative || duration.isZero -> "답장 유효기한 만료" + duration.toDays() > 0 -> "${duration.toDays()}일 남음" + duration.toHours() > 0 -> "${duration.toHours()}시간 남음" + duration.toMinutes() > 0 -> "${duration.toMinutes()}분 남음" + else -> "잠시 후 만료" + } + } + + companion object { + const val TAG = "ArrivedPendingLetterPopupDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentFailureDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentFailureDialog.kt new file mode 100644 index 00000000..b26e7a4a --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentFailureDialog.kt @@ -0,0 +1,55 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.egobook.app.R +import com.egobook.app.databinding.DialogDetectAbusiveContentFailureBinding +import com.egobook.app.removeScreenBlur + +class DetectAbusiveContentFailureDialog( + private val originalContent: String, + private val badWords: List +): DialogFragment(R.layout.dialog_detect_abusive_content_failure) { + private lateinit var binding: DialogDetectAbusiveContentFailureBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogDetectAbusiveContentFailureBinding.bind(view) + initViews() + initListeners() + } + + /** + * 정규식으로 문장 분리 + * 문장 분리 후 앞뒤 공백 제거 + * 문장이 채워져있고, badWords 중에 하나라도 포함하는 경우만 필터링 + * 검열된 문장 리스트를 다시 하나의 문장으로 잇기 + */ + private fun initViews() = with(binding) { + val splitSentences: List = originalContent.split(Regex("[.?!\n]]")) + val trimSentences: List = splitSentences.map { it.trim() } + val filteredSentences: List = trimSentences.filter { sentence -> sentence.isNotEmpty() && badWords.any { badWord -> sentence.contains(badWord) } } + tvDetectAbusiveContentFailureWordsDescription.text = filteredSentences.joinToString(", ") + } + + private fun initListeners() = with(binding) { + btnDetectAbusiveContentFailureEdit.setOnClickListener { + dismiss() + removeScreenBlur() + } + } + + companion object { + const val TAG = "DetectAbusiveContentFailureDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentLoadingDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentLoadingDialog.kt new file mode 100644 index 00000000..3d6de7e3 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentLoadingDialog.kt @@ -0,0 +1,29 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.egobook.app.R +import com.egobook.app.databinding.DialogDetectAbusiveContentLoadingBinding + +class DetectAbusiveContentLoadingDialog: DialogFragment(R.layout.dialog_detect_abusive_content_loading) { + private lateinit var binding: DialogDetectAbusiveContentLoadingBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogDetectAbusiveContentLoadingBinding.bind(view) + } + + companion object { + const val TAG = "DetectAbusiveContentLoadingDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt new file mode 100644 index 00000000..ea20c18d --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt @@ -0,0 +1,67 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.findNavController +import com.egobook.app.R +import com.egobook.app.databinding.DialogDetectAbusiveContentSuccessBinding +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.domain.model.square.letter.ReplyReward +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.ReplyLetterModel + +class DetectAbusiveContentSuccessDialog(private val status: LetterStatus, private val replyItem: ReplyLetterModel? = null): DialogFragment(R.layout.dialog_detect_abusive_content_success) { + private lateinit var binding: DialogDetectAbusiveContentSuccessBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogDetectAbusiveContentSuccessBinding.bind(view) + initViews() + initListeners() + } + + private fun initViews() = with(binding) { + when(status) { + LetterStatus.SENT -> { + btnDetectAbusiveSuccess.text = "잉크 1 획득!" // TODO: 조건에 따른 동적 변경 필요한 지 확인 + } + LetterStatus.REPLIED -> { + val rewardList = mutableListOf() + replyItem?.rewards?.forEach { reward -> + when(reward.kind) { + ReplyReward.INK -> { + rewardList.add("${ReplyReward.INK.label} ${reward.amount}") // 잉크 1 + } + ReplyReward.EMPATHY -> { + rewardList.add("${ReplyReward.EMPATHY.label} ${reward.amount}") // 공감성 1 + } + } + } + val totalRewards = rewardList.joinToString(", ") // 잉크 1, 공감성 1 + btnDetectAbusiveSuccess.text = "$totalRewards 획득!" // 잉크 1, 공감성 1 획득! + } + else -> {} + } + } + + private fun initListeners() = with(binding) { + btnDetectAbusiveSuccess.setOnClickListener { + removeScreenBlur() + findNavController().popBackStack() + } + } + + companion object { + const val TAG = "DetectAbusiveContentSuccessDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/FriendsListFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/FriendsListFragment.kt index 422f6c7e..c3aa6b3d 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/FriendsListFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/FriendsListFragment.kt @@ -59,6 +59,7 @@ class FriendsListFragment : Fragment(R.layout.fragment_friends_list) { is UiState.Success> -> { val friendList = state.data adapter.submitList(friendList) + tvFriendsValue.text = "${friendList.size}/${MAX_FRIEND_COUNT}명" } } } @@ -76,6 +77,7 @@ class FriendsListFragment : Fragment(R.layout.fragment_friends_list) { val deleteId = state.data val updateList = adapter.currentList.filter { it.id != deleteId } adapter.submitList(updateList.toList()) + tvFriendsValue.text = "${updateList.size}/${MAX_FRIEND_COUNT}명" } } } @@ -83,4 +85,8 @@ class FriendsListFragment : Fragment(R.layout.fragment_friends_list) { } } } + + companion object { + const val MAX_FRIEND_COUNT = 10 + } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt new file mode 100644 index 00000000..a5bf2841 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt @@ -0,0 +1,88 @@ +package com.egobook.app.ui.square.view + + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.egobook.app.R +import com.egobook.app.databinding.DialogGiveUpReplyLetterBinding +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterItemModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch +import kotlin.getValue + +class GiveUpReplyLetterPopupDialog( + private val letterInfo: ArrivedPendingLetterItemModel +): DialogFragment(R.layout.dialog_give_up_reply_letter) { + private lateinit var binding: DialogGiveUpReplyLetterBinding + private val viewModel: LetterViewModel by activityViewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogGiveUpReplyLetterBinding.bind(view) + initListeners() + initObservers() + } + + private fun initListeners() = with(binding) { + btnGiveUpReplyLetterDefer.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterInfo.letterId) + } + btnGiveUpReplyLetter.setOnClickListener { + viewModel.giveUpReplyLetter(letterId = letterInfo.letterId) + } + } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.deferReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + removeScreenBlur() + dismiss() + } + } + } + } + launch { + viewModel.giveUpReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + removeScreenBlur() + dismiss() + } + } + } + } + } + } + } + + companion object { + const val TAG = "GiveUpReplyLetterPopupDialog" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/LetterFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterFragment.kt deleted file mode 100644 index 93202d8c..00000000 --- a/app/src/main/java/com/egobook/app/ui/square/view/LetterFragment.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.egobook.app.ui.square.view - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.View -import com.egobook.app.R -import com.egobook.app.databinding.FragmentLetterBinding -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class LetterFragment : Fragment(R.layout.fragment_letter) { - private lateinit var binding: FragmentLetterBinding - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding = FragmentLetterBinding.bind(view) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/LetterReplyFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterReplyFragment.kt index 08d76654..33315283 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/LetterReplyFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/LetterReplyFragment.kt @@ -1,16 +1,221 @@ package com.egobook.app.ui.square.view import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.View +import android.widget.LinearLayout +import android.widget.PopupWindow +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import com.egobook.app.BlurLevel import com.egobook.app.R +import com.egobook.app.applyScreenBlur import com.egobook.app.databinding.FragmentLetterReplyBinding +import com.egobook.app.databinding.LayoutLetterTooltipPopupBinding +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.AbusiveContentModel +import com.egobook.app.ui.square.model.letter.ReplyLetterModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { private lateinit var binding: FragmentLetterReplyBinding + private val viewModel: LetterViewModel by activityViewModels() + private val letterItem by lazy { + val args: LetterReplyFragmentArgs by navArgs() + args.letterItem + } + private val premiumLetterColorList by lazy { + listOf( + binding.cvLetterReplyColorPink, + binding.cvLetterReplyColorGreen, + binding.cvLetterReplyColorBlue, + binding.cvLetterReplyColorPurple + ) + } + + private var loadingDialog: DetectAbusiveContentLoadingDialog? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLetterReplyBinding.bind(view) + initViews() + initListeners() + initObservers() + } + + private fun initViews() = with(binding) { + tvLetterReplyReceivedContent.text = letterItem.content + cvLetterReplyColorBeige.isSelected = true + cvLetterReplyColorBeige.getChildAt(0).isVisible = true + } + + private fun initListeners() = with(binding) { + ivLetterReplyChevron.setOnClickListener { + tvLetterReplyReceivedContent.isVisible = !tvLetterReplyReceivedContent.isVisible + val chevron = + if (tvLetterReplyReceivedContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down + ivLetterReplyChevron.setImageResource(chevron) + } + premiumLetterColorList.forEach { letterColor -> + letterColor.setOnClickListener { clickedView -> + val letterColor = when (clickedView.id) { + R.id.cv_letter_reply_color_pink -> LetterBackgroundColor.PINK + R.id.cv_letter_reply_color_green -> LetterBackgroundColor.GREEN + R.id.cv_letter_reply_color_blue -> LetterBackgroundColor.BLUE + else -> LetterBackgroundColor.PURPLE + } + val dialog = + PremiumLetterBuyDialog(letterColor = letterColor).apply { isCancelable = false } + dialog.show(childFragmentManager, PremiumLetterBuyDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + etLetterReplyContent.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(p0: Editable?) = Unit + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int + ) = Unit + + override fun onTextChanged( + text: CharSequence?, + start: Int, + before: Int, + count: Int + ) { + btnLetterReply.isEnabled = !text.isNullOrBlank() + tvLetterReplyContentLength.text = "${text?.length}/$MAX_LENGTH" + } + }) + ivLetterReplyTooltip.setOnClickListener { + showTooltipPopup(anchorView = ivLetterReplyTooltip) + } + btnLetterReply.setOnClickListener { + viewModel.detectAbusiveContent(text = etLetterReplyContent.text.toString()) + } + ivLetterReplyBack.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterItem.letterId) + } } + private fun showTooltipPopup(anchorView: View) { + val popupBinding = LayoutLetterTooltipPopupBinding.inflate(layoutInflater) + val popupWindow = PopupWindow( + popupBinding.root, + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + true + ) + popupBinding.root.measure( + View.MeasureSpec.UNSPECIFIED, + View.MeasureSpec.UNSPECIFIED + ) + val popupWidth = popupBinding.root.measuredWidth + popupWindow.showAsDropDown( + anchorView, + 0 - popupWidth + anchorView.width, + 0 + ) + } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.detectAbusiveContentResult.collect { state -> + when (state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> { + showLoadingDialog() + } + is UiState.Success -> { + hideLoadingDialog() + val abusiveContent = state.data + if (abusiveContent.isHarmful && abusiveContent.riskScore >= 80.0f) { + val dialog = DetectAbusiveContentFailureDialog( + originalContent = abusiveContent.text, + badWords = abusiveContent.detectedBadWords + ).apply { + isCancelable = false + } + dialog.show( + childFragmentManager, + DetectAbusiveContentFailureDialog.TAG + ) + applyScreenBlur(BlurLevel.BASE) + } else { + viewModel.replyLetter( + letterId = letterItem.letterId, + text = etLetterReplyContent.text.toString() + ) + } + } + } + } + } + launch { + viewModel.replyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val dialog = DetectAbusiveContentSuccessDialog(status = LetterStatus.REPLIED, replyItem = state.data).apply { + isCancelable = false + } + dialog.show(parentFragmentManager, DetectAbusiveContentSuccessDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + } + } + launch { + viewModel.deferReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + findNavController().popBackStack() + } + } + } + } + } + } + } + + private fun showLoadingDialog() { + if (loadingDialog == null) { + loadingDialog = DetectAbusiveContentLoadingDialog().apply { + isCancelable = false + } + loadingDialog?.show(childFragmentManager, DetectAbusiveContentLoadingDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + + private fun hideLoadingDialog() { + loadingDialog?.dismiss() + loadingDialog = null + removeScreenBlur() + } + + companion object { + private const val MAX_LENGTH = 350 + } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt new file mode 100644 index 00000000..96b80b05 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt @@ -0,0 +1,144 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.egobook.app.BlurLevel +import com.egobook.app.R +import com.egobook.app.applyScreenBlur +import com.egobook.app.databinding.DialogLetterSendBinding +import com.egobook.app.removeScreenBlur +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.ui.square.model.friend.FriendModel +import com.egobook.app.ui.square.model.letter.AbusiveContentModel +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterStatus +import com.egobook.app.ui.square.model.letter.SendLetterModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch + +class LetterSendDialog(private val mode: LetterMode, private val friendInfo: FriendModel? = null, private val letterContent: String, private val letterColor: LetterBackgroundColor): DialogFragment(R.layout.dialog_letter_send) { + + private lateinit var binding: DialogLetterSendBinding + private val viewModel: LetterViewModel by activityViewModels() + private var loadingDialog: DetectAbusiveContentLoadingDialog? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogLetterSendBinding.bind(view) + initViews() + initListeners() + initObservers() + } + + private fun initViews() = with(binding) { + when(mode) { + LetterMode.FRIEND -> { + tvLetterSendTitle.text = "${friendInfo?.name}에게\n편지를 보낼까요?" + tvLetterSendDescription.text = "상대에게 내 이름이 보여요" + } + LetterMode.RANDOM -> { + tvLetterSendTitle.text = "누군가에게\n편지를 보낼까요?" + tvLetterSendDescription.text = "익명으로 전달돼요" + } + } + } + + private fun initListeners() = with(binding) { + btnLetterSendBack.setOnClickListener { + removeScreenBlur() + dismiss() + } + btnLetterSendApply.setOnClickListener { + viewModel.detectAbusiveContent(text = letterContent) + } + } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.detectAbusiveContentResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> { + dismiss() // 얘가 중요하다 + showLoadingDialog() + } + is UiState.Success -> { + hideLoadingDialog() + val abusiveContent = state.data + if(abusiveContent.isHarmful && abusiveContent.riskScore >= 80.0f) { + val dialog = DetectAbusiveContentFailureDialog(originalContent = abusiveContent.text, badWords = abusiveContent.detectedBadWords).apply { + isCancelable = false + } + dialog.show(parentFragmentManager, DetectAbusiveContentFailureDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } else { + val letter = SendLetterModel( + mode = mode, + receiverId = friendInfo?.id, + content = letterContent, + letterColor = letterColor + ) + viewModel.sendLetter(letter = letter) + } + } + } + } + } + launch { + viewModel.sendLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val dialog = DetectAbusiveContentSuccessDialog(status = LetterStatus.SENT).apply { + isCancelable = false + } + dialog.show(parentFragmentManager, DetectAbusiveContentSuccessDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + } + } + } + } + } + + private fun showLoadingDialog() { + if(loadingDialog == null) { + loadingDialog = DetectAbusiveContentLoadingDialog().apply { + isCancelable = false + } + loadingDialog?.show(parentFragmentManager, DetectAbusiveContentLoadingDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + + private fun hideLoadingDialog() { + loadingDialog?.dismiss() + loadingDialog = null + removeScreenBlur() + } + + companion object { + const val TAG = "LetterSendDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt new file mode 100644 index 00000000..b9af78e9 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt @@ -0,0 +1,198 @@ +package com.egobook.app.ui.square.view + +import android.graphics.Canvas +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.widget.PopupWindow +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView +import com.egobook.app.BlurLevel +import com.egobook.app.R +import com.egobook.app.applyScreenBlur +import com.egobook.app.databinding.FragmentLetterWriteBinding +import com.egobook.app.databinding.LayoutPopupFriendListBinding +import com.egobook.app.domain.model.square.letter.LetterMode +import com.egobook.app.ui.square.adapter.FriendPopupListAdapter +import com.egobook.app.ui.square.model.friend.FriendModel +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { + private lateinit var binding: FragmentLetterWriteBinding + private val premiumLetterColorList by lazy { + listOf(binding.cvLetterWriteColorPink, binding.cvLetterWriteColorGreen, binding.cvLetterWriteColorBlue, binding.cvLetterWriteColorPurple) + } + + private val viewModel: LetterViewModel by activityViewModels() + + private lateinit var friendList: List + private var letterColor: LetterBackgroundColor = LetterBackgroundColor.WHITE + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLetterWriteBinding.bind(view) + fetchData() + initViews() + initListeners() + initObservers() + } + + private fun fetchData() { + viewModel.getFriendList() + } + + private fun initViews() = with(binding) { + cvLetterColorBeige.isSelected = true + cvLetterColorBeige.getChildAt(0).isVisible = true + } + + private fun initListeners() = with(binding) { + ivLetterWriteBack.setOnClickListener { findNavController().popBackStack() } + premiumLetterColorList.forEach { letterColor -> + letterColor.setOnClickListener { clickedView -> + val letterColor = when(clickedView.id) { + R.id.cv_letter_write_color_pink -> LetterBackgroundColor.PINK + R.id.cv_letter_write_color_green -> LetterBackgroundColor.GREEN + R.id.cv_letter_write_color_blue -> LetterBackgroundColor.BLUE + else -> LetterBackgroundColor.PURPLE + } + val dialog = PremiumLetterBuyDialog(letterColor = letterColor).apply { isCancelable = false } + dialog.show(childFragmentManager, PremiumLetterBuyDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + etLetterWriteContent.addTextChangedListener(object: TextWatcher { + override fun afterTextChanged(p0: Editable?) = Unit + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int + ) = Unit + override fun onTextChanged( + text: CharSequence?, + start: Int, + before: Int, + count: Int + ) { + btnLetterSendAnonymous.isEnabled = !text.isNullOrBlank() + btnLetterSendFriend.isEnabled = !text.isNullOrBlank() + tvLetterWriteContentLength.text = "${text?.length}/$MAX_LENGTH" + } + }) + btnLetterSendFriend.setOnClickListener { + showFriendListPopup(anchorView = btnLetterSendFriend) + } + btnLetterSendAnonymous.setOnClickListener { + applyScreenBlur(BlurLevel.BASE) + val dialog = LetterSendDialog(LetterMode.RANDOM, friendInfo = null, letterContent = etLetterWriteContent.text.toString(), letterColor = letterColor).apply { + isCancelable = false + } + dialog.show(childFragmentManager, LetterSendDialog.TAG) + tvLetterWriteReceiver.text = "To 낯선 고북이" + tvLetterWriteSender.text = "From 또다른 고북이" + } + } + + /** + * 1. 첫 번째 인자 width = 0 : DividerItemDecoration이 세로 방향(VERTICAL)일 때는 구분선의 너비를 부모의 너비에 맞게 자동으로 늘리므로, 0으로 두어도 괜찮다. + * 두 번재 인자 height : 구분선의 두께가 된다. + * 안드로이드의 dp 단위를 pixel 단위로 변환하는 공식이다. (1dp를 기기의 해상도에 맞는 실제 픽셀값으로 계산) + * 어떤 해상도의 기기에서든 똑같이 1dp 두께로 보이게끔, 그에 맞는 픽셀로 변환하라는 의미이다. + * 1 = dp 단위의 값, resources.displayMetrics.density = 현재 기기의 화면 밀도 계수 (기기마다 다름) + * 1 * resources.displayMetrics.density = 기기마다 1dp 에 대응하는 픽셀값 + * toInt()를 붙이는 이유는 픽셀은 정수 단위여야 하기 때문이다. + * 2. onDraw는 아이템의 배경색이 구분선을 덮어버리는 문제가 발생할 수 있기에, 아이템보다 위쪽에 구분선을 그리도록 하기 위해 onDrawOver을 쓰는 것을 권장한다. + * 3. dividerDrawable의 intrinsicHeight(고유 높이)는 setSize에 설정한 두 번째 인자(height)와 동일한 값이다. + */ + private fun showFriendListPopup(anchorView: View) = with(binding) { + val popupBinding = LayoutPopupFriendListBinding.inflate(layoutInflater) + val popupHeight = (200*resources.displayMetrics.density).toInt() + val popupWindow = PopupWindow( + popupBinding.root, btnLetterSendFriend.width, popupHeight, true + ) + with(popupBinding.rvPopupFriendList) { + adapter = FriendPopupListAdapter { friendInfo -> + tvLetterWriteReceiver.text = "To ${friendInfo.name}" + tvLetterWriteSender.text = "From 로그인한 유저" // TODO: 나중에 유저 정보 받아오기 + popupWindow.dismiss() + val dialog = LetterSendDialog(mode = LetterMode.FRIEND, friendInfo = friendInfo, letterContent = etLetterWriteContent.text.toString(), letterColor = letterColor).apply { isCancelable = false } + dialog.show(childFragmentManager, LetterSendDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + val dividerDrawable = GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + color = resources.getColorStateList(R.color.layer_white, null) + setSize(0, (1*resources.displayMetrics.density).toInt()) // 1 + } + addItemDecoration(object: RecyclerView.ItemDecoration() { + override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { // 2 + val left = parent.paddingLeft + val right = parent.width - parent.paddingRight + for(i in 0 until parent.childCount - 1) { + val child = parent.getChildAt(i) + val top = child.bottom + val bottom = top + dividerDrawable.intrinsicHeight // 3 + dividerDrawable.setBounds(left, top, right, bottom) + dividerDrawable.draw(c) + } + } + }) + } + (popupBinding.rvPopupFriendList.adapter as FriendPopupListAdapter).submitList(friendList) + popupWindow.showAsDropDown( + anchorView, + 0, + -(anchorView.height + popupHeight + 40) + ) + } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.friendList.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success> -> { + val friendList = state.data + if(friendList.isEmpty()) { + btnLetterSendFriend.isVisible = false + val params = btnLetterSendAnonymous.layoutParams as ConstraintLayout.LayoutParams + params.startToEnd = ConstraintLayout.LayoutParams.UNSET + params.bottomToBottom = ConstraintLayout.LayoutParams.UNSET + params.topToTop = ConstraintLayout.LayoutParams.UNSET + params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID + params.marginStart = (16 * resources.displayMetrics.density).toInt() + params.topToBottom = cvLetterContainer.id + params.topMargin = (28 * resources.displayMetrics.density).toInt() + btnLetterSendAnonymous.layoutParams = params + } else { + this@LetterWriteFragment.friendList = friendList + } + } + } + } + } + } + } + + companion object { + private const val MAX_LENGTH = 350 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt index 21a961dc..4394d63e 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt @@ -2,35 +2,151 @@ package com.egobook.app.ui.square.view import android.os.Bundle import android.view.View +import android.widget.Toast +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import com.egobook.app.BlurLevel import com.egobook.app.R +import com.egobook.app.applyScreenBlur import com.egobook.app.databinding.FragmentMyLetterDetailBinding +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.ui.square.model.letter.SentLetterWithReplyModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { private lateinit var binding: FragmentMyLetterDetailBinding - private val args: MyLetterDetailFragmentArgs by navArgs() + private val letterId: Long by lazy { + val args: MyLetterDetailFragmentArgs by navArgs() + args.letterId + } + private var replyId: Long? = null + private var threadId: Long? = null + private var isReplyReported: Boolean? = null + private val viewModel: LetterViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentMyLetterDetailBinding.bind(view) - initViews() + fetchData() initListeners() + initObservers() } - private fun initViews() = with(binding) { - val item = args.letterItem - tvMyLetterDetailDatetime.text = item.dateTime - tvMyLetterDetailSentContent.text = item.sentContent - tvMyLetterDetailReceiver.text = "To. ${item.receivedContent.receiverNickname}" - tvMyLetterDetailReceivedContent.text = item.receivedContent.letterContent - tvMyLetterDetailSender.text = "To. ${item.receivedContent.senderNickname}" + private fun fetchData() { + viewModel.getSentLetterWithReply(letterId = letterId) } private fun initListeners() = with(binding) { ivMyLetterDetailBack.setOnClickListener { findNavController().popBackStack() } + ivMyLetterDetailSentChevron.setOnClickListener { + cvMyLetterDetailSentContent.isVisible = !cvMyLetterDetailSentContent.isVisible + if(cvMyLetterDetailSentContent.isVisible) { + ivMyLetterDetailSentChevron.setImageResource(R.drawable.ic_chevron_up) + val params = llMyLetterDetailReplied.layoutParams as ConstraintLayout.LayoutParams + with(params) { + topToBottom = ConstraintLayout.LayoutParams.UNSET + topToBottom = cvMyLetterDetailSentContent.id + } + } else { + ivMyLetterDetailSentChevron.setImageResource(R.drawable.ic_chevron_down) + val params = llMyLetterDetailReplied.layoutParams as ConstraintLayout.LayoutParams + with(params) { + topToBottom = ConstraintLayout.LayoutParams.UNSET + topToBottom = tvMyLetterDetailSentTitle.id + } + } + } + ivMyLetterDetailReport.setOnClickListener { + if(isReplyReported == true) Toast.makeText(context, "답장이 이미 신고되었습니다.", Toast.LENGTH_SHORT).show() + else { + val dialog = SquareReportDialog(letterId = letterId, replyId = replyId ?: -1L).apply { isCancelable = false } + dialog.show(childFragmentManager, SquareReportDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + btnDeleteLetterThread.setOnClickListener { + viewModel.deleteLetterThread(threadId = threadId ?: -1L) + } + } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.sentLetterWithReply.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val data = state.data + threadId = data.threadId + val sentCardBackgroundColor = when(data.backgroundColor) { + LetterBackgroundColor.WHITE -> R.color.letter_bg_beige + LetterBackgroundColor.PINK -> R.color.letter_bg_pink + LetterBackgroundColor.GREEN -> R.color.letter_bg_green + LetterBackgroundColor.BLUE -> R.color.letter_bg_blue + LetterBackgroundColor.PURPLE -> R.color.letter_bg_purple + } + tvMyLetterDetailSentAt.text = formatDate(createdDateTime = data.createdAt) + tvMyLetterDetailSentContent.text = data.sentContent + cvMyLetterDetailSentContent.backgroundTintList = resources.getColorStateList(sentCardBackgroundColor, null) + if(data.reply != null) { + replyId = data.reply.replyId + isReplyReported = data.reply.isReported + tvMyLetterDetailRepliedContent.text = data.reply.replyContent + if(data.reply.isAIGenerated) { + tvMyLetterDetailReceiver.text = "To 사용자 닉네임" // TODO: 실제 유저 닉네임으로 변경하기 + tvMyLetterDetailSender.text = "FROM. 당신의 고북" + tvMyLetterDetailAiGeneratedDescription.isVisible = true + val params = tvMyLetterDetailSender.layoutParams as ConstraintLayout.LayoutParams + params.topMargin = (72 * resources.displayMetrics.density).toInt() + } else { + // TODO: 친구/익명 유무에 따라 보낸이 받는이 이름 변경하기 + tvMyLetterDetailAiGeneratedDescription.isVisible = false + } + } else { + llMyLetterDetailReplied.isVisible = false + } + } + } + } + } + launch { + viewModel.deleteLetterThreadResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + Toast.makeText(context, "내가 쓴 편지가 삭제되었습니다.", Toast.LENGTH_SHORT).show() + findNavController().popBackStack() + } + } + } + } + } + } + } + + private fun formatDate(createdDateTime: String): String { + val instant = Instant.parse(createdDateTime) + val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") + .withZone(ZoneId.systemDefault()) + return formatter.format(instant) } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/MyLettersFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/MyLettersFragment.kt index d4600884..86399cb7 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/MyLettersFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/MyLettersFragment.kt @@ -3,82 +3,44 @@ package com.egobook.app.ui.square.view import android.os.Bundle import androidx.fragment.app.Fragment import android.view.View +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.egobook.app.R import com.egobook.app.databinding.FragmentMyLettersBinding -import com.egobook.app.ui.square.adapter.MyLettersAdapter -import com.egobook.app.ui.square.model.friend.LetterModel -import com.egobook.app.ui.square.model.friend.ReceivedModel +import com.egobook.app.ui.square.adapter.MySentLettersAdapter +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch class MyLettersFragment : Fragment(R.layout.fragment_my_letters) { private lateinit var binding: FragmentMyLettersBinding - private val adapter = MyLettersAdapter { - val action = MyLettersFragmentDirections.actionMyLettersFragmentToMyLetterDetailFragment(letterItem = it) - findNavController().navigate(action) + private val adapter by lazy { + MySentLettersAdapter { letterId -> + val action = MyLettersFragmentDirections.actionMyLettersFragmentToMyLetterDetailFragment(letterId = letterId) + findNavController().navigate(action) + } } - private val dummyData = listOf( - LetterModel( - id = 1, - dateTime = "2025.01.20", - sentContent = "오늘 문득 내가 정말 잘 살고 있는지 의문이 들었어. 매일 똑같은 일상을 반복하다 보니 내 마음이 어디로 향하고 있는지 놓치고 있었던 것 같아. 그래서 오늘은 아주 오랜만에 나에게 집중하는 시간을 가져보려고 해.", - receivedContent = ReceivedModel( - senderNickname = "따뜻한 고북이", - receiverNickname = "나긋한 고북이", - letterContent = "따뜻한 편지 고마워요. 저도 요즘 비슷한 고민을 하고 있었는데 보내주신 글을 읽고 큰 위로를 받았어요. 우리 천천히, 하지만 꾸준히 나아가 봐요." - ) - ), - LetterModel( - id = 2, - dateTime = "2025.01.18", - sentContent = "요즘 날씨가 부쩍 추워졌는데 건강하게 잘 지내고 있니? 나는 오늘 길을 걷다 예쁘게 핀 겨울꽃을 봤어. 모진 추위 속에서도 꿋꿋하게 피어난 꽃을 보니 문득 네 생각이 나더라.", - receivedContent = ReceivedModel( - senderNickname = "용기있는 고북이", - receiverNickname = "나긋한 고북이", - letterContent = "보내주신 꽃 이야기에 마음이 몽글몽글해졌어요. 사실 오늘 조금 지쳐 있었는데, 다시 힘을 낼 수 있을 것 같아요. 당신도 감기 조심하세요!" - ) - ), - LetterModel( - id = 3, - dateTime = "2025.01.15", - sentContent = "누군가에게 내 속마음을 말한다는 게 참 어려운 일인데, 익명의 힘을 빌려 너에게 고백해봐. 나는 사실 남들의 시선을 너무 많이 신경 쓰며 살고 있어. 너는 너만의 확고한 기준이 있니?", - receivedContent = ReceivedModel( - senderNickname = "지혜로운 고북이", - receiverNickname = "나긋한 고북이", - letterContent = "자신의 모습을 마주하는 것부터가 시작이에요. 남들이 뭐라 하든 내가 행복한 일을 찾아보세요. 당신은 충분히 멋진 사람입니다." - ) - ), - LetterModel( - id = 4, - dateTime = "2025.01.10", - sentContent = "오늘은 내가 가장 좋아하는 책의 한 구절을 너에게 공유해주고 싶어. '삶은 속도가 아니라 방향이다'라는 말 들어본 적 있니? 조금 늦더라도 우리가 원하는 방향으로 가고 있다면 충분해.", - receivedContent = ReceivedModel( - senderNickname = "차분한 고북이", - receiverNickname = "나긋한 고북이", - letterContent = "그 문장, 저도 정말 좋아해요! 속도에 치여 살다 보면 소중한 걸 놓치기 쉬운데, 방향을 잘 잡고 있다면 잠시 쉬어가도 괜찮은 것 같아요." - ) - ), - LetterModel( - id = 5, - dateTime = "2025.01.05", - sentContent = "새해 계획은 잘 실천하고 있니? 나는 거창한 계획 대신 '하루에 한 번 나 칭찬하기'를 목표로 세웠어. 사소한 일이라도 나를 격려해주니 자존감이 조금씩 올라가는 기분이 들어.", - receivedContent = ReceivedModel( - senderNickname = "다정한 고북이", - receiverNickname = "나긋한 고북이", - letterContent = "정말 멋진 목표네요! 저도 오늘부터 따라 해봐야겠어요. 자책 대신 '포기하지 않은 나'를 칭찬해주는 하루가 되어볼게요. 감사합니다." - ) - ) - ) + + private val viewModel: LetterViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentMyLettersBinding.bind(view) + fetchData() initViews() initListeners() + initObservers() + } + + private fun fetchData() { + viewModel.getSentLetters(size = 10) } private fun initViews() = with(binding) { - rvMyLetters.adapter = adapter - adapter.submitList(dummyData) + rvMySentLetters.adapter = adapter } private fun initListeners() = with(binding) { @@ -86,4 +48,18 @@ class MyLettersFragment : Fragment(R.layout.fragment_my_letters) { findNavController().popBackStack() } } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.sentLetters.collectLatest { pagingData -> + if(pagingData != null) { + adapter.submitData(lifecycle, pagingData) + } + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/PremiumLetterBuyDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/PremiumLetterBuyDialog.kt new file mode 100644 index 00000000..66b92b7c --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/PremiumLetterBuyDialog.kt @@ -0,0 +1,60 @@ +package com.egobook.app.ui.square.view + +import android.app.Dialog +import android.graphics.Color +import android.os.Bundle +import android.view.View +import androidx.core.graphics.drawable.toDrawable +import androidx.fragment.app.DialogFragment +import com.egobook.app.R +import com.egobook.app.databinding.DialogPremiumLetterBuyBinding +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.removeScreenBlur + +class PremiumLetterBuyDialog(private val letterColor: LetterBackgroundColor): DialogFragment(R.layout.dialog_premium_letter_buy) { + private lateinit var binding: DialogPremiumLetterBuyBinding + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogPremiumLetterBuyBinding.bind(view) + initViews() + initListeners() + } + + private fun initViews() = with(binding) { + val backgroundColor = when(letterColor) { + LetterBackgroundColor.PINK -> R.color.letter_bg_pink + LetterBackgroundColor.GREEN -> R.color.letter_bg_green + LetterBackgroundColor.BLUE -> R.color.letter_bg_blue + else -> R.color.letter_bg_purple + } + val letterBanner = when(letterColor) { + LetterBackgroundColor.PINK -> R.drawable.img_letter_pink + LetterBackgroundColor.GREEN -> R.drawable.img_letter_green + LetterBackgroundColor.BLUE -> R.drawable.img_letter_blue + else -> R.drawable.img_letter_purple + } + cvPremiumLetterColor.backgroundTintList = resources.getColorStateList(backgroundColor,null) + ivPremiumLetterBanner.setImageResource(letterBanner) + } + + private fun initListeners() = with(binding) { + btnPremiumLetterBack.setOnClickListener { + dismiss() + removeScreenBlur() + } + btnPremiumLetterBuy.setOnClickListener { + // TODO: 유저 정보 연동 필요 + } + } + + companion object { + const val TAG = "PremiumLetterBuyDialog" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/view/SquareAllRepliesFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/SquareAllRepliesFragment.kt index c5c0aa3d..4bcd3410 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/SquareAllRepliesFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/SquareAllRepliesFragment.kt @@ -22,10 +22,7 @@ class SquareAllRepliesFragment : Fragment(R.layout.fragment_square_all_replies) private lateinit var binding: FragmentSquareAllRepliesBinding private val adapter by lazy { SquareAllRepliesAdapter { - val dialog = SquareReportDialog() - dialog.isCancelable = false - dialog.show(childFragmentManager, SquareReportDialog.TAG) - applyScreenBlur(BlurLevel.BASE) + } } private val viewModel: QuestionViewModel by activityViewModels() diff --git a/app/src/main/java/com/egobook/app/ui/square/view/SquareFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/SquareFragment.kt index 9606b3a8..7d9f442b 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/SquareFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/SquareFragment.kt @@ -14,15 +14,19 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController +import com.egobook.app.BlurLevel import com.egobook.app.R +import com.egobook.app.applyScreenBlur import com.egobook.app.databinding.FragmentSquareBinding import com.egobook.app.databinding.LayoutPopupVisibilityTypeBinding import com.egobook.app.domain.model.square.question.AnswerVisibility -import com.egobook.app.domain.model.square.question.TodayAnswer +import com.egobook.app.ui.square.adapter.MySentLettersAdapter import com.egobook.app.ui.square.adapter.TodayQuestionFriendRepliesAdapter +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterModel import com.egobook.app.ui.square.model.question.SubmitStatus import com.egobook.app.ui.square.model.question.TodayAnswerModel import com.egobook.app.ui.square.model.question.TodayQuestionModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.ui.square.viewmodel.QuestionViewModel import com.egobook.app.util.UiState import kotlinx.coroutines.flow.collectLatest @@ -31,13 +35,21 @@ import kotlinx.coroutines.launch class SquareFragment : Fragment(R.layout.fragment_square) { private lateinit var binding: FragmentSquareBinding private val questionViewModel: QuestionViewModel by activityViewModels() + private val letterViewModel: LetterViewModel by activityViewModels() private var visibilityType = AnswerVisibility.PUBLIC // 오늘의 질문 답변 제출할 때 필요한 변수 - private val adapter by lazy { + private val todayQuestionAdapter by lazy { TodayQuestionFriendRepliesAdapter() } + private val sentLetterAdapter by lazy { + MySentLettersAdapter { letterId -> + val action = SquareFragmentDirections.actionMenuSquareToMyLetterDetailFragment(letterId = letterId) + findNavController().navigate(action) + } + } + private var todayQuestionContent: String? = null private var submitButtonStatus: SubmitStatus = SubmitStatus.CREATE @@ -54,10 +66,13 @@ class SquareFragment : Fragment(R.layout.fragment_square) { private fun fetchData() { questionViewModel.getTodayQuestion() questionViewModel.getTodayFriendsReplies(size = 3) + letterViewModel.getArrivedPendingLetter() + letterViewModel.getSentLetters(size = 4) } private fun initViews() = with(binding) { - rvSquareTodayQuestionFriendReply.adapter = adapter + rvSquareTodayQuestionFriendReply.adapter = todayQuestionAdapter + rvSquareSentLetter.adapter = sentLetterAdapter } private fun initListeners() = with(binding) { @@ -120,12 +135,17 @@ class SquareFragment : Fragment(R.layout.fragment_square) { cvSquareTodayQuestionAnswered.isVisible = false cvSquareTodayQuestionWriting.isVisible = true } + cvSquareWriteLetter.setOnClickListener { + findNavController().navigate(R.id.action_menu_square_to_letterWriteFragment) + } } /** * 1. PopupWindow 생성자 * 두번째, 세번째 인자: 팝업창의 너비와 높이 - * 네번째 인자: true로 설정하면 팝업이 포커스를 가진다. 팝업이 포커스를 가지게 되면 팝업 바깥 영역을 터치했을 때 팝업이 자동으로 닫힌다. + * 네번째 인자: true로 설정하면 팝업이 포커스를 가진다. + * 팝업이 포커스를 가지게 되면 팝업 바깥 영역을 터치했을 때 팝업이 자동으로 닫힌다. + * 그리고 버튼을 다시 클릭해도 팝업창이 그대로 열린게 유지되는게 아니라 닫힌다. * 2. 뷰가 화면에 실제로 그려지기 전에 높이가 얼마인 지 미리 계산하는 함수이다. * 뷰의 크기는 화면에 그려진 후에야 알 수 있다. 하지만 팝업을 띄우기 전에 팝업의 높이를 알아야 적절한 위치에 띄울 수 있기 때문에, 먼저 계산해야 한다. * UNSPECIFIED 는 부모 뷰의 제약 없이 뷰가 원하는 만큼의 크기를 계산하라는 모드이다. @@ -231,7 +251,7 @@ class SquareFragment : Fragment(R.layout.fragment_square) { launch { questionViewModel.todayFriendsReplies.collectLatest { pagingData -> if(pagingData != null) { - adapter.submitData(lifecycle = lifecycle, pagingData = pagingData) + todayQuestionAdapter.submitData(lifecycle = lifecycle, pagingData = pagingData) } } } @@ -249,6 +269,30 @@ class SquareFragment : Fragment(R.layout.fragment_square) { } } } + launch { + letterViewModel.arrivedPendingLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val arrivedPendingLetter = state.data.letter + if(arrivedPendingLetter != null) { + val dialog = ArrivedPendingLetterPopupDialog(letterInfo = arrivedPendingLetter).apply { isCancelable = false } + dialog.show(childFragmentManager, ArrivedPendingLetterPopupDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + } + } + } + } + } + launch { + letterViewModel.sentLetters.collectLatest { pagingData -> + if(pagingData != null) { + sentLetterAdapter.submitData(lifecycle, pagingData) + } + } + } } } } diff --git a/app/src/main/java/com/egobook/app/ui/square/view/SquareReportDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/SquareReportDialog.kt index b7eb82e4..598afda0 100644 --- a/app/src/main/java/com/egobook/app/ui/square/view/SquareReportDialog.kt +++ b/app/src/main/java/com/egobook/app/ui/square/view/SquareReportDialog.kt @@ -3,15 +3,38 @@ package com.egobook.app.ui.square.view import android.app.Dialog import android.graphics.Color import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.View +import android.widget.Toast import androidx.core.graphics.drawable.toDrawable +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.egobook.app.R import com.egobook.app.databinding.DialogSquareReportBinding +import com.egobook.app.domain.model.square.letter.ReportLetterType import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.ReportLetterModel +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState +import kotlinx.coroutines.launch -class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { +class SquareReportDialog(private val letterId: Long, private val replyId: Long) : + DialogFragment(R.layout.dialog_square_report) { private lateinit var binding: DialogSquareReportBinding + private val viewModel: LetterViewModel by activityViewModels() + private val reportReasonsWithoutEtc by lazy { + listOf( + binding.tvSquareReportAbuse, + binding.tvSquareReportSpam, + binding.tvSquareReportInappropriate + ) + } + private lateinit var reportType: ReportLetterType override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return Dialog(requireContext()).apply { @@ -23,6 +46,7 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { super.onViewCreated(view, savedInstanceState) binding = DialogSquareReportBinding.bind(view) initListeners() + initObservers() } private fun initListeners() = with(binding) { @@ -30,6 +54,73 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { removeScreenBlur() dismiss() } + reportReasonsWithoutEtc.forEach { content -> + content.setOnClickListener { + reportReasonsWithoutEtc.forEach { it.isSelected = false } + tvSquareReportEtcNotClicked.isSelected = false + llSquareReportEtcClicked.isVisible = false + tvSquareReportEtcNotClicked.isVisible = true + content.isSelected = true + btnSquareReportSubmit.isEnabled = true + reportType = when(content) { + tvSquareReportAbuse -> ReportLetterType.ABUSE + tvSquareReportSpam -> ReportLetterType.SPAM + else -> ReportLetterType.INAPPROPRIATE + } + } + } + tvSquareReportEtcNotClicked.setOnClickListener { + reportReasonsWithoutEtc.forEach { it.isSelected = false } + it.isSelected = true + llSquareReportEtcClicked.isVisible = true + tvSquareReportEtcNotClicked.isVisible = false + btnSquareReportSubmit.isEnabled = !etSquareReportEtcReason.text.isNullOrBlank() + reportType = ReportLetterType.OTHER + } + etSquareReportEtcReason.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(p0: Editable?) = Unit + override fun beforeTextChanged( + p0: CharSequence?, + p1: Int, + p2: Int, + p3: Int + ) = Unit + + override fun onTextChanged( + text: CharSequence?, + start: Int, + before: Int, + count: Int + ) { + btnSquareReportSubmit.isEnabled = !text.isNullOrBlank() + } + }) + btnSquareReportSubmit.setOnClickListener { + viewModel.reportRepliedLetter(replyId = replyId, reportLetter = ReportLetterModel( + reason = reportType, + description = if(reportType == ReportLetterType.OTHER) etSquareReportEtcReason.text.toString() else null + )) + } + } + + private fun initObservers() { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.reportRepliedLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + Toast.makeText(context, "답장이 신고되었습니다.", Toast.LENGTH_SHORT).show() + viewModel.getSentLetterWithReply(letterId = letterId) + removeScreenBlur() + dismiss() + } + } + } + } + } } companion object { diff --git a/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt b/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt new file mode 100644 index 00000000..cfe7054f --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt @@ -0,0 +1,205 @@ +package com.egobook.app.ui.square.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map +import com.egobook.app.domain.usecase.GetFriendListUseCase +import com.egobook.app.domain.usecase.letter.DeferReplyLetterUseCase +import com.egobook.app.domain.usecase.letter.DeleteLetterThreadUseCase +import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase +import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase +import com.egobook.app.domain.usecase.letter.GetSentLetterWithReplyUseCase +import com.egobook.app.domain.usecase.letter.GetSentLettersUseCase +import com.egobook.app.domain.usecase.letter.GiveUpReplyLetterUseCase +import com.egobook.app.domain.usecase.letter.ReplyLetterUseCase +import com.egobook.app.domain.usecase.letter.ReportRepliedLetterUseCase +import com.egobook.app.domain.usecase.letter.SendLetterUseCase +import com.egobook.app.ui.square.model.friend.FriendModel +import com.egobook.app.ui.square.model.friend.toPresentation +import com.egobook.app.ui.square.model.letter.AbusiveContentModel +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterModel +import com.egobook.app.ui.square.model.letter.ReplyLetterModel +import com.egobook.app.ui.square.model.letter.ReportLetterModel +import com.egobook.app.ui.square.model.letter.SendLetterModel +import com.egobook.app.ui.square.model.letter.SentLetterModel +import com.egobook.app.ui.square.model.letter.SentLetterWithReplyModel +import com.egobook.app.ui.square.model.letter.toDomain +import com.egobook.app.ui.square.model.letter.toPresentation +import com.egobook.app.util.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LetterViewModel @Inject constructor( + private val getFriendListUseCase: GetFriendListUseCase, + private val sendLetterUseCase: SendLetterUseCase, + private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase, + private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase, + private val replyLetterUseCase: ReplyLetterUseCase, + private val deferReplyLetterUseCase: DeferReplyLetterUseCase, + private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase, + private val getSentLettersUseCase: GetSentLettersUseCase, + private val getSentLetterWithReplyUseCase: GetSentLetterWithReplyUseCase, + private val reportRepliedLetterUseCase: ReportRepliedLetterUseCase, + private val deleteLetterThreadUseCase: DeleteLetterThreadUseCase +): ViewModel() { + + private val _friendList = MutableStateFlow>>(UiState.Idle) + val friendList = _friendList.asStateFlow() + + fun getFriendList() { + viewModelScope.launch { + _friendList.value = UiState.Loading + getFriendListUseCase().onSuccess { domainList -> + _friendList.value = UiState.Success(domainList.map { it.toPresentation() }) + }.onFailure { error -> + _friendList.value = UiState.Failure(error.message) + } + } + } + + private val _sendLetterResult = MutableSharedFlow>() + val sendLetterResult = _sendLetterResult.asSharedFlow() + + fun sendLetter(letter: SendLetterModel) { + viewModelScope.launch { + _sendLetterResult.emit(UiState.Loading) + sendLetterUseCase(letter = letter.toDomain()).onSuccess { + _sendLetterResult.emit(UiState.Success(it)) + }.onFailure { error -> + _sendLetterResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _detectAbusiveContentResult = MutableSharedFlow>() + val detectAbusiveContentResult = _detectAbusiveContentResult.asSharedFlow() + + fun detectAbusiveContent(text: String) { + viewModelScope.launch { + _detectAbusiveContentResult.emit(UiState.Loading) + detectAbusiveContentUseCase(text = text).onSuccess { domain -> + _detectAbusiveContentResult.emit(UiState.Success(domain.toPresentation())) + }.onFailure { error -> + _detectAbusiveContentResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _arrivedPendingLetterResult = MutableStateFlow>(UiState.Idle) // stateflow vs sharedflow + val arrivedPendingLetterResult = _arrivedPendingLetterResult.asStateFlow() + + fun getArrivedPendingLetter() { + viewModelScope.launch { + _arrivedPendingLetterResult.value = UiState.Loading + getArrivedPendingLetterUseCase().onSuccess { domain -> + _arrivedPendingLetterResult.value = UiState.Success(domain.toPresentation()) + }.onFailure { error -> + _arrivedPendingLetterResult.value = UiState.Failure(error.message) + } + } + } + + private val _replyLetterResult = MutableSharedFlow>() + val replyLetterResult = _replyLetterResult.asSharedFlow() + + fun replyLetter(letterId: Long, text: String) { + viewModelScope.launch { + _replyLetterResult.emit(UiState.Loading) + replyLetterUseCase(letterId = letterId, text = text).onSuccess { domain -> + _replyLetterResult.emit(UiState.Success(domain.toPresentation())) + }.onFailure { error -> + _replyLetterResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _deferReplyLetterResult = MutableSharedFlow>() + val deferReplyLetterResult = _deferReplyLetterResult.asSharedFlow() + + fun deferReplyLetter(letterId: Long) { + viewModelScope.launch { + _deferReplyLetterResult.emit(UiState.Loading) + deferReplyLetterUseCase(letterId = letterId).onSuccess { + _deferReplyLetterResult.emit(UiState.Success(Unit)) + }.onFailure { error -> + _deferReplyLetterResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _giveUpReplyLetterResult = MutableSharedFlow>() + val giveUpReplyLetterResult = _giveUpReplyLetterResult.asSharedFlow() + + fun giveUpReplyLetter(letterId: Long) { + viewModelScope.launch { + _giveUpReplyLetterResult.emit(UiState.Loading) + giveUpReplyLetterUseCase(letterId = letterId).onSuccess { + _giveUpReplyLetterResult.emit(UiState.Success(Unit)) + }.onFailure { error -> + _giveUpReplyLetterResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _sentLetters = MutableStateFlow?>(null) + val sentLetters = _sentLetters.asStateFlow() + + fun getSentLetters(size: Int) { + viewModelScope.launch { + getSentLettersUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> + _sentLetters.value = pagingData.map { it.toPresentation() } + } + } + } + + private val _sentLetterWithReply = MutableStateFlow>(UiState.Idle) + val sentLetterWithReply = _sentLetterWithReply.asStateFlow() + + fun getSentLetterWithReply(letterId: Long) { + viewModelScope.launch { + _sentLetterWithReply.value = UiState.Loading + getSentLetterWithReplyUseCase(letterId = letterId).onSuccess { domain -> + _sentLetterWithReply.value = UiState.Success(domain.toPresentation()) + }.onFailure { error -> + _sentLetterWithReply.value = UiState.Failure(error.message) + } + } + } + + private val _reportRepliedLetterResult = MutableSharedFlow>() + val reportRepliedLetterResult = _reportRepliedLetterResult.asSharedFlow() + + fun reportRepliedLetter(replyId: Long, reportLetter: ReportLetterModel) { + viewModelScope.launch { + _reportRepliedLetterResult.emit(UiState.Loading) + reportRepliedLetterUseCase(replyId = replyId, reportLetter = reportLetter.toDomain()).onSuccess { + _reportRepliedLetterResult.emit(UiState.Success(it)) + }.onFailure { error -> + _reportRepliedLetterResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _deleteLetterThreadResult = MutableSharedFlow>() + val deleteLetterThreadResult = _deleteLetterThreadResult.asSharedFlow() + + fun deleteLetterThread(threadId: Long) { + viewModelScope.launch { + _deleteLetterThreadResult.emit(UiState.Loading) + deleteLetterThreadUseCase(threadId = threadId).onSuccess { + _deleteLetterThreadResult.emit(UiState.Success(it)) + }.onFailure { error -> + _deleteLetterThreadResult.emit(UiState.Failure(error.message)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/color/selector_letter_paper_color_stroke.xml b/app/src/main/res/color/selector_letter_paper_color_stroke.xml new file mode 100644 index 00000000..004f4726 --- /dev/null +++ b/app/src/main/res/color/selector_letter_paper_color_stroke.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_square_today_question_save_button_bg.xml b/app/src/main/res/color/selector_square_button_bg.xml similarity index 100% rename from app/src/main/res/color/selector_square_today_question_save_button_bg.xml rename to app/src/main/res/color/selector_square_button_bg.xml diff --git a/app/src/main/res/color/selector_square_today_question_save_button_text.xml b/app/src/main/res/color/selector_square_button_text.xml similarity index 100% rename from app/src/main/res/color/selector_square_today_question_save_button_text.xml rename to app/src/main/res/color/selector_square_button_text.xml diff --git a/app/src/main/res/color/selector_square_report_button_bg.xml b/app/src/main/res/color/selector_square_report_button_bg.xml new file mode 100644 index 00000000..31e1e383 --- /dev/null +++ b/app/src/main/res/color/selector_square_report_button_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_square_report_textview_bg.xml b/app/src/main/res/color/selector_square_report_textview_bg.xml new file mode 100644 index 00000000..ef523ead --- /dev/null +++ b/app/src/main/res/color/selector_square_report_textview_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_weekly_report_style_bg.xml b/app/src/main/res/color/selector_weekly_report_style_bg.xml index 994318fa..aeafbcc4 100644 --- a/app/src/main/res/color/selector_weekly_report_style_bg.xml +++ b/app/src/main/res/color/selector_weekly_report_style_bg.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/color/selector_weekly_report_style_text.xml b/app/src/main/res/color/selector_weekly_report_style_text.xml index 8b63c441..235336b6 100644 --- a/app/src/main/res/color/selector_weekly_report_style_text.xml +++ b/app/src/main/res/color/selector_weekly_report_style_text.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_round_dialog.xml b/app/src/main/res/drawable/bg_round_and_white_dialog.xml similarity index 100% rename from app/src/main/res/drawable/bg_round_dialog.xml rename to app/src/main/res/drawable/bg_round_and_white_dialog.xml diff --git a/app/src/main/res/drawable/bg_white_and_radius.xml b/app/src/main/res/drawable/bg_white_and_radius.xml new file mode 100644 index 00000000..4b894c9f --- /dev/null +++ b/app/src/main/res/drawable/bg_white_and_radius.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 00000000..621f3b59 --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_lock_key.xml b/app/src/main/res/drawable/ic_lock_key.xml new file mode 100644 index 00000000..ec60acf8 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_key.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/img_detect_words_failure.png b/app/src/main/res/drawable/img_detect_words_failure.png new file mode 100644 index 00000000..6785c11c Binary files /dev/null and b/app/src/main/res/drawable/img_detect_words_failure.png differ diff --git a/app/src/main/res/drawable/img_detect_words_loading.png b/app/src/main/res/drawable/img_detect_words_loading.png new file mode 100644 index 00000000..5d9db337 Binary files /dev/null and b/app/src/main/res/drawable/img_detect_words_loading.png differ diff --git a/app/src/main/res/drawable/img_letter_blue.png b/app/src/main/res/drawable/img_letter_blue.png new file mode 100644 index 00000000..45ba33f7 Binary files /dev/null and b/app/src/main/res/drawable/img_letter_blue.png differ diff --git a/app/src/main/res/drawable/img_letter_green.png b/app/src/main/res/drawable/img_letter_green.png new file mode 100644 index 00000000..52845162 Binary files /dev/null and b/app/src/main/res/drawable/img_letter_green.png differ diff --git a/app/src/main/res/drawable/img_letter_pink.png b/app/src/main/res/drawable/img_letter_pink.png new file mode 100644 index 00000000..b5be8807 Binary files /dev/null and b/app/src/main/res/drawable/img_letter_pink.png differ diff --git a/app/src/main/res/drawable/img_letter_purple.png b/app/src/main/res/drawable/img_letter_purple.png new file mode 100644 index 00000000..4101e601 Binary files /dev/null and b/app/src/main/res/drawable/img_letter_purple.png differ diff --git a/app/src/main/res/drawable/img_send.png b/app/src/main/res/drawable/img_send.png new file mode 100644 index 00000000..6446b903 Binary files /dev/null and b/app/src/main/res/drawable/img_send.png differ diff --git a/app/src/main/res/layout/dialog_letter.xml b/app/src/main/res/layout/dialog_arrived_pending_letter.xml similarity index 56% rename from app/src/main/res/layout/dialog_letter.xml rename to app/src/main/res/layout/dialog_arrived_pending_letter.xml index f9256809..0a0d5b07 100644 --- a/app/src/main/res/layout/dialog_letter.xml +++ b/app/src/main/res/layout/dialog_arrived_pending_letter.xml @@ -1,49 +1,52 @@ + app:layout_constraintTop_toBottomOf="@id/iv_arrived_pending_letter_defer"> + app:layout_constraintTop_toTopOf="@id/iv_arrived_pending_letter_report" /> + app:layout_constraintTop_toBottomOf="@id/iv_arrived_pending_letter_report" /> + app:layout_constraintTop_toBottomOf="@id/tv_arrived_pending_letter_content" /> + app:layout_constraintEnd_toStartOf="@id/btn_arrived_pending_letter_reply" + app:layout_constraintStart_toStartOf="@id/cv_arrived_pending_letter_container" + app:layout_constraintTop_toBottomOf="@id/cv_arrived_pending_letter_container" /> + app:layout_constraintBottom_toBottomOf="@id/btn_arrived_pending_letter_give_up" + app:layout_constraintEnd_toEndOf="@id/cv_arrived_pending_letter_container" + app:layout_constraintStart_toEndOf="@id/btn_arrived_pending_letter_give_up" + app:layout_constraintTop_toTopOf="@id/btn_arrived_pending_letter_give_up" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml b/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml new file mode 100644 index 00000000..11b0f17d --- /dev/null +++ b/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_delete_friend.xml b/app/src/main/res/layout/dialog_delete_friend.xml index 876f99c2..3a770d59 100644 --- a/app/src/main/res/layout/dialog_delete_friend.xml +++ b/app/src/main/res/layout/dialog_delete_friend.xml @@ -2,7 +2,7 @@ @@ -23,6 +27,10 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="상대방 목록에서도 사라져요" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" app:layout_constraintEnd_toEndOf="@+id/tv_delete_friend_title" app:layout_constraintStart_toStartOf="@+id/tv_delete_friend_title" app:layout_constraintTop_toBottomOf="@+id/tv_delete_friend_title" /> @@ -35,6 +43,8 @@ android:backgroundTint="@color/grey_light" android:text="돌아가기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:insetTop="0dp" android:insetBottom="0dp" app:layout_constraintHorizontal_chainStyle="packed" @@ -51,6 +61,8 @@ android:backgroundTint="@color/cos_red_dark" android:text="삭제하기" android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:insetTop="0dp" android:insetBottom="0dp" app:cornerRadius="4dp" diff --git a/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml b/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml new file mode 100644 index 00000000..e88f17b7 --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml b/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml new file mode 100644 index 00000000..4a9c03cc --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_detect_abusive_content_success.xml b/app/src/main/res/layout/dialog_detect_abusive_content_success.xml new file mode 100644 index 00000000..752eb7a0 --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_success.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_discard_reply.xml b/app/src/main/res/layout/dialog_give_up_reply_letter.xml similarity index 53% rename from app/src/main/res/layout/dialog_discard_reply.xml rename to app/src/main/res/layout/dialog_give_up_reply_letter.xml index ab0f7611..a42b749d 100644 --- a/app/src/main/res/layout/dialog_discard_reply.xml +++ b/app/src/main/res/layout/dialog_give_up_reply_letter.xml @@ -5,53 +5,70 @@ android:layout_height="wrap_content" android:paddingVertical="24dp" android:paddingHorizontal="16dp" - android:background="@color/white"> + android:background="@drawable/bg_white_and_radius"> + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" + android:textAlignment="center" + app:layout_constraintEnd_toEndOf="@+id/tv_give_up_reply_letter_title" + app:layout_constraintStart_toStartOf="@+id/tv_give_up_reply_letter_title" + app:layout_constraintTop_toBottomOf="@+id/tv_give_up_reply_letter_title" /> + app:layout_constraintTop_toBottomOf="@+id/tv_give_up_reply_letter_description" + app:layout_constraintEnd_toStartOf="@id/btn_give_up_reply_letter"/> + app:layout_constraintStart_toEndOf="@+id/btn_give_up_reply_letter_defer" + app:layout_constraintTop_toTopOf="@+id/btn_give_up_reply_letter_defer" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_letter_arrived.xml b/app/src/main/res/layout/dialog_letter_arrived.xml deleted file mode 100644 index eb3c5184..00000000 --- a/app/src/main/res/layout/dialog_letter_arrived.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_letter_send.xml b/app/src/main/res/layout/dialog_letter_send.xml new file mode 100644 index 00000000..143b8306 --- /dev/null +++ b/app/src/main/res/layout/dialog_letter_send.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_premium_letter_buy.xml b/app/src/main/res/layout/dialog_premium_letter_buy.xml new file mode 100644 index 00000000..81e4f584 --- /dev/null +++ b/app/src/main/res/layout/dialog_premium_letter_buy.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_square_add_friend.xml b/app/src/main/res/layout/dialog_square_add_friend.xml index 069d4101..d840a1ac 100644 --- a/app/src/main/res/layout/dialog_square_add_friend.xml +++ b/app/src/main/res/layout/dialog_square_add_friend.xml @@ -3,7 +3,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingHorizontal="16dp" - android:background="@drawable/bg_round_dialog" + android:background="@drawable/bg_round_and_white_dialog" android:paddingVertical="24dp" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> @@ -13,6 +13,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="친구 추가하기" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> @@ -47,19 +50,28 @@ + android:text="계정 ID" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:text="ABC123456789" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> @@ -94,7 +109,8 @@ + android:orientation="horizontal" + android:gravity="center_vertical"> + android:text="검색" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold"/> @@ -155,6 +177,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 9999" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toTopOf="@id/iv_add_friend_search_level" app:layout_constraintStart_toEndOf="@id/iv_add_friend_search_level" app:layout_constraintBottom_toBottomOf="@id/iv_add_friend_search_level"/> @@ -164,6 +189,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="친구 이름" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="18dp" app:layout_constraintTop_toBottomOf="@id/iv_add_friend_search_level" app:layout_constraintStart_toStartOf="@id/iv_add_friend_search_level" @@ -184,7 +212,9 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:text="신청하기" - android:textColor="@color/white" + android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:backgroundTint="@color/neutral" android:layout_marginTop="18dp" android:insetTop="0dp" @@ -202,6 +232,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="해당하는 유저가 없어요\nID를 다시 입력해 보세요" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:layout_marginTop="52dp" android:layout_marginBottom="36dp" android:visibility="gone" diff --git a/app/src/main/res/layout/dialog_square_report.xml b/app/src/main/res/layout/dialog_square_report.xml index 6aec7331..498a2de6 100644 --- a/app/src/main/res/layout/dialog_square_report.xml +++ b/app/src/main/res/layout/dialog_square_report.xml @@ -1,145 +1,208 @@ + android:paddingHorizontal="16dp" + android:paddingVertical="24dp"> + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/tv_square_report_title" /> + app:layout_constraintTop_toBottomOf="@id/tv_square_report_description" /> + app:layout_constraintTop_toBottomOf="@id/tv_square_report_abuse" /> + app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_abuse" /> + app:layout_constraintTop_toBottomOf="@id/tv_square_report_spam" /> + app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_spam" /> + app:layout_constraintTop_toBottomOf="@id/tv_square_report_inappropriate" /> - + app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_inappropriate"> + + + + + + + app:layout_constraintTop_toBottomOf="@id/fl_square_report_etc" /> + app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_etc" /> + app:layout_constraintTop_toTopOf="@id/btn_square_report_back" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_weekly_report_unlock.xml b/app/src/main/res/layout/dialog_weekly_report_unlock.xml new file mode 100644 index 00000000..ea157dc5 --- /dev/null +++ b/app/src/main/res/layout/dialog_weekly_report_unlock.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_ego_room.xml b/app/src/main/res/layout/fragment_ego_room.xml index 7425a846..9380f27f 100644 --- a/app/src/main/res/layout/fragment_ego_room.xml +++ b/app/src/main/res/layout/fragment_ego_room.xml @@ -1,7 +1,7 @@ + app:layout_constraintTop_toTopOf="parent"> + android:textColor="@color/neutral" + android:textSize="20sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:fontFamily="@font/arita_medium" + android:text="나를 돌보는 시간, 에고북의 위로와 조언" + android:textColor="@color/neutral" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@id/tv_counseling_main_title" + app:layout_constraintTop_toBottomOf="@id/tv_counseling_main_title" /> + app:layout_constraintTop_toBottomOf="@id/tv_counseling_sub_title" + app:tabIndicatorColor="@color/brand" + app:tabIndicatorFullWidth="true" + app:tabSelectedTextColor="@color/neutral" + app:tabTextAppearance="@style/CounselingTabTextAppearance" + app:tabTextColor="@color/neutral_subtle"> + + tools:text="일간 칭찬서" /> + + tools:text="주간 리포트" /> + + tools:text="통계" /> + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/cl_counseling_main_top_container" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_ego_room_daily_praise.xml b/app/src/main/res/layout/fragment_ego_room_daily_praise.xml index f346d8ee..5700ab34 100644 --- a/app/src/main/res/layout/fragment_ego_room_daily_praise.xml +++ b/app/src/main/res/layout/fragment_ego_room_daily_praise.xml @@ -24,7 +24,10 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - tools:text="일기를 작성하면 자정에 칭찬서가 도착해요"/> + tools:text="일기를 작성하면 자정에 칭찬서가 도착해요" + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium"/> + android:text="도착한 칭찬서가 없어요" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> diff --git a/app/src/main/res/layout/fragment_ego_room_statistics.xml b/app/src/main/res/layout/fragment_ego_room_statistics.xml index 9906d5fc..a313b89d 100644 --- a/app/src/main/res/layout/fragment_ego_room_statistics.xml +++ b/app/src/main/res/layout/fragment_ego_room_statistics.xml @@ -28,6 +28,9 @@ android:layout_height="wrap_content" android:paddingVertical="8dp" android:text="전체 감정 기록 횟수" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -58,6 +61,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="999회" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/hbc_counseling_statistics_total_count_very_happy" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/hbc_counseling_statistics_total_count_very_happy" @@ -89,6 +95,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="999회" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/hbc_counseling_statistics_total_count_happy" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/hbc_counseling_statistics_total_count_happy" @@ -120,6 +129,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="999회" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/hbc_counseling_statistics_total_count_neutral" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/hbc_counseling_statistics_total_count_neutral" @@ -151,6 +163,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="999회" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/hbc_counseling_statistics_total_count_sad" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/hbc_counseling_statistics_total_count_sad" @@ -182,6 +197,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="999회" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/hbc_counseling_statistics_total_count_very_sad" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/hbc_counseling_statistics_total_count_very_sad" @@ -203,6 +221,9 @@ android:layout_height="wrap_content" android:paddingVertical="8dp" android:text="요일별 감정 기록" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -236,14 +257,21 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:text="주로 기분이 좋았던 때" /> + android:text="주로 기분이 좋았던 때" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01"/> + tools:text="금 17시" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + android:text="주로 기분이 나빴던 때" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01"/> + tools:text="금 17시" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> @@ -290,6 +325,9 @@ android:layout_height="wrap_content" android:paddingVertical="8dp" android:text="6개월간 평균 감정 점수" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -370,14 +408,21 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - tools:text="10월 평균 점수" /> + tools:text="10월 평균 점수" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> + tools:text="3.2" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + tools:text="11월 평균 점수" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> + tools:text="4.3" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + tools:text="지난달 보다 1.1 상승했어요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> @@ -450,6 +506,9 @@ android:layout_height="wrap_content" android:paddingVertical="8dp" android:text="자주 쓴 단어" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/fragment_ego_room_weekly_report.xml b/app/src/main/res/layout/fragment_ego_room_weekly_report.xml index d7f9684c..b5cddb19 100644 --- a/app/src/main/res/layout/fragment_ego_room_weekly_report.xml +++ b/app/src/main/res/layout/fragment_ego_room_weekly_report.xml @@ -25,7 +25,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - tools:text="매일 월요일 자정 에고북이 작성한 리포트가 도착해요" /> + tools:text="매일 월요일 자정 에고북이 작성한 리포트가 도착해요" + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium"/> + app:contentPaddingTop="6dp" + app:contentPaddingBottom="6dp"> + app:contentPaddingTop="6dp" + app:contentPaddingBottom="6dp"> + app:contentPaddingTop="6dp" + app:contentPaddingBottom="6dp"> @@ -135,13 +147,21 @@ + android:text="도착한 리포트가 없어요" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> + android:text="알림을 설정하면 매주 월요일 리포트를\n받아볼 수 있어요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_ego_room_weekly_report_detail.xml b/app/src/main/res/layout/fragment_ego_room_weekly_report_detail.xml index d1c00af5..67fb50a4 100644 --- a/app/src/main/res/layout/fragment_ego_room_weekly_report_detail.xml +++ b/app/src/main/res/layout/fragment_ego_room_weekly_report_detail.xml @@ -34,7 +34,10 @@ android:id="@+id/tv_counseling_weekly_report_detail_datetime" android:layout_width="wrap_content" android:layout_height="wrap_content" - tools:text="2025.12.25" /> + tools:text="2025.12.25 ~ 2025.12.31" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> +분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -121,7 +131,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:text="이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 -분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" /> +분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -175,7 +192,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:text="이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 -분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" /> +분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -230,7 +254,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:text="이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 -분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" /> +분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -281,6 +312,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="응원 및 격려" + android:textColor="@color/brand" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold" android:layout_marginHorizontal="10dp" app:layout_constraintEnd_toStartOf="@id/iv_counseling_weekly_report_detail_encouragement_right" app:layout_constraintStart_toEndOf="@id/iv_counseling_weekly_report_detail_encouragement_left" @@ -318,7 +352,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:text="이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 -분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" /> +분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -327,6 +366,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="본 리포트는 사용자의 기록을 기반으로 한 조언이며,\n심리적 문제 발생 시 반드시 전문가의 도움을 받으시길 바랍니다." + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" android:textAlignment="center" android:layout_marginTop="52.61dp" android:layout_marginBottom="49.47dp" diff --git a/app/src/main/res/layout/fragment_friends_list.xml b/app/src/main/res/layout/fragment_friends_list.xml index b330d18a..033f1321 100644 --- a/app/src/main/res/layout/fragment_friends_list.xml +++ b/app/src/main/res/layout/fragment_friends_list.xml @@ -17,14 +17,31 @@ app:layout_constraintStart_toStartOf="parent"/> + + @@ -25,8 +28,11 @@ android:id="@+id/tv_friends_pending_list_received_num" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="4dp" + android:layout_marginStart="8dp" android:text="3" + android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/tv_friends_pending_list_received_title" app:layout_constraintStart_toEndOf="@id/tv_friends_pending_list_received_title" app:layout_constraintTop_toTopOf="@id/tv_friends_pending_list_received_title" /> @@ -47,6 +53,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="내가 한 신청" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/ll_friends_pending_list_received" app:layout_constraintStart_toStartOf="parent"/> diff --git a/app/src/main/res/layout/fragment_letter.xml b/app/src/main/res/layout/fragment_letter.xml deleted file mode 100644 index dd778310..00000000 --- a/app/src/main/res/layout/fragment_letter.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_letter_reply.xml b/app/src/main/res/layout/fragment_letter_reply.xml index 2106bec5..e30ba77c 100644 --- a/app/src/main/res/layout/fragment_letter_reply.xml +++ b/app/src/main/res/layout/fragment_letter_reply.xml @@ -23,6 +23,7 @@ app:layout_constraintTop_toTopOf="parent"> + android:text="답장 하기" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -76,13 +90,14 @@ + app:layout_constraintTop_toBottomOf="@id/cl_letter_reply_letter_received_container" + app:layout_constraintBottom_toTopOf="@id/btn_letter_reply"> - + app:layout_constraintTop_toBottomOf="@id/tv_letter_reply_mine_title" > + + - + app:cardBackgroundColor="@color/letter_bg_pink" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/Circle" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_reply_color_beige" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_beige" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_beige" > + + - + app:cardBackgroundColor="@color/letter_bg_green" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/Circle" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_reply_color_pink" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_pink" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_pink" > + + - + app:cardBackgroundColor="@color/letter_bg_blue" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/Circle" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_reply_color_green" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_green" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_green" > + + - + app:cardBackgroundColor="@color/letter_bg_purple" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/Circle" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_reply_color_blue" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_blue" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_blue"> + + @@ -176,52 +255,69 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintTop_toBottomOf="@id/iv_letter_reply_tooltip" + app:layout_constraintBottom_toTopOf="@id/tv_letter_reply_content_length"/> + app:layout_constraintBottom_toTopOf="@id/tv_letter_reply_sender" /> + app:layout_constraintEnd_toEndOf="parent"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_my_letter_detail.xml b/app/src/main/res/layout/fragment_my_letter_detail.xml index 75925a53..9b93af49 100644 --- a/app/src/main/res/layout/fragment_my_letter_detail.xml +++ b/app/src/main/res/layout/fragment_my_letter_detail.xml @@ -31,13 +31,17 @@ android:src="@drawable/ic_chevron_left" /> + tools:text="2025.12.25" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -62,14 +68,18 @@ android:layout_marginStart="16dp" android:layout_marginTop="24dp" android:text="나의 편지" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/ll_my_letter_detail_header" /> @@ -92,82 +102,120 @@ android:id="@+id/tv_my_letter_detail_sent_content" android:layout_width="match_parent" android:layout_height="match_parent" - android:text="사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 + tools:text="사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 -사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용" /> +사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> - - - + app:layout_constraintTop_toBottomOf="@id/cv_my_letter_detail_sent_content"> + + - - - - - - - + + + + + + + + - - - - - + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_my_letters.xml b/app/src/main/res/layout/fragment_my_letters.xml index bb8f1aa1..f7237908 100644 --- a/app/src/main/res/layout/fragment_my_letters.xml +++ b/app/src/main/res/layout/fragment_my_letters.xml @@ -28,11 +28,14 @@ + android:text="내가 쓴 편지" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> + android:text="나의 답변 기록" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> + android:text="광장" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -54,6 +59,9 @@ android:layout_marginHorizontal="16dp" android:layout_marginTop="24dp" android:text="편지" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/ll_square_header" /> @@ -75,6 +83,7 @@ android:showDividers="middle"> + android:text="편지쓰기" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:text="1/1" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium"/> @@ -131,13 +146,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="2dp" - android:text="짱구" /> + android:text="짱구" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" + android:layout_marginTop="2dp"/> @@ -165,13 +187,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="2dp" - android:text="흰둥이" /> + android:text="흰둥이" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" + android:layout_marginTop="2dp"/> @@ -199,47 +228,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="2dp" - android:text="짱아" /> + android:text="짱아" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> - - - - - - - - - - - - + android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" + android:layout_marginTop="2dp"/> @@ -263,7 +265,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:text="내가 쓴 편지" /> + android:text="내가 쓴 편지" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/ll_square_letter_mine_header" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/item_square_letter"/> + app:layout_constraintEnd_toEndOf="@id/rv_square_sent_letter" + app:layout_constraintStart_toStartOf="@id/rv_square_sent_letter" + app:layout_constraintTop_toBottomOf="@id/rv_square_sent_letter"> + android:text="오늘의 질문" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> + tools:text="Q. 다시 태어난다면 어느 나라에서 태어나고 싶으신가요?" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp"/> + tools:text="Q. 다시 태어난다면 어느 나라에서 태어나고 싶으신가요?" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp"/> + tools:text="사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답 사용자가 작성한 답" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -530,7 +555,11 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:text="Q. 다시 태어난다면 어느 나라에서 태어나고 싶으신가요?" /> + tools:text="Q. 다시 태어난다면 어느 나라에서 태어나고 싶으신가요?" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp"/> @@ -569,7 +603,9 @@ android:layout_marginTop="4dp" android:gravity="end" android:text="0/250" - android:textColor="@color/neutral_subtle" /> + android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> @@ -590,7 +626,11 @@ android:id="@+id/tv_square_today_question_input_visibility_type" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="전체 공개" /> + android:text="전체 공개" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp"/> + android:text="친구의 답변" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> + android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02"/> + android:src="@drawable/ic_square_view_all" />3 + android:text="전체 유저의 답변" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> + android:text="친구" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -53,6 +59,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:background="@android:color/transparent" + android:layout_marginHorizontal="16dp" + app:tabIndicatorColor="@color/brand" + app:tabIndicatorFullWidth="true" + app:tabSelectedTextColor="@color/neutral" + app:tabTextAppearance="@style/CounselingTabTextAppearance" + app:tabTextColor="@color/neutral_subtle" app:layout_constraintTop_toBottomOf="@id/ll_square_friends_header" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> diff --git a/app/src/main/res/layout/item_counseling_daily_praise.xml b/app/src/main/res/layout/item_counseling_daily_praise.xml index 1a5d6ec8..c04e7840 100644 --- a/app/src/main/res/layout/item_counseling_daily_praise.xml +++ b/app/src/main/res/layout/item_counseling_daily_praise.xml @@ -12,6 +12,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="2025.12.12" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> @@ -43,7 +46,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:lineSpacingExtra="4dp" - tools:text="칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리"/> + tools:text="칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> \ No newline at end of file diff --git a/app/src/main/res/layout/item_counseling_weekly_report.xml b/app/src/main/res/layout/item_counseling_weekly_report.xml index 6800dc2d..413e26db 100644 --- a/app/src/main/res/layout/item_counseling_weekly_report.xml +++ b/app/src/main/res/layout/item_counseling_weekly_report.xml @@ -12,14 +12,18 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="2025.12.12" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent"/> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent"/> diff --git a/app/src/main/res/layout/item_friend_popup_list.xml b/app/src/main/res/layout/item_friend_popup_list.xml new file mode 100644 index 00000000..db0f82d3 --- /dev/null +++ b/app/src/main/res/layout/item_friend_popup_list.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_square_friend_answer.xml b/app/src/main/res/layout/item_square_friend_answer.xml index 44f6e6fc..b56a8965 100644 --- a/app/src/main/res/layout/item_square_friend_answer.xml +++ b/app/src/main/res/layout/item_square_friend_answer.xml @@ -31,7 +31,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 1234" - android:layout_marginTop="4dp" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" + android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@id/civ_item_square_friend_answer_user_image" app:layout_constraintStart_toStartOf="@id/civ_item_square_friend_answer_user_image" app:layout_constraintEnd_toEndOf="@id/civ_item_square_friend_answer_user_image"/> @@ -41,6 +45,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="김철수" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" android:layout_marginStart="12dp" app:layout_constraintTop_toTopOf="@id/civ_item_square_friend_answer_user_image" app:layout_constraintStart_toEndOf="@id/civ_item_square_friend_answer_user_image"/> @@ -58,8 +65,13 @@ android:id="@+id/tv_item_square_friend_answer_user_content" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="8dp" + android:layout_marginTop="12dp" tools:text="친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" app:layout_constraintTop_toBottomOf="@id/tv_item_square_friend_answer_user_name" app:layout_constraintStart_toStartOf="@id/tv_item_square_friend_answer_user_name" app:layout_constraintEnd_toEndOf="parent"/> diff --git a/app/src/main/res/layout/item_square_friend_list.xml b/app/src/main/res/layout/item_square_friend_list.xml index 004133e0..a68357b4 100644 --- a/app/src/main/res/layout/item_square_friend_list.xml +++ b/app/src/main/res/layout/item_square_friend_list.xml @@ -38,6 +38,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 9999" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toTopOf="@id/iv_item_friend_list_level" app:layout_constraintStart_toEndOf="@id/iv_item_friend_list_level" app:layout_constraintBottom_toBottomOf="@id/iv_item_friend_list_level"/> @@ -47,6 +50,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="친구 이름" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="12dp" app:layout_constraintTop_toBottomOf="@id/iv_item_friend_list_level" app:layout_constraintStart_toStartOf="@id/iv_item_friend_list_level" diff --git a/app/src/main/res/layout/item_square_friend_pending_received_list.xml b/app/src/main/res/layout/item_square_friend_pending_received_list.xml index 4cfa2f37..f6fe9655 100644 --- a/app/src/main/res/layout/item_square_friend_pending_received_list.xml +++ b/app/src/main/res/layout/item_square_friend_pending_received_list.xml @@ -37,6 +37,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 9999" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toTopOf="@id/iv_item_friend_pending_list_level" app:layout_constraintStart_toEndOf="@id/iv_item_friend_pending_list_level" app:layout_constraintBottom_toBottomOf="@id/iv_item_friend_pending_list_level"/> @@ -46,6 +49,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="친구 이름" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="18dp" app:layout_constraintTop_toBottomOf="@id/iv_item_friend_pending_list_level" app:layout_constraintStart_toStartOf="@id/iv_item_friend_pending_list_level" @@ -67,6 +73,8 @@ android:layout_height="wrap_content" android:text="거절하기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:backgroundTint="@color/grey_light" android:layout_marginTop="18dp" android:insetTop="0dp" @@ -85,6 +93,8 @@ android:backgroundTint="@color/grey_light" android:text="수락하기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:insetTop="0dp" android:insetBottom="0dp" app:cornerRadius="4dp" diff --git a/app/src/main/res/layout/item_square_friend_pending_sent_list.xml b/app/src/main/res/layout/item_square_friend_pending_sent_list.xml index 07febe9f..e9d738c8 100644 --- a/app/src/main/res/layout/item_square_friend_pending_sent_list.xml +++ b/app/src/main/res/layout/item_square_friend_pending_sent_list.xml @@ -36,6 +36,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 9999" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toTopOf="@id/iv_item_friend_pending_sent_list_level" app:layout_constraintStart_toEndOf="@id/iv_item_friend_pending_sent_list_level" app:layout_constraintBottom_toBottomOf="@id/iv_item_friend_pending_sent_list_level"/> @@ -45,6 +48,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="친구 이름" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="18dp" app:layout_constraintTop_toBottomOf="@id/iv_item_friend_pending_sent_list_level" app:layout_constraintStart_toStartOf="@id/iv_item_friend_pending_sent_list_level" @@ -66,6 +72,8 @@ android:layout_height="wrap_content" android:text="취소하기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:backgroundTint="@color/grey_light" android:layout_marginTop="18dp" android:insetTop="0dp" diff --git a/app/src/main/res/layout/item_square_letter.xml b/app/src/main/res/layout/item_square_letter.xml index 1ce759a2..d795b2e4 100644 --- a/app/src/main/res/layout/item_square_letter.xml +++ b/app/src/main/res/layout/item_square_letter.xml @@ -1,6 +1,7 @@ @@ -18,7 +19,10 @@ android:id="@+id/tv_item_square_letter_datetime" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="2025.08.27" + tools:text="2025.08.27" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.02" android:layout_marginStart="8dp" app:layout_constraintTop_toTopOf="@id/iv_item_letter" app:layout_constraintStart_toEndOf="@id/iv_item_letter" diff --git a/app/src/main/res/layout/item_square_my_reply.xml b/app/src/main/res/layout/item_square_my_reply.xml index 7d96bd2f..d24a4e84 100644 --- a/app/src/main/res/layout/item_square_my_reply.xml +++ b/app/src/main/res/layout/item_square_my_reply.xml @@ -18,6 +18,9 @@ android:layout_height="wrap_content" android:text="2025.12.25" android:textColor="@color/neutral_subtle" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> diff --git a/app/src/main/res/layout/item_square_question_reply.xml b/app/src/main/res/layout/item_square_question_reply.xml index 4f9adb7e..26d5e912 100644 --- a/app/src/main/res/layout/item_square_question_reply.xml +++ b/app/src/main/res/layout/item_square_question_reply.xml @@ -31,7 +31,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LV 1234" - android:layout_marginTop="4dp" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@id/civ_item_square_question_reply_user_image" app:layout_constraintStart_toStartOf="@id/civ_item_square_question_reply_user_image" app:layout_constraintEnd_toEndOf="@id/civ_item_square_question_reply_user_image"/> @@ -64,6 +67,11 @@ 친구의 답이 보이는 자리친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리친구의 답이 보이는 자리친구의 답이 보이는 자리친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리 친구의 답이 보이는 자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" android:ellipsize="end" app:layout_constraintTop_toBottomOf="@id/iv_item_square_question_reply_symbol" app:layout_constraintStart_toStartOf="@id/iv_item_square_question_reply_symbol" diff --git a/app/src/main/res/layout/layout_letter_tooltip_popup.xml b/app/src/main/res/layout/layout_letter_tooltip_popup.xml new file mode 100644 index 00000000..b62fd507 --- /dev/null +++ b/app/src/main/res/layout/layout_letter_tooltip_popup.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_popup_friend_list.xml b/app/src/main/res/layout/layout_popup_friend_list.xml new file mode 100644 index 00000000..de809438 --- /dev/null +++ b/app/src/main/res/layout/layout_popup_friend_list.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/bottom_navigation.xml b/app/src/main/res/navigation/bottom_navigation.xml index 4ac63428..e23c2f8a 100644 --- a/app/src/main/res/navigation/bottom_navigation.xml +++ b/app/src/main/res/navigation/bottom_navigation.xml @@ -65,6 +65,15 @@ + + + @@ -130,9 +139,8 @@ android:name="com.egobook.app.ui.counseling.view.EgoRoomWeeklyReportDetailFragment" android:label="CounselingWeeklyReportDetailFragment" tools:layout="@layout/fragment_ego_room_weekly_report_detail"> - + + tools:layout="@layout/fragment_letter_reply"> + + + tools:layout="@layout/fragment_letter_write" /> + android:name="letterId" + app:argType="long" /> #F2F2F2 #D41A1A + #E14444 #FFD9D9D9 #F4F3F3 + #AEAEAE #FF95BB2D #FFE8EED9 @@ -47,9 +49,9 @@ #FCF8E8 #FFC5C5 - #D0DEAD - #BFFBEA - #E4D4FF + #D0DEAD + #BFFBEA + #E4D4FF #DBDBDB diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d8f8988d..1ab46e7a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -192,5 +192,9 @@ 48dp + \ No newline at end of file