From 3030f520fd00956079841e899296334005e2d7be Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 30 Jan 2026 15:20:45 +0900 Subject: [PATCH 01/55] =?UTF-8?q?feat(question):=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=81=B4=EB=A6=AD=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20-=20=EA=B0=81=20=ED=95=AD=EB=AA=A9=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20UI=20=EB=B3=80=EA=B2=BD=20-=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=20=EC=9C=A0=EB=AC=B4=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/square/view/SquareReportDialog.kt | 36 +++++ ...xt.xml => selector_square_button_text.xml} | 0 .../selector_square_report_button_bg.xml | 5 + .../selector_square_report_textview_bg.xml | 5 + .../main/res/drawable/bg_white_and_radius.xml | 5 + .../main/res/layout/dialog_square_report.xml | 149 +++++++++++------- app/src/main/res/layout/fragment_square.xml | 2 +- 7 files changed, 144 insertions(+), 58 deletions(-) rename app/src/main/res/color/{selector_square_today_question_save_button_text.xml => selector_square_button_text.xml} (100%) create mode 100644 app/src/main/res/color/selector_square_report_button_bg.xml create mode 100644 app/src/main/res/color/selector_square_report_textview_bg.xml create mode 100644 app/src/main/res/drawable/bg_white_and_radius.xml 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..72c68316 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,8 +3,11 @@ 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 androidx.core.graphics.drawable.toDrawable +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import com.egobook.app.R import com.egobook.app.databinding.DialogSquareReportBinding @@ -12,6 +15,9 @@ import com.egobook.app.removeScreenBlur class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { private lateinit var binding: DialogSquareReportBinding + private val clickContentList by lazy { + listOf(binding.tvSquareReportAbuse, binding.tvSquareReportSpam, binding.tvSquareReportInappropriate, binding.tvSquareReportEtcNotClicked) + } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return Dialog(requireContext()).apply { @@ -30,6 +36,36 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { removeScreenBlur() dismiss() } + clickContentList.forEach { content -> + content.setOnClickListener { + clickContentList.forEach { it.isSelected = false } + content.isSelected = true + if(tvSquareReportEtcNotClicked.isSelected) { + llSquareReportEtcClicked.isVisible = true + tvSquareReportEtcNotClicked.isVisible = false + } else { + llSquareReportEtcClicked.isVisible = false + tvSquareReportEtcNotClicked.isVisible = true + } + } + } + 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() + } + }) } companion object { 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/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/layout/dialog_square_report.xml b/app/src/main/res/layout/dialog_square_report.xml index 6aec7331..9d85c924 100644 --- a/app/src/main/res/layout/dialog_square_report.xml +++ b/app/src/main/res/layout/dialog_square_report.xml @@ -1,145 +1,180 @@ + 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/fragment_square.xml b/app/src/main/res/layout/fragment_square.xml index e5b2e1a8..acded17c 100644 --- a/app/src/main/res/layout/fragment_square.xml +++ b/app/src/main/res/layout/fragment_square.xml @@ -609,7 +609,7 @@ android:insetTop="0dp" android:insetBottom="0dp" android:text="저장하기" - android:textColor="@color/selector_square_today_question_save_button_text" + android:textColor="@color/selector_square_button_text" app:backgroundTint="@color/selector_square_today_question_save_button_bg" android:enabled="false" app:cornerRadius="4dp" From 78d359a9e5dea1c89ef9261b63a9a6bf6511d69e Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 30 Jan 2026 20:06:48 +0900 Subject: [PATCH 02/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=84=A0=ED=83=9D=20-=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EC=83=89=EC=83=81=20=EC=84=A0=ED=83=9D=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20-=20=EC=83=89=EC=83=81=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=8B=9C=20=ED=8E=B8=EC=A7=80=EC=A7=80=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20=EC=B2=B4=ED=81=AC=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80=20-=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/square/view/LetterFragment.kt | 17 --- .../app/ui/square/view/LetterWriteFragment.kt | 46 ++++++ .../app/ui/square/view/SquareFragment.kt | 3 + .../selector_letter_paper_color_stroke.xml | 5 + app/src/main/res/drawable/ic_check.xml | 17 +++ ...t_letter.xml => fragment_letter_write.xml} | 137 +++++++++++++----- app/src/main/res/layout/fragment_square.xml | 1 + 7 files changed, 169 insertions(+), 57 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/ui/square/view/LetterFragment.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt create mode 100644 app/src/main/res/color/selector_letter_paper_color_stroke.xml create mode 100644 app/src/main/res/drawable/ic_check.xml rename app/src/main/res/layout/{fragment_letter.xml => fragment_letter_write.xml} (56%) 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/LetterWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt new file mode 100644 index 00000000..dea7e320 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt @@ -0,0 +1,46 @@ +package com.egobook.app.ui.square.view + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.egobook.app.R +import com.egobook.app.databinding.FragmentLetterWriteBinding +import com.google.android.material.card.MaterialCardView +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { + private lateinit var binding: FragmentLetterWriteBinding + private val letterColorList by lazy { + listOf(binding.cvLetterColorBeige, binding.cvLetterColorPink, binding.cvLetterColorLeaf, binding.cvLetterColorMint, binding.cvLetterColorLavender) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentLetterWriteBinding.bind(view) + initListeners() + } + + private fun initListeners() = with(binding) { + ivLetterWriteBack.setOnClickListener { findNavController().popBackStack() } + letterColorList.forEach { letterColor -> + letterColor.setOnClickListener { clickedView -> + letterColorList.forEach { card -> + card.isSelected = false + card.getChildAt(0).isVisible = false + } + clickedView.isSelected = true + (clickedView as MaterialCardView).getChildAt(0).isVisible = true + when(clickedView.id) { + R.id.cv_letter_color_beige -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null)} + R.id.cv_letter_color_pink -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null)} + R.id.cv_letter_color_leaf -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null)} + R.id.cv_letter_color_mint -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null)} + R.id.cv_letter_color_lavender -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null)} + } + } + } + } +} \ No newline at end of file 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..1154f84b 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 @@ -120,6 +120,9 @@ class SquareFragment : Fragment(R.layout.fragment_square) { cvSquareTodayQuestionAnswered.isVisible = false cvSquareTodayQuestionWriting.isVisible = true } + cvSquareWriteLetter.setOnClickListener { + findNavController().navigate(R.id.action_menu_square_to_letterWriteFragment) + } } /** 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/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/layout/fragment_letter.xml b/app/src/main/res/layout/fragment_letter_write.xml similarity index 56% rename from app/src/main/res/layout/fragment_letter.xml rename to app/src/main/res/layout/fragment_letter_write.xml index dd778310..8a929704 100644 --- a/app/src/main/res/layout/fragment_letter.xml +++ b/app/src/main/res/layout/fragment_letter_write.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/layer_white" - tools:context=".ui.square.view.LetterFragment"> + tools:context=".ui.square.view.LetterWriteFragment"> - + app:layout_constraintTop_toBottomOf="@id/ll_letter_header"> + + - + app:cardBackgroundColor="@color/letter_bg_pink" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/CircleShape" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_beige" + app:layout_constraintStart_toEndOf="@id/cv_letter_color_beige" + app:layout_constraintTop_toTopOf="@id/cv_letter_color_beige"> + + - + app:cardBackgroundColor="@color/letter_bg_leaf" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/CircleShape" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_pink" + app:layout_constraintStart_toEndOf="@id/cv_letter_color_pink" + app:layout_constraintTop_toTopOf="@id/cv_letter_color_pink"> + + - + app:cardBackgroundColor="@color/letter_bg_mint" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/CircleShape" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_leaf" + app:layout_constraintStart_toEndOf="@id/cv_letter_color_leaf" + app:layout_constraintTop_toTopOf="@id/cv_letter_color_leaf"> + + - + app:cardBackgroundColor="@color/letter_bg_lavender" + app:strokeColor="@color/selector_letter_paper_color_stroke" + app:strokeWidth="2dp" + app:contentPadding="8dp" + app:shapeAppearanceOverlay="@style/CircleShape" + app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_mint" + app:layout_constraintStart_toEndOf="@id/cv_letter_color_mint" + app:layout_constraintTop_toTopOf="@id/cv_letter_color_mint"> + + - + app:layout_constraintTop_toBottomOf="@id/iv_letter_tooltip"/> + app:layout_constraintTop_toBottomOf="@id/cv_letter_content" /> Date: Fri, 30 Jan 2026 20:43:37 +0900 Subject: [PATCH 03/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=93=B0=EA=B8=B0=20=EC=A0=9C=ED=95=9C=20-=20360=EC=9E=90=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EB=B0=8F=20=ED=98=84=EC=9E=AC=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=EC=88=98=20=EA=B0=90=EC=A7=80=20-=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=EC=88=98=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=ED=99=9C=EC=84=B1=ED=99=94/=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/square/view/LetterWriteFragment.kt | 25 +++++++++++++++++++ ...n_bg.xml => selector_square_button_bg.xml} | 0 .../main/res/layout/fragment_letter_write.xml | 19 ++++++++------ app/src/main/res/layout/fragment_square.xml | 2 +- .../main/res/navigation/bottom_navigation.xml | 9 ++++--- app/src/main/res/values/styles.xml | 3 +++ 6 files changed, 47 insertions(+), 11 deletions(-) rename app/src/main/res/color/{selector_square_today_question_save_button_bg.xml => selector_square_button_bg.xml} (100%) 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 index dea7e320..4ae6e968 100644 --- 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 @@ -1,6 +1,8 @@ package com.egobook.app.ui.square.view import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.View import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -42,5 +44,28 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { } } } + 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" + } + }) + } + + companion object { + private const val MAX_LENGTH = 360 } } \ 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/layout/fragment_letter_write.xml b/app/src/main/res/layout/fragment_letter_write.xml index 8a929704..24810d8b 100644 --- a/app/src/main/res/layout/fragment_letter_write.xml +++ b/app/src/main/res/layout/fragment_letter_write.xml @@ -173,25 +173,26 @@ app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/et_letter_write_content" /> + app:layout_constraintTop_toBottomOf="@id/tv_letter_write_content_length" /> @@ -210,11 +211,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="28dp" - android:backgroundTint="@color/neutral" android:text="친구에게 보내기" android:layout_marginStart="16dp" android:insetTop="0dp" android:insetBottom="0dp" + android:textColor="@color/selector_square_button_text" + android:backgroundTint="@color/selector_square_button_bg" + android:enabled="false" app:cornerRadius="4dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cv_letter_container" @@ -224,12 +227,14 @@ android:id="@+id/btn_letter_send_anonymous" android:layout_width="0dp" android:layout_height="wrap_content" - android:backgroundTint="@color/neutral" android:text="누군가에게 보내기" android:layout_marginEnd="16dp" android:layout_marginStart="12dp" android:insetTop="0dp" android:insetBottom="0dp" + android:textColor="@color/selector_square_button_text" + android:backgroundTint="@color/selector_square_button_bg" + android:enabled="false" app:cornerRadius="4dp" app:layout_constraintStart_toEndOf="@id/btn_letter_send_friend" app:layout_constraintTop_toTopOf="@id/btn_letter_send_friend" diff --git a/app/src/main/res/layout/fragment_square.xml b/app/src/main/res/layout/fragment_square.xml index 7f5b4166..9ef7c8ee 100644 --- a/app/src/main/res/layout/fragment_square.xml +++ b/app/src/main/res/layout/fragment_square.xml @@ -611,7 +611,7 @@ android:insetBottom="0dp" android:text="저장하기" android:textColor="@color/selector_square_button_text" - app:backgroundTint="@color/selector_square_today_question_save_button_bg" + app:backgroundTint="@color/selector_square_button_bg" android:enabled="false" app:cornerRadius="4dp" app:layout_constraintBottom_toBottomOf="@id/ll_square_today_question_visibility_type" diff --git a/app/src/main/res/navigation/bottom_navigation.xml b/app/src/main/res/navigation/bottom_navigation.xml index a7ab1f81..b793ce8c 100644 --- a/app/src/main/res/navigation/bottom_navigation.xml +++ b/app/src/main/res/navigation/bottom_navigation.xml @@ -65,6 +65,9 @@ + @@ -163,10 +166,10 @@ android:label="fragment_letter_reply" tools:layout="@layout/fragment_letter_reply" /> + tools:layout="@layout/fragment_letter_write" /> @color/cos_black + \ No newline at end of file From c30f44c60e4435fbd7df496c593909d4496fb848 Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 30 Jan 2026 21:13:17 +0900 Subject: [PATCH 04/55] =?UTF-8?q?design(letter):=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=97=B4=EA=B8=B0=20=ED=8C=9D?= =?UTF-8?q?=EC=97=85=20UI=20-=20recyclerview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/item_friend_list.xml | 30 +++++++++++++++++++ .../res/layout/layout_popup_friend_list.xml | 16 ++++++++++ 2 files changed, 46 insertions(+) create mode 100644 app/src/main/res/layout/item_friend_list.xml create mode 100644 app/src/main/res/layout/layout_popup_friend_list.xml diff --git a/app/src/main/res/layout/item_friend_list.xml b/app/src/main/res/layout/item_friend_list.xml new file mode 100644 index 00000000..29f02c83 --- /dev/null +++ b/app/src/main/res/layout/item_friend_list.xml @@ -0,0 +1,30 @@ + + + + + + + + \ 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..009770bd --- /dev/null +++ b/app/src/main/res/layout/layout_popup_friend_list.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file From 05a38e273bf0e409e144cb033cf57381815ea759 Mon Sep 17 00:00:00 2001 From: se05503 Date: Sat, 31 Jan 2026 17:45:44 +0900 Subject: [PATCH 05/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=93=B0=EA=B8=B0=20=EC=B9=9C=EA=B5=AC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?-=20recyclerview=20=EA=B5=AC=EB=B6=84=EC=84=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EB=A7=A8=20=ED=95=98=EB=8B=A8=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=20=EC=95=84=EB=9E=98=20x)=20-=20=EC=B9=9C?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=8F=99?= =?UTF-8?q?=EC=A0=81=20UI=20=EA=B5=AC=EB=B6=84=20-=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20mock=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/FriendsRepositoryImpl.kt | 13 +- .../square/adapter/FriendPopupListAdapter.kt | 55 ++++++++ .../ui/square/model/letter/LetterSendType.kt | 6 + .../app/ui/square/view/LetterSendDialog.kt | 58 ++++++++ .../app/ui/square/view/LetterWriteFragment.kt | 125 ++++++++++++++++++ .../ui/square/viewmodel/LetterViewModel.kt | 33 +++++ .../main/res/layout/dialog_letter_send.xml | 62 +++++++++ .../main/res/layout/fragment_letter_write.xml | 2 + ...nd_list.xml => item_friend_popup_list.xml} | 11 +- .../res/layout/layout_popup_friend_list.xml | 3 +- 10 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/ui/square/adapter/FriendPopupListAdapter.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt create mode 100644 app/src/main/res/layout/dialog_letter_send.xml rename app/src/main/res/layout/{item_friend_list.xml => item_friend_popup_list.xml} (68%) 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/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/model/letter/LetterSendType.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt new file mode 100644 index 00000000..9733d625 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt @@ -0,0 +1,6 @@ +package com.egobook.app.ui.square.model.letter + +enum class LetterSendType(val value: String) { + RANDOM("RANDOM"), + FRIEND("FRIEND") +} \ 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..cada0974 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt @@ -0,0 +1,58 @@ +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.DialogLetterSendBinding +import com.egobook.app.removeScreenBlur +import com.egobook.app.ui.square.model.letter.LetterSendType + +class LetterSendDialog(private val type: LetterSendType): DialogFragment(R.layout.dialog_letter_send) { + + private lateinit var binding: DialogLetterSendBinding + + 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() + } + + private fun initViews() = with(binding) { + when(type) { + LetterSendType.FRIEND -> { + tvLetterSendTitle.text = "친구이름에게\n편지를 보낼까요?" + tvLetterSendDescription.text = "상대에게 내 이름이 보여요" + } + LetterSendType.RANDOM -> { + tvLetterSendTitle.text = "누군가에게\n편지를 보낼까요?" + tvLetterSendDescription.text = "익명으로 전달돼요" + } + } + } + + private fun initListeners() = with(binding) { + btnLetterSendBack.setOnClickListener { + removeScreenBlur() + dismiss() + } + btnLetterSendApply.setOnClickListener { + removeScreenBlur() + dismiss() + } + } + + 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 index 4ae6e968..74a64f59 100644 --- 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 @@ -1,16 +1,34 @@ 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.ui.square.adapter.FriendPopupListAdapter +import com.egobook.app.ui.square.model.friend.FriendModel +import com.egobook.app.ui.square.model.letter.LetterSendType +import com.egobook.app.ui.square.viewmodel.LetterViewModel +import com.egobook.app.util.UiState import com.google.android.material.card.MaterialCardView import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { @@ -19,10 +37,26 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { listOf(binding.cvLetterColorBeige, binding.cvLetterColorPink, binding.cvLetterColorLeaf, binding.cvLetterColorMint, binding.cvLetterColorLavender) } + private val viewModel: LetterViewModel by activityViewModels() + + private lateinit var friendList: List + 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) { @@ -63,6 +97,97 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { tvLetterWriteContentLength.text = "${text?.length}/$MAX_LENGTH" } }) + btnLetterSendFriend.setOnClickListener { + showFriendListPopup(anchorView = btnLetterSendFriend) + } + btnLetterSendAnonymous.setOnClickListener { + applyScreenBlur(BlurLevel.BASE) + val dialog = LetterSendDialog(LetterSendType.RANDOM).apply { + isCancelable = false + } + dialog.show(childFragmentManager, LetterSendDialog.TAG) + } + } + + /** + * 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 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 { 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..9463fe76 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt @@ -0,0 +1,33 @@ +package com.egobook.app.ui.square.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.egobook.app.domain.usecase.GetFriendListUseCase +import com.egobook.app.ui.square.model.friend.FriendModel +import com.egobook.app.ui.square.model.friend.toPresentation +import com.egobook.app.util.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LetterViewModel @Inject constructor( + private val getFriendListUseCase: GetFriendListUseCase +): 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) + } + } + } +} \ 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..8af9f9c5 --- /dev/null +++ b/app/src/main/res/layout/dialog_letter_send.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_letter_write.xml b/app/src/main/res/layout/fragment_letter_write.xml index 24810d8b..8543ea95 100644 --- a/app/src/main/res/layout/fragment_letter_write.xml +++ b/app/src/main/res/layout/fragment_letter_write.xml @@ -156,6 +156,7 @@ android:padding="16dp"> - - + app:layout_constraintBottom_toBottomOf="parent"/> \ 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 index 009770bd..de809438 100644 --- a/app/src/main/res/layout/layout_popup_friend_list.xml +++ b/app/src/main/res/layout/layout_popup_friend_list.xml @@ -12,5 +12,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="16dp" - tools:listitem="@layout/item_friend_list"/> + tools:listitem="@layout/item_friend_popup_list" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> \ No newline at end of file From cc0c0fc79053da4cd5c4776d9bffe29640a15c5a Mon Sep 17 00:00:00 2001 From: se05503 Date: Sat, 31 Jan 2026 18:48:44 +0900 Subject: [PATCH 06/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20API=20=EC=84=B8=ED=8C=85=20-=20=EB=B7=B0?= =?UTF-8?q?=20=EC=A0=9C=EC=99=B8=ED=95=98=EA=B3=A0=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20enum=20class=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 12 ++++++++++ .../model/square/letter/SendLetterRequest.kt | 24 +++++++++++++++++++ .../model/square/letter/SendLetterResponse.kt | 18 ++++++++++++++ .../data/repository/LetterRepositoryImpl.kt | 20 ++++++++++++++++ .../com/egobook/app/di/RepositoryModule.kt | 8 ++++++- .../java/com/egobook/app/di/ServiceModule.kt | 7 ++++++ .../domain/model/square/letter/LetterMode.kt | 6 +++++ .../model/square/letter/LetterStatus.kt | 10 ++++++++ .../domain/model/square/letter/SendLetter.kt | 10 ++++++++ .../app/domain/repository/LetterRepository.kt | 7 ++++++ .../usecase/letter/SendLetterUseCase.kt | 9 +++++++ .../model/letter/LetterBackgroundColor.kt | 9 +++++++ .../ui/square/model/letter/LetterSendType.kt | 6 ----- .../ui/square/model/letter/SendLetterModel.kt | 18 ++++++++++++++ .../app/ui/square/view/LetterSendDialog.kt | 8 +++---- .../app/ui/square/view/LetterWriteFragment.kt | 4 ++-- .../ui/square/viewmodel/LetterViewModel.kt | 22 ++++++++++++++++- 17 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/api/LetterApiService.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterRequest.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/SendLetterResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/LetterMode.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/LetterStatus.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/SendLetterUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt delete mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/SendLetterModel.kt 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..9f2ff811 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -0,0 +1,12 @@ +package com.egobook.app.data.api + +import com.egobook.app.data.model.ApiResponse +import com.egobook.app.data.model.square.letter.SendLetterRequest +import com.egobook.app.data.model.square.letter.SendLetterResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface LetterApiService { + @POST("/plaza/letters") + suspend fun sendLetter(@Body request: SendLetterRequest): ApiResponse +} \ 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..ef1ae0bd --- /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.ui.square.model.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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt new file mode 100644 index 00000000..d7384f48 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.egobook.app.data.repository + +import com.egobook.app.data.api.LetterApiService +import com.egobook.app.data.model.square.letter.toData +import com.egobook.app.domain.model.square.letter.SendLetter +import com.egobook.app.domain.repository.LetterRepository +import javax.inject.Inject + +class LetterRepositoryImpl @Inject constructor(private val apiService: LetterApiService): LetterRepository { + override suspend fun sendLetter(letter: SendLetter): Result = try { + val response = apiService.sendLetter(request = letter.toData()) + 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/di/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt index 50a71ab3..fa2e7995 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/RepositoryModule.kt @@ -3,11 +3,13 @@ package com.egobook.app.di import com.egobook.app.data.repository.CounselingRepositoryImpl import com.egobook.app.data.repository.DiaryRepositoryImpl import com.egobook.app.data.repository.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.QuestionRepositoryImpl import com.egobook.app.domain.repository.DiaryRepository import com.egobook.app.domain.repository.FriendsRepository +import com.egobook.app.domain.repository.LetterRepository import com.egobook.app.domain.repository.NotificationRepository import dagger.Binds import com.egobook.app.domain.repository.QuestionRepository @@ -38,4 +40,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindDiaryRepository(impl: DiaryRepositoryImpl): DiaryRepository -} \ No newline at end of file + + @Binds + @Singleton + abstract fun bindLetterRepository(impl: LetterRepositoryImpl): LetterRepository +} diff --git a/app/src/main/java/com/egobook/app/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/ServiceModule.kt index 5b2207fe..6f098257 100644 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/ServiceModule.kt @@ -2,6 +2,7 @@ package com.egobook.app.di import com.egobook.app.data.api.CounselingApiService 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 dagger.Module @@ -37,4 +38,10 @@ object ServiceModule { fun provideQuestionService(retrofit: Retrofit): QuestionApiService { return retrofit.create(QuestionApiService::class.java) } + + @Provides + @Singleton + fun provideLetterService(retrofit: Retrofit): LetterApiService { + return retrofit.create(LetterApiService::class.java) + } } \ 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..fc139b9f --- /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"), + DEFERRED("DEFERRED"), + REPLIED("REPLIED"), + GAVE_UP("GAVE_UP"), + AI_REPLIED("AI_REPLIED") +} 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..1b2722ac --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt @@ -0,0 +1,10 @@ +package com.egobook.app.domain.model.square.letter + +import com.egobook.app.ui.square.model.letter.LetterBackgroundColor + +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/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt new file mode 100644 index 00000000..48804334 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -0,0 +1,7 @@ +package com.egobook.app.domain.repository + +import com.egobook.app.domain.model.square.letter.SendLetter + +interface LetterRepository { + suspend fun sendLetter(letter: SendLetter): Result +} \ No newline at end of file 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/square/model/letter/LetterBackgroundColor.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt new file mode 100644 index 00000000..76e843c0 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt @@ -0,0 +1,9 @@ +package com.egobook.app.ui.square.model.letter + +enum class LetterBackgroundColor(val text: String) { + BEIGE("BEIGE"), + PINK("PINK"), + LEAF("LEAF"), + MINT("MINT"), + LAVENDER("LAVENDER") +} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt deleted file mode 100644 index 9733d625..00000000 --- a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterSendType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.egobook.app.ui.square.model.letter - -enum class LetterSendType(val value: String) { - RANDOM("RANDOM"), - FRIEND("FRIEND") -} \ No newline at end of file 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..910f831f --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/SendLetterModel.kt @@ -0,0 +1,18 @@ +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.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/view/LetterSendDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt index cada0974..3c953aad 100644 --- 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 @@ -9,9 +9,9 @@ import androidx.fragment.app.DialogFragment import com.egobook.app.R import com.egobook.app.databinding.DialogLetterSendBinding import com.egobook.app.removeScreenBlur -import com.egobook.app.ui.square.model.letter.LetterSendType +import com.egobook.app.domain.model.square.letter.LetterMode -class LetterSendDialog(private val type: LetterSendType): DialogFragment(R.layout.dialog_letter_send) { +class LetterSendDialog(private val type: LetterMode): DialogFragment(R.layout.dialog_letter_send) { private lateinit var binding: DialogLetterSendBinding @@ -30,11 +30,11 @@ class LetterSendDialog(private val type: LetterSendType): DialogFragment(R.layou private fun initViews() = with(binding) { when(type) { - LetterSendType.FRIEND -> { + LetterMode.FRIEND -> { tvLetterSendTitle.text = "친구이름에게\n편지를 보낼까요?" tvLetterSendDescription.text = "상대에게 내 이름이 보여요" } - LetterSendType.RANDOM -> { + LetterMode.RANDOM -> { tvLetterSendTitle.text = "누군가에게\n편지를 보낼까요?" tvLetterSendDescription.text = "익명으로 전달돼요" } 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 index 74a64f59..94005272 100644 --- 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 @@ -23,7 +23,7 @@ import com.egobook.app.databinding.FragmentLetterWriteBinding import com.egobook.app.databinding.LayoutPopupFriendListBinding import com.egobook.app.ui.square.adapter.FriendPopupListAdapter import com.egobook.app.ui.square.model.friend.FriendModel -import com.egobook.app.ui.square.model.letter.LetterSendType +import com.egobook.app.domain.model.square.letter.LetterMode import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.util.UiState import com.google.android.material.card.MaterialCardView @@ -102,7 +102,7 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { } btnLetterSendAnonymous.setOnClickListener { applyScreenBlur(BlurLevel.BASE) - val dialog = LetterSendDialog(LetterSendType.RANDOM).apply { + val dialog = LetterSendDialog(LetterMode.RANDOM).apply { isCancelable = false } dialog.show(childFragmentManager, LetterSendDialog.TAG) 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 index 9463fe76..7d1578c4 100644 --- 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 @@ -3,18 +3,24 @@ package com.egobook.app.ui.square.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.GetFriendListUseCase +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.SendLetterModel +import com.egobook.app.ui.square.model.letter.toDomain 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.launch import javax.inject.Inject @HiltViewModel class LetterViewModel @Inject constructor( - private val getFriendListUseCase: GetFriendListUseCase + private val getFriendListUseCase: GetFriendListUseCase, + private val sendLetterUseCase: SendLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -30,4 +36,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ No newline at end of file From 5d6882f0395f4597d2d01a01dd9845d0ba871721 Mon Sep 17 00:00:00 2001 From: se05503 Date: Sun, 1 Feb 2026 22:10:39 +0900 Subject: [PATCH 07/55] =?UTF-8?q?chore:=20=EB=B9=8C=EB=93=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 9 +++++---- build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b9b501d..05cd5644 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,16 +74,17 @@ android { } dependencies { + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.1.0")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6") implementation("androidx.activity:activity-ktx:1.9.3") implementation("androidx.fragment:fragment-ktx:1.8.5") - implementation("com.google.dagger:hilt-android:2.51.1") + implementation("com.google.dagger:hilt-android:2.54") implementation("androidx.hilt:hilt-navigation-fragment:1.2.0") implementation(libs.androidx.annotation) implementation(libs.androidx.lifecycle.livedata.ktx) - ksp("com.google.dagger:hilt-compiler:2.51.1") + ksp("com.google.dagger:hilt-compiler:2.54") implementation("androidx.hilt:hilt-navigation-compose:1.2.0") implementation("androidx.navigation:navigation-fragment-ktx:2.8.5") implementation("androidx.navigation:navigation-ui-ktx:2.8.5") @@ -119,8 +120,8 @@ dependencies { implementation("de.hdodenhof:circleimageview:3.1.0") // Hilt 테스트를 위한 의존성 추가 - androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1") - kspAndroidTest("com.google.dagger:hilt-android-compiler:2.51.1") + androidTestImplementation("com.google.dagger:hilt-android-testing:2.54") + kspAndroidTest("com.google.dagger:hilt-android-compiler:2.54") implementation("me.relex:circleindicator:2.1.6") implementation("com.github.Dimezis:BlurView:version-3.2.0") diff --git a/build.gradle.kts b/build.gradle.kts index ca3f5603..bf121f9a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,9 +2,9 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false - id("com.google.dagger.hilt.android") version "2.51.1" apply false + id("com.google.dagger.hilt.android") version "2.54" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0" apply false id("androidx.navigation.safeargs.kotlin") version "2.8.5" apply false id("org.jlleitschuh.gradle.ktlint") version "14.0.1" apply false - id("com.google.devtools.ksp") version "2.0.21-1.0.26" apply false + id("com.google.devtools.ksp") version "2.1.0-1.0.29" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3ddfa0e..f03cb41e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] agp = "8.13.2" -kotlin = "2.0.21" +kotlin = "2.1.0" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" From 87b8111c1746d8cb2a632eef7d4cc2fe9051d538 Mon Sep 17 00:00:00 2001 From: se05503 Date: Sun, 1 Feb 2026 22:53:33 +0900 Subject: [PATCH 08/55] =?UTF-8?q?feat(letter):=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=97=B4=EA=B8=B0=20-=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=20=EC=9D=B4=EB=A6=84=20UI=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egobook/app/ui/square/view/LetterSendDialog.kt | 5 +++-- .../com/egobook/app/ui/square/view/LetterWriteFragment.kt | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) 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 index 3c953aad..2792a519 100644 --- 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 @@ -10,8 +10,9 @@ import com.egobook.app.R 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 -class LetterSendDialog(private val type: LetterMode): DialogFragment(R.layout.dialog_letter_send) { +class LetterSendDialog(private val type: LetterMode, private val friendInfo: FriendModel? = null): DialogFragment(R.layout.dialog_letter_send) { private lateinit var binding: DialogLetterSendBinding @@ -31,7 +32,7 @@ class LetterSendDialog(private val type: LetterMode): DialogFragment(R.layout.di private fun initViews() = with(binding) { when(type) { LetterMode.FRIEND -> { - tvLetterSendTitle.text = "친구이름에게\n편지를 보낼까요?" + tvLetterSendTitle.text = "${friendInfo?.name}에게\n편지를 보낼까요?" tvLetterSendDescription.text = "상대에게 내 이름이 보여요" } LetterMode.RANDOM -> { 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 index 94005272..2fed824e 100644 --- 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 @@ -102,7 +102,7 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { } btnLetterSendAnonymous.setOnClickListener { applyScreenBlur(BlurLevel.BASE) - val dialog = LetterSendDialog(LetterMode.RANDOM).apply { + val dialog = LetterSendDialog(LetterMode.RANDOM, friendInfo = null).apply { isCancelable = false } dialog.show(childFragmentManager, LetterSendDialog.TAG) @@ -131,6 +131,9 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { tvLetterWriteReceiver.text = "To ${friendInfo.name}" tvLetterWriteSender.text = "From 로그인한 유저" // TODO: 나중에 유저 정보 받아오기 popupWindow.dismiss() + val dialog = LetterSendDialog(type = LetterMode.FRIEND, friendInfo = friendInfo).apply { isCancelable = false } + dialog.show(childFragmentManager, LetterSendDialog.TAG) + applyScreenBlur(BlurLevel.BASE) } val dividerDrawable = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE From 8d22bbc54362fb39aa07187f08ce2c0d7b6cbe97 Mon Sep 17 00:00:00 2001 From: se05503 Date: Sun, 1 Feb 2026 23:43:08 +0900 Subject: [PATCH 09/55] =?UTF-8?q?refactor(di):=20Retrofit,=20OkHttpClient?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EB=B0=94=EC=9D=B8=EB=94=A9=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9=20-=20=EA=B8=B0=EC=A1=B4=20=EB=B0=B1=EC=97=94?= =?UTF-8?q?=EB=93=9C=20=EC=84=9C=EB=B2=84=20=EC=99=B8=EC=97=90=20AI=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=97=B0=EB=8F=99=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=E2=86=92=20=EC=83=88=EB=A1=9C=EC=9A=B4=20Retrofit=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4,=20OkHttpClient=20=EA=B0=9D=EC=B2=B4=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=20-=20=EA=B0=99=EC=9D=80=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9D=84=20=ED=9E=90=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EC=A4=91=EB=B3=B5=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9=20=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=EC=84=9C?= =?UTF-8?q?=EB=8A=94=20@Qualifier,=20@Named=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=B4?= =?UTF-8?q?=EC=95=BC=20=ED=95=A8=20-=20@Named=EB=B3=B4=EB=8B=A4=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EB=B0=8F=20=EC=95=88=EC=A0=95?= =?UTF-8?q?=EC=84=B1=EC=9D=B4=20=EC=A2=8B=EC=9D=80=20@Qualifier=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 ++-- .../com/egobook/app/data/api/AIApiService.kt | 11 ++++++ .../letter/DetectAbusiveContentRequest.kt | 8 +++++ .../letter/DetectAbusiveContentResponse.kt | 16 +++++++++ .../java/com/egobook/app/di/NetworkModule.kt | 36 +++++++++++++++++-- .../main/java/com/egobook/app/di/Qualifier.kt | 9 +++++ .../java/com/egobook/app/di/ServiceModule.kt | 18 +++++++--- 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/api/AIApiService.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentRequest.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentResponse.kt create mode 100644 app/src/main/java/com/egobook/app/di/Qualifier.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 05cd5644..4116dec1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,8 +36,10 @@ 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 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..a4805675 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/api/AIApiService.kt @@ -0,0 +1,11 @@ +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.http.Body +import retrofit2.http.POST + +interface AIApiService { + @POST("/detect") + suspend fun detectAbusiveContent(@Body request: DetectAbusiveContentRequest): DetectAbusiveContentResponse +} \ No newline at end of file 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..1b5538aa --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/DetectAbusiveContentResponse.kt @@ -0,0 +1,16 @@ +package com.egobook.app.data.model.square.letter + +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 +) diff --git a/app/src/main/java/com/egobook/app/di/NetworkModule.kt b/app/src/main/java/com/egobook/app/di/NetworkModule.kt index 75a70298..9c74d995 100644 --- a/app/src/main/java/com/egobook/app/di/NetworkModule.kt +++ b/app/src/main/java/com/egobook/app/di/NetworkModule.kt @@ -18,8 +18,9 @@ import javax.inject.Singleton object NetworkModule { @Provides @Singleton - fun provideRetrofit( - client: OkHttpClient, + @BackendApi + fun provideBackendRetrofit( + @BackendApi client: OkHttpClient, gsonConverterFactory: GsonConverterFactory ): Retrofit { return Retrofit.Builder() @@ -29,6 +30,20 @@ object NetworkModule { .build() } + @Provides + @Singleton + @AIApi + fun provideAIRetrofit( + @AIApi client: OkHttpClient, + gsonConverterFactory: GsonConverterFactory + ): Retrofit { + return Retrofit.Builder() + .baseUrl(BuildConfig.AI_BASE_URL) + .addConverterFactory(gsonConverterFactory) + .client(client) + .build() + } + @Provides @Singleton fun provideGsonConverterFactory(): GsonConverterFactory { @@ -39,7 +54,8 @@ object NetworkModule { @Provides @Singleton - fun provideOkHttpClient( + @BackendApi + fun provideBackendOkHttpClient( authInterceptor: AuthInterceptor ): OkHttpClient { return OkHttpClient.Builder().apply { @@ -49,4 +65,18 @@ object NetworkModule { addInterceptor(authInterceptor) }.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() + } } diff --git a/app/src/main/java/com/egobook/app/di/Qualifier.kt b/app/src/main/java/com/egobook/app/di/Qualifier.kt new file mode 100644 index 00000000..a8350522 --- /dev/null +++ b/app/src/main/java/com/egobook/app/di/Qualifier.kt @@ -0,0 +1,9 @@ +package com.egobook.app.di + +import jakarta.inject.Qualifier + +@Qualifier +annotation class BackendApi + +@Qualifier +annotation class AIApi diff --git a/app/src/main/java/com/egobook/app/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/ServiceModule.kt index 6f098257..4d0372da 100644 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/ServiceModule.kt @@ -1,5 +1,6 @@ package com.egobook.app.di +import com.egobook.app.data.api.AIApiService import com.egobook.app.data.api.CounselingApiService import com.egobook.app.data.api.FriendsApiService import com.egobook.app.data.api.LetterApiService @@ -17,31 +18,38 @@ 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 provideLetterService(retrofit: Retrofit): LetterApiService { + 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) + } + } \ No newline at end of file From e030f1504948130a3469120862de88295577056d Mon Sep 17 00:00:00 2001 From: se05503 Date: Sun, 1 Feb 2026 23:50:04 +0900 Subject: [PATCH 10/55] =?UTF-8?q?rename:=20di=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=B6=84=EB=A6=AC=20-=20module,=20qualifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egobook/app/di/{ => module}/NetworkModule.kt | 4 +++- .../java/com/egobook/app/di/{ => module}/RepositoryModule.kt | 2 +- .../java/com/egobook/app/di/{ => module}/ServiceModule.kt | 4 +++- .../java/com/egobook/app/di/{ => module}/UseCaseModule.kt | 2 +- .../main/java/com/egobook/app/di/{ => qualifier}/Qualifier.kt | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) rename app/src/main/java/com/egobook/app/di/{ => module}/NetworkModule.kt (94%) rename app/src/main/java/com/egobook/app/di/{ => module}/RepositoryModule.kt (98%) rename app/src/main/java/com/egobook/app/di/{ => module}/ServiceModule.kt (93%) rename app/src/main/java/com/egobook/app/di/{ => module}/UseCaseModule.kt (96%) rename app/src/main/java/com/egobook/app/di/{ => qualifier}/Qualifier.kt (74%) 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 94% 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 9c74d995..192e12c9 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,7 +1,9 @@ -package com.egobook.app.di +package com.egobook.app.di.module import com.egobook.app.BuildConfig import com.egobook.app.data.interceptor.AuthInterceptor +import com.egobook.app.di.qualifier.AIApi +import com.egobook.app.di.qualifier.BackendApi import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides 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 98% 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 fa2e7995..2bbfcc9b 100644 --- a/app/src/main/java/com/egobook/app/di/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt @@ -1,4 +1,4 @@ -package com.egobook.app.di +package com.egobook.app.di.module import com.egobook.app.data.repository.CounselingRepositoryImpl import com.egobook.app.data.repository.DiaryRepositoryImpl 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 93% 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 4d0372da..d55cdf8b 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,4 +1,4 @@ -package com.egobook.app.di +package com.egobook.app.di.module import com.egobook.app.data.api.AIApiService import com.egobook.app.data.api.CounselingApiService @@ -6,6 +6,8 @@ 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.BackendApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn 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 96% 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 1321a939..e0bf2789 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.DiaryRepository import com.egobook.app.domain.usecase.diaryusecase.AddDiary diff --git a/app/src/main/java/com/egobook/app/di/Qualifier.kt b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt similarity index 74% rename from app/src/main/java/com/egobook/app/di/Qualifier.kt rename to app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt index a8350522..2f8d48de 100644 --- a/app/src/main/java/com/egobook/app/di/Qualifier.kt +++ b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt @@ -1,4 +1,4 @@ -package com.egobook.app.di +package com.egobook.app.di.qualifier import jakarta.inject.Qualifier From f3f6f6fc4c1feb1ea7bf6a12d290b8c989b633db Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 2 Feb 2026 00:15:57 +0900 Subject: [PATCH 11/55] =?UTF-8?q?feat(letter):=20=EB=82=98=EC=81=9C?= =?UTF-8?q?=EB=A7=90=20=EA=B0=90=EC=A7=80(AI)=20API=20=EC=84=B8=ED=8C=85?= =?UTF-8?q?=20-=20LetterRepositoryImpl=20=EC=97=90=EC=84=9C=20=EB=91=90=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=98=20ApiService=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=20-=20AI=20API=EA=B0=80=20letter=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=97=90=20=EC=A2=85=EC=86=8D?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?repository=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/data/api/AIApiService.kt | 3 ++- .../letter/DetectAbusiveContentResponse.kt | 9 ++++++++ .../data/repository/LetterRepositoryImpl.kt | 22 +++++++++++++++++-- .../square/letter/AbusiveContentAnalysis.kt | 9 ++++++++ .../app/domain/repository/LetterRepository.kt | 2 ++ .../letter/DetectAbusiveContentUseCase.kt | 8 +++++++ .../model/letter/AbusiveContentModel.kt | 19 ++++++++++++++++ .../ui/square/viewmodel/LetterViewModel.kt | 20 ++++++++++++++++- 8 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/AbusiveContentAnalysis.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/DetectAbusiveContentUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/AbusiveContentModel.kt 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 index a4805675..cf91eb59 100644 --- a/app/src/main/java/com/egobook/app/data/api/AIApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/AIApiService.kt @@ -2,10 +2,11 @@ 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): DetectAbusiveContentResponse + suspend fun detectAbusiveContent(@Body request: DetectAbusiveContentRequest): Response } \ No newline at end of file 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 index 1b5538aa..46d0ed28 100644 --- 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 @@ -1,5 +1,6 @@ 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( @@ -14,3 +15,11 @@ data class DetectAbusiveContentResponse( @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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index d7384f48..79a854bc 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -1,14 +1,21 @@ package com.egobook.app.data.repository +import com.egobook.app.data.api.AIApiService import com.egobook.app.data.api.LetterApiService +import com.egobook.app.data.model.square.letter.DetectAbusiveContentRequest import com.egobook.app.data.model.square.letter.toData +import com.egobook.app.data.model.square.letter.toDomain +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis import com.egobook.app.domain.model.square.letter.SendLetter import com.egobook.app.domain.repository.LetterRepository import javax.inject.Inject -class LetterRepositoryImpl @Inject constructor(private val apiService: LetterApiService): LetterRepository { +class LetterRepositoryImpl @Inject constructor( + private val letterApiService: LetterApiService, + private val aiApiService: AIApiService +): LetterRepository { override suspend fun sendLetter(letter: SendLetter): Result = try { - val response = apiService.sendLetter(request = letter.toData()) + val response = letterApiService.sendLetter(request = letter.toData()) if(response.status == 200) { Result.success(Unit) // 나중에 구현하면서 응답 필요할 때 변경 } else { @@ -17,4 +24,15 @@ class LetterRepositoryImpl @Inject constructor(private val apiService: LetterApi } 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()}")) + } + } catch (e: Exception) { + Result.failure(e) + } } \ 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/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt index 48804334..2070ccf9 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -1,7 +1,9 @@ package com.egobook.app.domain.repository +import com.egobook.app.domain.model.square.letter.AbusiveContentAnalysis import com.egobook.app.domain.model.square.letter.SendLetter interface LetterRepository { suspend fun sendLetter(letter: SendLetter): Result + suspend fun detectAbusiveContent(text: String): Result } \ 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/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/viewmodel/LetterViewModel.kt b/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt index 7d1578c4..e58b3baa 100644 --- 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 @@ -3,11 +3,14 @@ package com.egobook.app.ui.square.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.GetFriendListUseCase +import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase 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.SendLetterModel 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 @@ -20,7 +23,8 @@ import javax.inject.Inject @HiltViewModel class LetterViewModel @Inject constructor( private val getFriendListUseCase: GetFriendListUseCase, - private val sendLetterUseCase: SendLetterUseCase + private val sendLetterUseCase: SendLetterUseCase, + private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -50,4 +54,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ No newline at end of file From c8b5b35bc33433db6a3b511076e120b022797818 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 2 Feb 2026 11:36:30 +0900 Subject: [PATCH 12/55] =?UTF-8?q?design(letter):=20=EB=82=98=EC=81=9C?= =?UTF-8?q?=EB=A7=90=20=ED=86=B5=EA=B3=BC=20=EC=97=AC=EB=B6=80=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20UI=20=EC=A0=9C=EC=9E=91?= =?UTF-8?q?=20-=20=ED=95=84=EC=9A=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EB=82=98=EC=81=9C=EB=A7=90=20=EA=B2=80=EC=82=AC=EC=A4=91,=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=ED=86=B5=EA=B3=BC,=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=20=EB=AF=B8=ED=86=B5=EA=B3=BC=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20UI=20=EC=A0=9C=EC=9E=91=20-=20color=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=EA=B3=B5=ED=86=B5=20drawable=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...alog.xml => bg_round_and_white_dialog.xml} | 0 .../res/drawable/img_detect_words_failure.png | Bin 0 -> 50822 bytes .../res/drawable/img_detect_words_loading.png | Bin 0 -> 49824 bytes app/src/main/res/drawable/img_send.png | Bin 0 -> 18720 bytes .../dialog_detect_abusive_content_failure.xml | 75 ++++++++++++++++++ .../dialog_detect_abusive_content_loading.xml | 47 +++++++++++ .../dialog_detect_abusive_content_success.xml | 56 +++++++++++++ app/src/main/res/values/colors.xml | 1 + 8 files changed, 179 insertions(+) rename app/src/main/res/drawable/{bg_round_dialog.xml => bg_round_and_white_dialog.xml} (100%) create mode 100644 app/src/main/res/drawable/img_detect_words_failure.png create mode 100644 app/src/main/res/drawable/img_detect_words_loading.png create mode 100644 app/src/main/res/drawable/img_send.png create mode 100644 app/src/main/res/layout/dialog_detect_abusive_content_failure.xml create mode 100644 app/src/main/res/layout/dialog_detect_abusive_content_loading.xml create mode 100644 app/src/main/res/layout/dialog_detect_abusive_content_success.xml 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/img_detect_words_failure.png b/app/src/main/res/drawable/img_detect_words_failure.png new file mode 100644 index 0000000000000000000000000000000000000000..6785c11c713d1b42b564fa95d02e64c7ab7e2a63 GIT binary patch literal 50822 zcmcG#1yoz#w=Npop+ISiL!i*&gd{jcf?M(879_a4)8Z5>7Tn#X6mOv|6nB>vcXxaI z{&vp2=bU@byYJndvB%hZWv(gT`sQ47XY7Q(RgxyarN9LM00go!FjW8mCGGJP#m0Ob zu`(Wc3IN~=AS5K-I;%>H)5%IoK)Ly$P<9XpHvk|Lo}p%;uD10|_+s%?Ts|z|)3CP& zU4RTun@m+MNdh3}{R@)n=P-2#7;#)7Ht5xIKpq2~spM0y zZ{2zuul;$9{a!RUG!mMjeAro-IM@+PiLrlw_vcPc#r8y&7xz$eA%m z#cu=v2yEALQ_An)^+7>BJVyROZ?d6&GHBlQfTht6V?h)BhGK*iP3giC#RbsuC^M;S zt^hjIvvQVSDSa(C!U+cyhXfV*t!rkPh&U@_Jcoy40E+(%kccITe)R?z=~wE#0(6Q4 zvbnSv`_Vcp0Rbw7Lh|SVZJMj*IG+3fKBRau&J_Z{CkV)v&13iiXd(wN>TGNI+-vDE z>Ag~W`h6gYEj~hZ3~s~I&Of*K{X-YwlXWv(GtiC$&6{9mvg$80+9a7C#GM<+H)4t- zx^CH=Q@xAddwXZU{Jyey?jGBFs1gTl)yYz+)Dqh)$NtAJ05ZifOo0;}#V@-F9T_l$3_ z)jW(H|jC1B(bCFR6MbPbiX+VBSOGaEIjUgGyPZTp8iA+;L^k`5sHCA;-X-PtiO1PBe^9fjG!)ybvSI$z{OcGG^v7p4b!2 zkywCpAQ2)KL$xQkpc)r9=LPhuW^s^?WdM`qhr3~v(BJ6kAtw&RmEUY%yeIa+UpXXU zB1WeNy1Wmx1bT&Wn}qYxQNfx1M82nslR=G-$_JXjKC($_GJKbLt@>e{Ua{w*hkg~^ z=^44G9Z;g@#j)V=v+r*ye!*Pdl8!%H%Qmp&qKF<8Qyan9G^qzHglT46sp{r0QItDJ1-n^#TWG~GG6cQC{93qI%| z#YPBT=lA8yCXA-FCW0oGc^W;zN-TGB-uIM0eLKgFWFh8b}4l$ zcKiBP;}-k=WuT}Lu>#KEdw@79eMa^B>cHxZVJSYP7j*cfOkuEGrjhtfC47eCFpvp- zO?*?bN0di6pSX`=Zv3eXA>$iWpsd0#=EMY~th8*hOkTchE$&Rpt{fo)UQcAt%T-LL z=U1qn?}cPl3Q|<(Uypop`NblKQIO~q;S_qDE>}SDR==27o!IqdLr`;!SBL=2S3)gM zd$_5d5t-f?*O=y(?v`+#u2hsZEW8!&Lhj4Z7Va1C7jv%@o&(Q&TFm%XCr7 zs|2Jy^KM4ov+~$v&1nto*Yi(V#jzR+>P;*{iEAtqETQU^WeUY0^*xO)^*VLlVrW^W zhHtTyI*-bU25B)_@t&$+3ADfSMF>7e@X)&fOQUp?C@vfYHFG+i4DJ5gk26D5T3TvZ zl{97TZ`*Y4!mjD?OjD~-wNuMUCjBNMlXI^H)LIL%i^{V`1=dDCX;G*_gdp!JBq?ku zv>=OCwGdwj517~5&2n+#8$#Ps(sB*4H!M(a24`_Dom0Ob&n>wrS{#*XV6SIrNVYr} z-5<*w9+^0rirZA*P9OKo?HfHCSDa!W6D}1j{5hOf!aT}4CRlh8Rxx;PVo}RFExhaK zYkLRq{o>i+6Ehk2QySL2o?Dv=}XrnTHZ`35}F z9IK94N!1eg5*nDOnTQl23TTSY(;8D2x%1LIQ`ou6cw0=AxHeKOQtWv;; zb@i%Y>=|s5NptL2<%gOankHplWjm8#Yt9|Loo74rlu3$VitUP_18TYqbFjIA#uQRT-glP%*LGi%)`FX?@sq(wj_fcvwYo+e^O)brrFM#He z3p9gV8T9ERT&B|tof#Vj8zOuO!aSbqgQ>wZ*EB*gUNJYBA(^N{QbQ`7&Q&f|Z3fD1 zd#weFSZ#)m`d|8*e)@9J`ey;N{0t#WFP2!Ay!|l!O8nUUR_;IE%iqReEu#gYL9z6R z#&K4$pco&q__5gWws8tEq%pKGmWXPw<3e0MR!{)--=HP2R0 ztoWl|$KHY70}ta?uf^`$&MU~t&nvxq=)GxRQMLjXlgslYTxqp=H90k!!sa47S!($c z8CGR);Gc+6*w852sP&kk*?Y6y!OIl#vMm+PZq+8s~J^(YWT&1%uM4)Y1Z-HvHXc)`A6La zjcm2*S|g9>2V|0XfjHv#S%aOm_0;dl@X52Ptj2byyHKFm$QP5AKgWOEDK9ApeN7(} zzw^@a7TWA`Ij##X-V|9K0f~?cqu69 zuCbrAG}f;5P|#89pV#my)V7cA>2>vcPl^0c_4*LGfev_W;+@x?Be0!(Qm)| zCvPT3ibkGBS>$m8I-5cknJfEW4xD^$Ke{}Mvlz9Y^jOjdV zZS0%{JcNON(<|_J{ns!jknV3HF4n?8vA+z`y;FQkCt>eoM#s;=&2GxY#Y4x-&%qDn z=Q8HwH8*9W195>MoLoGdT#s!pmjHxIfE!Bpj|2E<&xxw+#FngMfEp9xaq&-99*4j{$?C*%4ud} zW@~2W;`}Jf{V!PuOM4f4XG{D4LiNAC{vQlHqN}L*FB|`Gl}LKLGhp zrJdD19n3gY&7AFBolMOn-5%X!_-8cEE~;k#hUb4{`lI;YCOZg7IGGu{*gL7)+uQte zq2K;9UOEsj2N&HdMPpNh-Cqd2{>$D!rkKHuUCe}mkBbgv=i+APf~a#t1t2^ET)eDY z`~qBDe-~A>ha=2A|AQ!qM*svCfbjhfqK~A58@m|)Z-wEe0_OHkw#JWmA#9B;%s3tF zEP!fOGqc_S< zh{u#Rw)yv3|MCj{sFBBv%h(jeYs}7P&cnqHHsgY@^YMZC*&#e2V;(4!my3(r?60i( z8xaL$5YCUQ?D>y8R5Nq@$CV9&?r+=^FgE=wr-Xr~fAPT#4*bV3;y=OTzh(I!^W81Y z9+mztSn_x4&i3Xm?#51LViu3s{Wo~X`Cqy3Z0z>GcHY#4n}?r|+mxLbYHG|5<}%}D zH{mleVK;`FKtWt^s3|Y#5&pk>^&gxEfds(Zk1YQuZ~xDn|A%%{OJh3=v&TBk3H(0~ z2hIaG0r8vjv-6sAf!M)3ARcypV`DCMQ*JO9gold{&SMVxufzGD((wO!IHvY?Ze~vZ zj$RJNPR5VBjhU0PFwoq|-j>eT!NCS$YWx=$Io<5w|ES>q${sowd%Ay1`u`F(xS12; z-|^tzTltp@bpJov``@zezgI&3E7tvATqe%HHqpPA1J3`sQvCfM_IE6${#u>C?K1-Z zSxxxw^6+uv5qPYK|EiJyud3NU?A!l;t%`r~=5K-jyKds2Lys-WUzh)Cj~;LS)nJ*~ zJ!Y%ZWBc?=XmuO_sMV8&iK%;}@3mp2v(0GG+%If{x7M1r_N?3!-5P$8c#UE+4A(N5 zCe(2K2eEj7uW}a{q4XfJ;D8w!F*V=HSaai;WqVPrHD~m6qiu(U)2F#-N zC{bO{%D{+;Tb{((xG{YJqwJzMum)J5c+do3fxl9raIIK3kc0A5uXTq6EQ?;xURjq; zj`(|}eLC+koFLw$t-vfu1tvG(zk3@0-a!dKi*<+|E3_yR|I=c-;zaFieWF<=X_U>n zs&H>G`fS4HrT&oW9|#>oQ0~<8#cTE>spzm5rm)%>a*94OD+|7p7d)Pb#jD7BBk?M@Ljoq%`Y9}5?Rl?`A|orgLtchkrCaT;PRK59 zKpB7oI7KnPMIp$jKCZHP3-7Wt4E0<*f?+e~1{sYE7Vla-fQ|5pj zfSu)^FNLyOCcy45nyUUz%_9OGMT^bD3fo%f4r&Ri(l-fz2xy6G2p%l~n;g!PyI~dd zRzL){B$af%n^W@SyD+0n%;jQea6>7oP#B+#ytd-pyRCB5m$s(twtZ%B%sCO0*AlO2 za+)NsbZ~&5>Y3utVHNGQMcj_A&5NzmPa4JmG-!v!fD?d{b&w7a7=iv6n9n1UOsx;| z%3-NqWnYT8YdR9gvy!lnY!q4CL8Q}Swy0@vrCZe};jxIQ9shRZ1zO`ZM=yLsI&l?#4nz7Yl$7cIO*_M-~&ktf(4K75!*LSlA0X;EWCN zqVKx2BrrbQJbs>A-@eVp-t^Dok8pP)IbmybJb@FbTji{qQ`SY)vK;I7!nS!l@w0vA z;EY_QG0H7$ur*}2MA=9=?}NGO#f5xB_{XsIV8A-ymLPyAwxc+~tjzn|=R2QaPgq!- zC}SG^p9KrQPuMF)ij178#Sjty=0%Sgg*I1fhLR!d?61`4ccM(6We5tR)AlsjieOy7 zzPwpPClz;>+xp;Q1^fzY3v4^XQJ6|Aj#T9ssy~-N!SN?prUooFARDuUsxJ)VhF$@I zCTNfAg-#ICZ-DCI#$M9q1}yR4S7`I}C|$Gx$2V_EawUm{yp(4*Cy2~|AUNhy(YtE*ey>uzACL?eoI#=d&ecJAE5juBS`!(5J-2BMiV9 z{3LiGXt{tO&d0fI(!)|YZvt5g8Ig&%6R#jy4W&PsCP8ph?}W|>Z?b!2f0n>GUdT=y zfB8E$Y3zwGYszY$rxp9&?S0}|A}@QR)>49b({by{Qhl0VR9F}Ref$96aG&Rm-01c_ zQl`B@qs;#(^BN|PTca2oC0p4#V~|h?ieQ<$(oyK>?s}t3-J?$-@Los05PY4HpOnx8 z2r|GDlP$95oh>_PZ*0E~X#o3LgzTQ4eDqfX*Ga-Y(Zvs}gUNB<_;~@lwd&)qtuMe# zg#F{XX+ZK(6V2Y|W7^+Uy%pp8FD2s%C?|48)^y+H*4$u&6Oi~c(^^@8m!apl!23ic z=C^c76;3S^~qcFp&=iwCfdIe0}N8c3meX6iu3e876AY{tafwjrb7 zfaFQlP{13qC+f77ck`wX(EC{87MeJlYX&AjX8i_+gOhTj`2w|%&amMu9t<vJ)-S$KJnyx#IE zn18>UH6IM)rGV8Q9cDVeQS#NblTPwQ3GinK@ZOfKkoxEkN`>$mfq0Nnz*LR${GB*B=Eviu ze8Y$6ma5nSyZj_I`}3!lVjXNR=ShCExx??2eF?wVO9Nt1|9Ash?*dH?X&I)0ZWzEB zOTD)HvRRaJQvD?mm>P<5>%Dz)lKyCiwmXbET3;F3 z9QB6({>}4FUNjG=YXM^@r+%7Fyi7bXrNiygZn>;;YX4~rBTwUO^sJ#we9n_CzaT8l zQ9_O?4%3pl!5*fATE&6i9Oj*$@#qa6((??Tmyy7B-|gzujxag{I?GUe@B*IO)@zbu zLFja?s#c`DC1>};UEDUvXxgksavJVP2vgsd%nt;vGcs94XEM>3A+Yeu#;yz=}AN|-HMn8s4J5DFU>mFNzZLAYHkTKx$X@tkZE_W|h-MDtw*%{dtMctir zxXt+Sul{tJenbf4X5w}!@$Xp9s^{P62i8YRBZT_j3zJZ~V$zE+(%q8{N3;xMA6+~w9`|a?s@Af|ldM?m>R^+n zZ4`7}7KzlG#M|_v3zFrzX=g9rskzjM-{F}DcK=oc3WrTDwBw1jjFe=z9 zb;iV={F2@evv_a2e{(U@Q3gg?_N$h=j!sOl-T9Xx&h_`$yoY#*I-s+b3_nN&Kib?M z?J9!cJC)1Y zF_mE>%T8Ny!n}18?7N@SL1v279*-#XLe9N>21MAiI*B~^FX8bI-PAODo&V7u_KbjwyvdtdaK_8DbI@NxqT+j zai`%nEk+mQ`Ly!^a47iL+k~4pgvI@wjvw$IY^%gsChMs^z&bp}*Z#Ai@kHibLNzct zq~D^=5}dEEr*HV9;V}<0X%+d?KBTLtVe&&}bEm{`L`VmcZrt)4>%4<-qj<8y(y=ot z+1sGjXK}#iXS$oWFQ6sAhe(Onp8Ql`zD8eL0bG7BNuAQ87(@VpZrn4i8}mCKQ9alL z9>Un>hG_k0Vc@6O!(}lc^(lSU^0flaHJPd2KB?aH@j|uHU>dJCuTV^ba9_Fqgw^eJ zXQMW2t_#Gk%b-7Qk&-dCp@sxVq0Q?tT11M=!j@mQRw8p=0=M(GoQL@&j8o2+KC1Lu zBM@cz{e^>z0{tPBOESP4VaN%RC+%*Ln7HfPySM@Gp*!_x-~b5z26b5suDzdz6Gwoc zQ&)WtM%t03@hF1v%6QGJ*B!_Lj;+n&Z)9YYCXw+z(9w_CX^j$)sp4}TX37Jkbx1Mg zHIi6GnNdz+o+T)b0*4surB-2tf=ohzgR3N4lBH!@x)J!xdxhdF&>gj9t#dtuUA73< zTAjMJj$@#K6hbyHIwf{`4k^x@r9`=X5%9Cf+<>$zk3oviR04V+e4#cLu*-cSL0&tz zN?JwiB~8Tgfj8l*Jo(U^WEHP0vT3H!=`hDS{%o(ikX&G5FjtE~2#QDIQ@b0^m;os# z-IAf_yXKk-L51|(TGpqCVd}e-9n(Rc%B3)h$7$bWq+2qUSXR8`jY_;VUjuheZu)Mu z&{!+Vj~QKEg7gdxe>R*Wg~TKK!yw0MDoSsaF9yi~_GqG{HAKn-t>#pA;bHpkYv|H0 zGH*(B19t^W*quYlGpmM8FB&M+QZF{U6_jBT^gYR%_Hy4|cTiBaD1p_Ov%28=SMJbR zIRP*QrAQmJ76l{3Na6Iw83u5`{_UzRrrEtJTznQ6QSmfD>>%-VKk`>U-;HhQq-*qI zRy%X5BN4bXk50Db3**hwTVhK&P=Rb@EKYI$-V>COjR-*U&NxF+!f&j~hPsO-zhzdb z9rMQu@d8onx%4HAiD%~Iam_K{oVdp2x)KVsOGc%~t@Ixek7N-7W!++}=F8;Ru*Ah+Wvn#r2q zg)dF@cE4Hl-ek>{WjA5us)e7lpS&4EuN@ky-E{?gZ218~CV6F@p~<6*^E?*pT4EQ|GUi6VN_&c4juwY1@-;+K%bLs@F&*z)YKUhk8>vh|sZ@7D1s>z4+p7%mu#V=7WozJXm<_AcM;jXchmQRo z(b=eRD;U|xA>@mG>x}Sm)O#`vwAiYy>D|x4HsZaJOvM3U8j|HS;_1R6LWm2)D7i}( z^~BhRZ|!NS@(gM-*;WJO{24;Xa1j)r@p&8)5fGNFoDU2YslP&}v6mxRc^&fFwm*HCQK~(n_Z4gqFt@5` z)%j{(PmQ})&`J-amLplP;isa&iw-a4j)g*H45LaZr;=7aF^(a2ncmTT%~@wWPqgH%11SWo`ZGxCXprJm0<(9tMXDZ9HS@z?Eq}5z|g}rpPDC`UTge=9E!PxRmqf$JB%G@oKzPUso)E zi$A^`6ak+1)dRlf$@{X}Jz#|sPx$`w1)bkJK*wD@nxPz7J_FE=W5(z3+ru;RS_`ut z10p!*x=s`o!O(Kj;qDnSY*S2la@$U%j(+g>Hedw?tz3E^gyRx+gn7pV1ANMIcJ-Bg z*pmIq|%6qP9JJZ_(*?Hi<)o;KThyeNF32le9xnU-c{q6 ztKuD6nfO-PY$9u7^etD=h{7eG`dSlZl_gu`iqMc?Pg_2`=E)(6W@5^cE4no1% z-CI|`I4Oy_36TS#9HXnRTUaHod3p}XHT`q;x3@DU41H>kz-bDSS=6a1m27unbb;=E zt|}Plr;5;psgFSQl6xd0H7K~jDq8VJq$`&G%S56&riK?@3p5t~9o1`A<3+`hYN4Q;rX37)?qNvas&e2i*1;o82OM{`#Kd_@ z&=`Z;m!Px^^h19TTv_WriA{#>%S?HY%`3tP;1D6S>}D+XQ1G)88n2*r0nD_Tn1aH! z7!wYW<@2UYh!4OdXckuwwW+CNx#P~)Z}=GmhaOaISxMnhbwR?n>a2KPJbbp4VimZ# zvjm^n|4EtnX3qOt>tzpmcj=Q}hJZ^X>COF>H*1Frtov2`K@8a95E|s(T>A6I= zNM;61qObx56U*c0p`xy2?IDCR%SL?A-;lL-mWU{sFfRJy3p?!CPSj^H-1i}!qE-@y z|1`LSouu!~I8Ja0JZ2ulj!1`d7w?>;fw*M`Po%45*HCa=U@?aE&LZU?KQH1ZL6 z`o{<1sT5Bq)V8!33-)uqUlBu-T zEKAz)V9(B+ug=)ZQUcRd=|8j#nOUjhg@y;ZP%em6bFC2U>AO9zU5TUPjCFDk&ZwHuQY7?IEZ`Gy#}^W) z^zgB08A7l1SLGiL3lrgf8-6q*UsZH9T^IX%XKR{?PINH$9iga*MMB-@MbI@OWRX4B z%F&mA)%TLot@KTdcp>C#^3mL6(@&p@!j?>L|*aIMi3w% zx$r2zY!vtFmX4B1x#MvCY3uwSs{*ciwy&7GWJPaxiWgu}Y%}nlbGj}fvre&wZswG4 zgkn!Og4OR*C}82+_?Ho@0|tX*u0{-mi0xdz*Q7kn3*rZ}QKhZ(x~NQ70feN#$A4;f zZIPU_X`Mcq)$he7_O3*O%3e*ti})haEQuGQkneK$~nCmiw6Dhw$hLp zm@AF1pz3ov`cP25Y9HEJmO-|zT{HFyhq6(dV3w0uu2aKSUPbjzO2>xb~{trud~sa1izSIcB_sB3LXNB6Vh;}fsl$4nN9o{COEDdQij`* zDJJIpqpHDOPQMn*fy*fJxO)3Gg$MOAzNqrS;NpIpm{?a5O51Xa*(_lw|4~xcy31XE zwt9o=<-)To(8`O7Usxbd+Pfsll4o%%c?+?a5(VZmX*rsJHsnBn7Xm3uut6(>LCN8|2V3*njjMzbk5oKdw&uY_ErAF zq#1{F3ZNK_J1~fqu=mw8K#I~OL!q8LPO-|ICvV_M$=f*9QCtF*zVS%g?;8L}!hX z|KRkXeQ`iTT!R7U@xPkNM5Ff;2i#1M@EVN;rHx#onM_{K#?GiBAgh;#NeeF{+c7mX znoO7$lT$5(6fD8wqb5D>PTBl3&SrpSgHOy7v-k+e)2~{SZp)*{O4cb1^?|@2V()p7 zOpv9h0_$WAMONscaJn$A4m8b8wjigeqad{!XAm8`r$*2mtj558i=Vm-sH+m3E$vOD z+SJPYa&(@ju_OpI*@T}=uNf{S)8E(w_IvHMHrgA^!R}Bq+K5=L!;P$ZvQ*bs+6h(; zah>_khU#}`1w2Frc>^eHC21% zF@MX+A7Q8v206MsdFhB5d3bSpAntCay*e$`m8s1U#j{m=;L~QLeZ^4!WmCaX4Kohv2Z>vzy6ACjsdiCCzLV@`&+f(kX~sV(VK z%Ff?1Gt6462o9!Y6) z95|T0c2cI0ODWcj3xkp9@m74emAau!7-g;>B~L^m4E$tyNp-eVe&x1(`_5aIJA8Zg zedNnMb)Q7P%Q{<)Ik(aaC|#!|SI(-EGo-01k+Z7^;6WJRj?wPZ{p|N?FwtmC$|miP z)ZPq_4MFJ@70P<^lOxuShBp%Dx;>*9{LCcVDvz8yc#~i)aOJdjevxbBLGp@6 z;AykVv!ogZbQP5&{#D`GCO!&|k>W|*HVo9^9xcJ0+ILJ>hF&-p`)+>61k_f8E@yqi zUxwHXog;JXSjB_dtxgoJpkMd2s~sypyuV;C`%@164L605w$t+F%nW@uZA#2$7aQpg zqLn_l5uoq>OqF%j|IWoB(((E$7Bk8`>azBAq62y7%hF-VR9bLVzI3J#;cg<(XEsk9 z=4%bjYC4`{+%jMYP#)N{>!*&JF7Cep>b5BC#XwL`Cf{-WOHlC^3|Jz(J9E!H{$S2Tiqxa9r`um4PUj85 z6!B@WQPzwBI(1%n>A*mgf|b0lC5=AI<;NsK(w-cIg0Z1bDe z6t>dHahX?;wrlHhEukh`vXRT5uae>&)4>?Ij=o&_?U90(Zs=1f>(_dvke1u6yzsow z!jvNw>b=yRlq{hyxVh5(4!?#}{y98AX^rnjxp6Fv)3U+=j;)?J0*KH&gyROo-P;qu z*|uVa#u71xz6>SKOY!tAd0GUWsh_XDU|i8hd;y5Xp8g^g`z#)Bd$5~+%dq-Dm%H`y zTjYBo1kY1jDUk%@dH|h2c}DQJB8ftNQm7)oK;B|dcGgJZLV^`y4ZnB)0K!og5n4A^ z#ihO4hx#3|=P_{-FOB3MN{41`!IVt}>Q+>|SHJQHZF?+3Hl9B?l>X_H!hgmC5TWd3 zMzcJB9s7PVh-O%4WD1qf?~yv66^qX(^R9aXF5`!9hf@2vD}GQ+;CED$zoSDR-~E|9 zM?&p$4xKee20l@AmSeIx7YD;o)ty3oz?3-Xn^@KYe|Fif%qF!~Ey$WilKhYrFS_WA z*6etFa}}mrV5+_}OH;+@&27n%=DZ!xjCz(eUu2D)&qT5M_?&2us#Hz{TxBOhfBq1w z+i8*Ous00R+MeCeg~ntXQ`dh=3{$6%peQ=*mFs){8 zy$~ve9_M853Ma~y6MEFzKs2l9R7#3E(JxtM48MnNjoGNDG@p%^E6bc{S6D=wt6U=* z8IB`+Di+8SeJIeDsGb^FKUa@aO(kO9l_Rnz8yAzia2?RsLj&b)oDIpG(0wb;Ng9vE z2Ktq*zLjP#>2rmsOx;r^W)+@0R$+1`<@vsGMKg6ICHmG??N{crH zdQ!*B=kss^uWTqj&yg6VVcvBrj}=02SzajCdj6SBBLd9uB025M-+by!^-tolA{{%> z`POtEKa@^In%2M?y04Ta1a-l3r|(knV3CQ8^8^d8);%f41t7rVm7ZYnC~1Moe(kw3 z9Cm#BtZbFkI9S|5tACfdKm$|n3WW4JriTHMtCII@iPUoP)zkJ5UZXH}glHIYo`d-H-&;g{(u>rJxRyPjfK5*~dhQkeiJL=` zfE%6>@_I8Ijc^T>$i?d!#aPeq37;+3s$~M*N>w`T~m2u+pKWf_IlAZ~t zSnehsgsaPVtK2_bG2G%v_|*zcoQjN@JD#jN%bJ9X!bd0Rn|Q%j>4l@=01^CtHQ0E9 z04Mxqyzw#%_8IAs`K0qQ4Qpw#}GNRtgeg1W@ zmtpE{X|&;;i6C0u){Tma;;-R@m&OU=NQRNqm~5GS-MTF)u#@C>X|1x~IO(T4q>z#Z z1<1K+v*(kFSujOI()hd5CpQZF ztL`+uD6wnJ_e@o1#FI9)&rv)Q>lk5d@#qRBMP5Q{LnLUhYOIOy_K3BTteZ)=bv_t@+|N#l{` z&5OG;F3-XX{cQ~LW_s@F4L^D*#-pFT2dAJ-j|oKeUek<|ZhJM`YJM~hZWkITJm2al zLL6UB0YuQ3s{*dx01m@QYE|X}foW$bA6PA#YXKN8Q8Y8y%lqmOsf0#jT-3K;0c>(n zl9iuQ8|Rk+Z-)6DG%XNZVa>-rT_udM1SN?l`Q?A++1=+E)#A6z<~pI=V?vC+T&u}j=QjtUu~)XL57a1!su_648-PXp{)Yx!k?D#2 zx?)8{Fdb!1jP&e)W!!+;)RL51`4E=FCqcYKjOYf`;$e?|ixp4D6=QIeEqggb&A@y? zremeM4WF__HIjfjx}JR0s`&$-+TyQ*3CYwc5?`>!A@y!s^Psn&n>M;`oMpsI(;hbS z`jo97C}67uLG8$`=cjfIsf?RKS&*l~g;ctc8j-a21adz0zLmqvFdM zY0hFxl#IkHa~=`vQ6px>u)^#e?oz3u01TaKnoe zCUIvhRTy+!Oi!hoNWfK87!dCVxu@%)5H;?Ffo^nua?MOLziD9y08v3(P=I0ifDMuL zsAm-m$aARpr7HnxpFq=}VZg^k#2>4eT=Ko|0ZmoAH4QqE1*D`g2goV$!p8ES0RZ>8 zbcc|)l18jz=n~%{Eg0y`J9U7UI008=voJ~ReNsD)i?Tb$XY0$qZE zwc((4M>NSjhwI_4KPAhaC7&k@Db)NqD~B^JM=hGbl5*}H#%#SyVQKGzLtR}2>&}Xb zh0XkkMB7@W_3Pj9Bsx1Cox*73gG8I}w%m}za=lWbRKYzW`Wi(>&Cv){GVc*fN!ut2o8yCZwKSO*q5_){t!?{EnCoRneSNV^62^TY z@K?2L$UaQW=GYro4AW6J14TPC04@6Z65(=&;3t=7t{Nd?*%bp$GhwMC-OebE&+Us0 zHaA}~6bToR(jo^0xtT!DvMpfa6?4ARDvHIelDp}Ta=1_Z zOr}~Xq!5LQdF*HI539M5Z)%QqN7PS&CtIgor8+V~4l|7@t_gyBUaM#6m-b5etC2Wp zWv@LmsSfm~=><73an)WL6^f|5Ufyh02?qTt2=}8v^K0{hR2?tydL@6_lO7UNOp4)e z-ymFL)s3*eZ|^S89g#9+mktnZFFB)X8S-C zQ@gb>fT@pft$}dab6L2ZrY#^MlF8-8JuedzW_TU(2o>f-G<^j^xa@^*)9A*`8?#i+ z8?O%L#mpNp6rmL;?}dq)fntW(id~tBX8Dok`V{;}`H(rI-d1;St4%B|^)t*Pb;`yc z{)WvD0X5{wHAJPJQh_KM*`fiC2qCE6dza{=$;eyc*>cDcRLfp*n_j0m;=^Q^exJFBQif*0ONj%1^^HEdP_B%gq)?B7MV3^> z_8aKIj-y}Vlxkbyp<$aR-pFpjoAz9ZDj}zzm;vYdNJJvJ<0^{UNVBEz0UC)nH}SI& zR!7X!yjjQv%Z7Vy)|j_buHS3f!@{e+*M{R5tVpl?Je_hOWi!)tRovhGn%SY95#BCH zte6;}tE`r)Mmk_&Hq~P_E3D`XEgxwbv5-sSkC;234`mp0?`7%d{^ZFfiX1pVy%aOL z=lb!4`O`Bejg49SAR8HZ3WD0++Fhuq&=8Je(h!yH4+XPw`UEeHb|S;_7I~YeI_)vg z&-`;-dp|@@EBz7=3_CHwX*9l2joh-bdj@qvDY7I%>)QL7eJOX_Jm#?((UABaxqj)Q zgCDlu2MOyuz|623R1D%AAymd#`Z#Y;Y7fCEL|Wb zFufE};AKniGGE0of?!x^&{4GS5YtjD3`I>_h_Lx(sKS_HN7MP}%+I*3mOLoWy~h*4 z+KSICAMqeg!HgayV5WS_C*xcuHafr(o8AC;m+gfUy0qG>zf#ylMvQC4VQ0~k{|kf7 z3paRw#Sat?6OLDE{MxX3%E_}8w`JoxwK0y*MP*7_ky(Pa9@snQc(b@+WWQ6)mQ7y6 zAbRKGht-*kaQX9){a-m<_|NxJB}9!h4vICS`I z8Z(Q{H^nfv@qW>9P)jSB*9r}hzj z$BAu{Py$( zu~OlQDU^YZV@D{>7UMH<`V$$#0&@P63gwqsS2$MOZxvd>x^nTxvYD-V1Xbhxk+V&E z=pNMp=X*VS1D+l^N}nqO1&~?j&0j%KH2Gtf1U1=UZ7a1WqPmmPd5p>kk)8U--m!Jk z1{@|Ed^Yr!w9b_0!lcx(+L}(f`y*h0$xqd6bLin$*k^VbZNZIeCK8D%JByD5{mY!} zce47}j{ZqzXAWIFsD0tYi2a?Ow1N*2_O-%zuEVSC2!|W-hM?940Oz{4s){H96W2Y* z`#Ka9rskw4u3{UEO9T4~v-33ZuMf&)2YZqIsr&-IF`zWb+!@3H#5HR3GB`RfAj5az@t; zW$gyzP~%@O-)xcK>QA><*S;~C9ssLGJF#q~`E6y+)`zQY^As~bb(DfRb24pvo6vRc zV`{|uF>oofmq?RM@&0!1bx}^Y-7}Nm8vDW&yMQPYDw{{El%>oy+)46|t5{%Y;^=~fz83e}7Dd4%gBNLLi)=B$?-o1YasTaPbbff3n!9W? z)A|t1l4?R)7YXQ@!v>(ZUrMAaE+J?R;EW(NFrtv??+uZ}g` zPP5*k^3%LK;l{DwPtqA_AJWIL5G0_I%3nd1I6Khk%XeNb80pcR)(=$xQ4Z!2m$QrF zNt^G-DGmZhV4niE#qX1e@xu+qiOnb%+~ z(g|q|;jwR;N;jQHV>M(V7b-hLL=$spBD&x^0K9y}%G#`HC;vvo%T4X?83i+%tj9rQ zBVn}&A`!cx4RJO#(yZ2E)`t=_4fSvnnR%brNXxBdR?V?GjNOMHH!S$fJ7?g55p4iH z2tKP9ZWNfTAF8__gt6F4+=rz}BJPbk_oBBBx`58nV_lqyAc zbQOV(^dBLA&%$=+*bq!DnY)CLZvT7*4-Vj3iqdLafn&{Nif}k#Imwqg7sm}eFuobL z&VcKy@oA!9Xe%cYMsRFlg|OVXhSS>_dV#<9pa>{%-An(nF8hWNGHv!Z#7&w$^%~NX z8}&&XoH`-@8d9Sf6(Wxqaoi5zb-?_n*jt6pwb@95-eaRsJ%Er1$X0{5I-=a`M`KK7 zgiN=1F18y`QD`m!So2y=C}s84@|tNp#?3c@xe;)Bb5Di@j?b%6S6qyqB9pc>ESd}c z&1yaUlGn!06{4x0%w>J}X1Wm#fy`x8dgf40G!YSf;Y$I0^N9FwGVOQix2-cJg1&2H z0t6J{B!^A;82>?5z8dS$7f?;Wtl3P>BmIg2{GFkx!unjB3C*~}ZXp!0AxPdfh5debzv8a-<^eFk52BN>NiYAqa`K7oT1x1*7a%H%X+%s{u(e+BT> zBSJWz0PsO=7Q3!BokbnmOvO5j?Zkk;2k>Vn-ucdL&#^J~$r_OD1vg(2k>s_`R9!S# z3|d;kQb*HK(nZxxv#=&LQx{DUR8b4$K>+R%Y>AsZoLXx?#ojRfe7aM?hHt)oy*Qo8yHu7BZ|Kex4TfWRLasHKW+;x13 zId^b*2}-xwL`M6zd+P>_o@u0Stx)v5K1u4>H*p-tr=NjVeY&snL_}0z0mwcR5P!d2 zgwtntPl-_o3g8e|m*);=$o?VA(i189pG?$QPA(=2O%srOpXMY%vcq(klD$yg0BvZ}Onn5Q&i%;`I zqD|D9Dr=*b=eBc}z{%m};HWdI<8Yq~|I@iQ1-av%(XA3)_0A0%uxG0FsuaSo2a+2y z3Wl%#uwidepAlwN*V!f!vt>yQQ;q#NGW|46G_*xHB4U6LB6)6_uD(Tx`sXb!K1}AS zh(4h`|5-x`1Ra{}kQS zT^zHdC{;*NPo!_%d%_uV+qZ@1da2viNcr?{nFny@*uz zD!R*h*|yj73^#Aa9f`A*py~jVGHG*9>xk@66A>{y2sE1q52WeZ*QV+EizxYXaq;1| z&dfY?X$$=^H60j0!Ck5TGd<<$@ik4tM$Ttb&)LBfIf5Qs^2Ove(r8&C{VYmY`yD+^ zAOWp4q~-ceWC*o96E$BGCcrHxCG#1X-k){wRO?MtTgpgCN37+<bLFQgILw-@s0%PiK*_v~l49?$A;RZAMO1PuiR)MO!yh=}1LNm{7a z_s*=ZUwKuQr7x$_Hj9f7{|bP=nwfbh8#2+{58w*`yiiA-t?8((e1paAmho0p_z2^~#WwiBuAf z(s;Pm<>@u7U3FenEtgJ0x0o)1hi9vudI+KBWYsGn@k$08jA9$8#<5wH5laQp^zQuk z_bQ&7w3z*^TU24qScWEI3(;uoL#MNhPG^ZijQ=HoX94)d#l?sJkOcjZ0tjjV{;dvw zF3bDLGyxI$y!G^h5B^IEo5qO1Ob6S(??jo$tQKK?C3=sHv=kN2oICN2Mxr**1ZG=C ztD_sju>UF(nK7Z!3nwOH2=nrGGT%^UgqYET461W-LhYf|SbEcPHe`|u3FJ*T2HOJk zK`dni(>u|G&1U8p6c9ia0?3Ru0@NF^i$Ig|W^+GQR@RVZZ4&&i(1b&#wO<7A-XWZJ zLRY1(N-(7dp{4&mS^MNihHMfclFX2ei7?}ZGF9~ew}rEji*De6@9<~hMfb^3PJ4P! zL}gnISY5;hBXFT)^`9!SQZxj)dz1$Z6a-+8WSm{~fBk7P z_I|Q=6&r^J5zNu)dt~HU3PQlLnPzC9o6OP|HTFMvQ%uN!L z+1af3``kFZ=PoyzSD?FSF9?XglI{APy_+3Nh{hN6gCZiIJbY3aU>~22XfkCaL#T^} z)M#d!2P4r$Y$F63jr}se%QJO-4uJm#;I)g34<8ud38ysS^a?VkO^jNe4NQSIH4CL& z8-J#*bqirzKIi>w@{nhxvDi>&Ll){2FYakICR+(Qq#0R5nHKi}wz67<9W&1latNn_ zJPO)LK)}1=Ff8aoXYLyzVP8c!y)N^Y513Ro_I*pJ+#8p=V-oCX9mRo(+mOV7fQW5I zk~C4PO&4dl%8~z_TFZI<;^M=Da4HgU^i!kl#weN4jAoMxkz6(elZO^FO-4HU3$@1- z>{3e<#n#dGHTUl@Ldusj!f^?P?VW$t;;A6tJPpY`x+7pihPmDdr#rZoGw>OBnFhi? z`_B6T5=&t?-OQ4~J@_;MrQ_tsqY^Z+rE|on1q&oekON4o`6>WE0^rpHI90!;%NKM@ z=#fD^L$)p;&L}}lA<5Q>Z^VeH@LX`U=aE&WNT+q;=dW@btu@KeY0A|x+p}YNnv`|- z+ekA(C2#WWtjW%XKU={iquj5KhQIw@5QHE_SxA7uvrr1BXv6q4$G4 zU0(#+g{(`)$j?Ec%?Bq>U@DfPi5MXe(bVhHSX-k2u)=dqYR^ODYgt@;n0n;AWoG7~ ziyJ&uzuM82Q6n4av&oQsJVUBzLz~2WxR+5kNz>*r8x`jM8FN`4ARv!VW=&6vVLl+e zAgFcl{fZ_xUz71obNncdYUM8TwzJ#%GwzTG6@6(t{(qy4-p3lc4@wM=CEAvI4rM-@ zX&sjNG=+O5B4S&SQQl3Nc4P2-0N)MZ-*3W?)31QI&XPSxCK`=`13sYQjdhr^CxA&{ zlWEK$50Z1qqOLQy~KcfZG6k3xFS6TzvTc#l?sF zeW*GcFr$r@&t|6i(vI$Vu<9lq+AfG&Vgh$$$1orI3gls&8XLB{K&L2Q`i?%-~fQ9_j$r=x2PX~T)bWTo5u$Tr;FieVB`}f&f-30`}@nQj@8QuOFxx_ZimOj37_^$(5la< zax~Ruiin6UKvwdnd^bsNU16sH{4=fL+}Fp+&ja|IK1^d|MjNS2aVhZy#5EzvlvXr# z88S&^2s!uZD4|Ydl!r0vy?EuWTq|MARhYCT1!JP)gp+G4e*O*9aPsqUqRX`&-=yYl zTbx5%Lh?m*sfNSRmw9bCGxRb-8OE91KkrkC7E`W?af&ukb}A@fx_Jn#+H}-vBDN8> z?qhY{&5SeAuG|{~ut&FmCbL{a|52Fcv|cW>7Bo^2M(8iD{Vi27{Ua3)ZMtw;vzZ|cA{|+j zGp~83*XtGnc6+^-?(iu^veQmlzdHLDyLp<{(r%k+E#ULwnyP-A+TWf%KTe=?XZ~-Y zK&RKiGbbyThAQi;^-%ZBl4|d3wg#?dYL@gcZ94N!VP?DB50oEU*H2R$+H*1US^im<)3Lz{zWJY<;Q1Ng1^hHYuuk; zj$_YJYF^XdA!KIrM-yICG+Av9WoLHZ2hSKvCJhjcd95NE=l(H=(n}giOPbCm>NBW` z#&{Gdjo1OmdL$axU&FVddTBkH(l|)N`S!)dhxcPQkWD5XP~yG0us5Wkg)whT$Kw+LH#8Yx z1(!dZOsZflF!VMN6e!TxNOgd(ChcZ%@!@{zpN$oAsXxQ$)F)3YuWA=6^2JZ$czhwr|xr>Y%j81Gz}1S38BQO>3Mh6w*FWI?$o3pQk?}3qURo>iiNq11 z`3@zTK))JK6$y3GghFR6*NRHcU3Jc`5oN`*IaftTmYMfXp;l+qDyL?$z8}px5zQp( zNmE88Zt%g4i0A<&B$BV=uZ&#GUtm9KKd`}|mVE&J6M&au2g11`0{9q^+*HEC3UD>M zbOlV7o5f73P7*fVc&!Hf>dB(;H(V zBT$rAA-g{p-qZ+96OTty27){9rR?X9;^HyQUe-LvXUeA58aLVbgrP?5n9uSFHW!@S zbmo(ZCaHq3Gh3VX349lS418?W#KW&a(DY{BcN4xnYDS?k@XziS^S?{zn=!4g@O%2^ zGQL#jRfz=C%>y~n)N2$*S})fIBO-=EGR1^VtHp>Ct%s*~s3_hf*_0*hA!`4;)BHDn z1L$71r)K%C()a01XLN_dA5Q=>^rAN_dar-j2>{KN(XQOPJ+8u_E=d34s2M zTeUrytnWiTX`zu!$R=r#Xd(s=uC4gQQ{|80>q9{VE5?gh^3UsAKxm`fjQQh`A7222@K+NSII{Zjy6 zfE|LsQ!O5&qMTeP`*Uf#L=%lujoH*XQ(MWCm;eL6m2`=@%@sCoG?_0FjrO9}2ehwT zXu99S5v2A^HlfS82f27O`zpE+$&H+l?GC^j(@WEx9{AGUFvrAH&2of-$hvBOh z7azV0JCSSz#6=;*->LWNU)P~;2thbyTsSG1BNdL!$V={<6}6b$w2~8uIkxNTu_iSh z4Tk~&y2m$1^t21_c>J72)0$^m@-!CrTXXTuMQtGOM16^(S;`9ycf`9#!u5UeCtu10PF`n?oziGPoJe?uXl1UmT|V+NjbC7t4J=4(CfG5NWy*29h)2wUbogIvP! z{VV)43q58gLpz?NyXNANhWl;&{TX>VW=J6`I}_PN(^qv7#{VPo zNpm7HQL~#w1Hiu^?PdcF*G3A#D&cH} zqtMiQraAe!GyFJwM(k-GdgS5#?KjIw$B#@iZ;i&_oRjavX6~G59MUM{-?$?voHr=a z%d`nTZu6a$uZ;bI?)t#h@M+K2KKOR|_Wb*Mq!G|{@^$P0&UolO&Hkw9Ccj1z5yJ~} z_U_cmx_h2qra&w{!EoYI6YLGtNh`~}d1z+lpIVB)IRis*fRZEggM8x$Fx757dYUQLg8AFZPcdL z88E@knz!-&`IbciYcz_Y_F}Xr$4uvk3p>6IE~4FYhDjkrOjUn&PxEhxCOJ9lL9ABBdewIGB#`*rj4jJ2WDUY zc&^5ikCA2mazUtMzFUdF*Q~7I)uGa!`OG$#D@Nq`*UHyB@@+gn4Bx*%3SNk~uN%2n zgk2Mlo|HOg%HUY@p!o}pl%Lt1IGF3CzKcuvc0`=~&B*B9qE-R}n*BvY=zBC2y6in6 zLGxL_Pve-(LYj<`FNR=Tk{aN>y$`^H7-N)Qi7Y!Kg!m0bG>N>3R{StC7Xtdg|m+to3H7xXg5YI_LyeNV%Zz_yhd=l z6|mjeJs0E5q{oTCaq2Td;u?tKFJnZNpPyyu^JCW1~ZAW zUc)=W2=WwM`_^M1M`h=3!0nu3+Z`Qne5F&xAsq zJ%Y)Jrs$_}){z;_%(DiAX9IW{#uSx0o)F2$0PzW{N$BN{lRXJ&5!Qs`%(|tsU#XV# z$+VhwOVHT8&8)bz=2`xkYbZrQ?Fzf15JXi3T255l2Vrx0jP~FQ@%;MmfCt}KQ>eM7 zuUFx-JJ!PQ#LnlEmfgRovGCtWjmeq?&1T%ZXLFEz(#${eLIM2R`b4R}H$|bX!FVeUNA)2e|!{0;_z$AdHyK=AJ>GKAEg)(7}DNYqASb& z@wG!ys2;(upw?`X782PCA|hhY2nqO*iOgsrW*M}@-w$_I4u3cFSuYC)5^!y2V4#QP z6#(wRP9vL4>4EPRLS)87!UZ?M5!9a0HX4lD*Db)QPf*ZUcwIFs*m+w4lbETNyI=x6 zpFwNEp2rDTGmaIQHP4^pMzZC(Lrk!8Uy(g?fJ7Yf3y;<+64q~IW3mGQ2tFb4aWO5! z?4Rf1r#7lQ!5ddN+ViUR->5>}F||Px@s21$4DOtgax}UeuqjmH1I-E~j(B zY-jN%$urcM(}X%Z1~yJ~0inx%J51s-jfx9Uvki~7^E4et;CdCO9pP)gog=7odFzvu z@1PH8U5<&+5QjBM@$xpg=x4;uO^jBnD$`i(_2Go3w4LHu?EZ8OSzB-xG{Pq&=JzyX zq^_LNUlXx$gdF`Pn+4ZZgg^5@fIKjOYcqi)Pov>@{vByX8=%t$s-nQFFox)AFc(PX zGo*_YdZ$E4&GEHlpf1wfSZ{>AwvFnX8WVynl?)l#W^!wEZ~+e?)B?(^G2+Ua>F5JL zzG_iz_O@-S4I%1dn;S$El1W-+U)DA6%4Ig-Kpj)j0v}r@^l|ho$(n|_eVAnu345dS z^Jj8<^ZoLr-qo;UF29BSHSTqlCQW-8%%JRbV(B$@jII{PuTOqqobB};q7!+!mP4S* z0kIxh#Kuu2z!{Mgo+b7^DyCqR|EnDdl6;Bw_ZF$C5K}b$t7k^q5($i(1dVo2$&|Fv z`L*QNulZ42ZnnRc%bARTy|e3Mxnp)o#T_l*&}vdPsKADj`{(n6M#yX_{WZzhSpp(P z4k1Bqgpe=z**-o=INA$=Ybb_z;4Sk^Z;pAcYJ8i-Q;`!K6^-9X?t0Xj6j6DMslQ z{x)EO7DCVdbe&B*{0rMR8yl^MKF8;M+UKD;X=F?)o zpO@3*4#H|WPQ)PpFaJ$}dMySAL~Isi<}rV{Yhfso1b6WJ+xSG}N1QQI;uWDCQ`*3h zYQGI7sqGxHnW#UHBGUd85TqWk--NXuu#%h!WNsc~CYNBe7ZZemN~uODn|%_UtHYzG zII+1iX)fff##3xbUnDS`y4jw4}Zm_p`Vp#tDlP zVTq7;h{k=*kCFR=T)&q48`v?KtE;YuNgj1I7?a%T*G4muXoE~Zh=_1VrcgLDUKR%7>$<>cy45^|d0QFms@{=a81d$;9`;u;74#Kc}p8jZZN4&kvOv5bgx6N@ zGR}lRdkKh6n6QqJV?x^r3-|WVN9$-jt_I+1E{?D^*VB}JF-PELL#OP&PgLgBHGLH| zC70AqxVnEG@{o4N%xU+lsxpx8pVUwnF*qP%qu`!NE=M>4nC;569kZVA>yrgLhF_{C zq~V0Loyryln*t?vzG$4<4)jDe0%92gZ&amg>{8!`Z_Qe48%;tc$0j#5`FSDvE=&*$ zJKMIxiau*n-~yEiM}LOnhbQMIFEeYa zYcg}z6%s!~W=Ee)T(hBD4#>?3@_(P=G5M=<_~#^)uVJ}S`L$7d)I=jz4v5$QY#>l^ zSsC1ku6^%0@pDq(A4gvFBPK@@qswa)ylo}DyJIYALCl7DAucgShRpu{#Odd4g=iG#rht4-N<>P(; zdod>HIel9|Pzdy|3J9IxX@rU0fcezsKu)GKekW;=dz&BA;w zp5VwepQ4|`F6SmUv&@vjVVXytGh#?ho90mSOl{VY?rVHu?W3p(Iph@}C_iGXXzsET z0(oWfstEeKg@}cblO;mWT;>qHI~K0tiokWQC9Q%s77@`qe4??>V|W`wE_@v$8M!JD z08pmE6#O`$G1<>UjDwi~a2#WTo}c(;?SYOE;>{5FIImwOXW6-S%V+pz_Uz7Tx!KFi zZtaYl3A<1k6A>tRBSGgeeRsGi$M|x%Nl4wCJkwquIv<1e=P;UN{twrT zjPD}qdQ<_4&D( zX60xGT(e+WnQKC0nuwudQM1u`K@usN;<&bu*PjapE4bZMT>@}TedfBj-`CwrSA78VED_ zc}?6LQ=$3Jf&85OJ32(cHBFcN_;t51+i0_=d{5)j<%1IX9z-@#vx(>jy=VvBG!65@ zNko6nm-~4(0pcnUP1X1^2<;yo0@eW>z?fhoAwiD=;(r3-7a+uxv02iLI@Ri@5k77+ zHEm|gSRWfLi1kWjSw{+TRXoOoM;kKA=}f)qNsK%fE03ejZECTp3IU4j=y&+<*V~v1y&qfM7u@73wY_=_4v5$Q_>{~;A-q@w$i>+q zprr(ApZTQXl&lH8hQ>0~c~uj@6vhO7duf}s2k2_PU4=p;wT^ycO3?yWG69=G=Y9)X z>zakEV6sBU&f<&B*^I!;!)Nm@nCi^dWX4QUPROy)rf6=!JQm!i%w*=Bt0bpph&p_Y@@*CuA07+B*-%JOv-X_pfcWnK z9!W$kFZXLqL$*W>7sv{^uyagMR%7927$4SjW{PZ@J-FkZo6xwQ!_C8Sd?UH3$VQWK z--X(bd=9R&yy(kt{XA-{CB6*D0nZ)mT%%zj(nd=#5pcPQja?lc4VDKa1bX2(VcVYh z&`AXK%y3>C1ZyU>-_NO$$9-k_*wl55D8ta4$vX(1;133y1hkp5WKU;(3Aq%^Y2G+0 zlQ)NEMoTr}EMp9?;iq1xilg5S;G*@Zn1ibS4CyGd7&HB7LFiOqHnp^Jr@lT*Zo0AC z_#?@Q%q#Ah3(uTfjFyFxh$~x3fcuo}Oq^*)%(-vJ>4qHVq~{m}?e^q*;eiwOxNfz9 z5|5ClF>#{h=0}%M>9Z;MUksBj9;85PbPh!HXZ7TxnXVT}U76ay6Wz1y9Zr=Pl zu$$5p8s!4W_JncGSdM*~9kuKx=f^PrT5p)$-vo82_hurXqRFy^lcxE8YJy1<2(H=k zF(oXxA;Q4u>fNr1>vt>eztiF0)7^x)OCVXA;bZe3!^A`j$BrCAqd^lrwp}m}r^H<5 zkQHC%`<2)}LdlQ;L-kIv8KxtRP1Ou()*e_{>pb%70)p7}e`d;Ir4j)3%s^=|M%deM zY4bx!@{ti6#u0BsROSQ~sN>kaGg?AhVQI7!ecp@{RDPctA!?exnHC9Z%(+0!Xg%9}Ji9EQxs!3+sF1s@+DT@ae}DlL<6x6KEt8sOju~a}ISzBHRu-X&aqv9qn`t?Q~UsPP4Y7y_rPP2##JzBqa1) z!Ve4NoqDbLcMW{!e36pHD=W+R&EI}AZZ6%#;X?;;&(rS2)9<|tx7~6Kd-qPECVMLx z;^BuYA-HbZjyKXFfyjnruBymC;@EV7s%GthrM1o@|3^keWZ8ceLe!L&UA9m|I~cC`;- z{79(y%vuL#b3P(Gt*NzeE0xP+- zyX6S>@0~%TQA3j7s%07uKmSn8Wo2mUr;f~M)J=J$F_3DA&DsMu*E=709YlI81HReV zV2lQ!gqoT0TfuLa!xfrR)8Snp_3S$r+E#N>AKA7>Thg)ZHi1-E*~wK|MiNYGF?;2X zeyRTI&YAi=$|U3-+toAN=rC84Gunh%=4&YS!7-uOK53Yv7TY_hV;Jh=N0r}5x5zfy zB&Tj}{+p;zW4d`z63s?I=CX)S+~hoakUwH z1CjW%8i?ASH-J|AlQzTq+^5=%W&I=iJb51;ZEBL^a{W2E@MTdeQj8m4k0u=J`?2Ik z=QUAxe1CIPt3jZoI<@hASfb-vpg$w#nB|6G9+)mCl7)!*KDeO05i>lQnMKp z>PZvz=0T|;IW5F@B>3-SQ1-dRMAP#*x^1&lNvG^u855dK#~=$++DY+=$4^%uIm^&) zchGJxZN`R};?2W1u#T zM(sIEfcRP<`xw9AN|?LN&E#_9PjMbb)6C17dJOCld1YlwJhm1a54+_8HTR(CL-6%( zD6@LC5m|XdD$DU0h&5%d6XCTu{gO!tY(~S1ZS!&=as6#mg%tVLg+Hj zCvwMT#-+wz$DZa9>}?$xDAD+&L%yG#3*U!g(Cu^8c3i-2gfMzJ_qNW=yP!-9tk}jg zNw2M~;^M`H4V|5|p~ag^xVUfyANkm6r5%ZW#y3=QPXH`w!WnrC+<@M!J?AL^Uk`}u zf(uVZ1lCVsd#Jd*ag>l&WMo5uZCe8S8!PR{)M0bmLYpn}<-EiVGaGS2;w5&tFwe(j z2*>!Jyz}vIk)NrMXT#57wo^qaO{83&(~}o1^~=k+&C9Imj$OliH05MR>*~pbBpK3Z zij@E&6uN04)=$~rvG~6J`?7O~{zAtp8!KNP(iTREe7M=W+csgXVtIKLHsVWB_o&(Mzo8KhX*4%R><25wusT@%i2!~e1AZtY)-`dl1l^_6&e5z%$oMGiN^APE zNf0iM`((+BLu5RAaw6@+xyVGWhM7Qh5e(P)~*OU(0o|$=Q1!I7rJiXM8 zAOrpvfH%lMVp9Mze|T(Cqzy0*`F8kwYdUkyU996~eoR#djSFwJm)TyKqWEL)_&+#5 z19%!k8j56Rg)>% z-%%!5G00$RV?Gh`wlVvb4re1{9D~Vne$4{Q8P?a=WiY^I(dl&1X?J?OKHQBkw5v(1 zx3IYQ@TkoWf*9IVu~~c0l0fpG0rB^0iCg>2H}^0R9dwS{N&vpE!}x?)TjSgm#Y*-w zQ<Qt)oG^`zoiB*yWgvk#{3|D*%s^baZA9^bD>JO*zabD#w5u)<~`?{`DQVd zbm^AWvEPedX979wr`r`KniNq&FUEf=%O z!UbXp9fyDRj$d}pY$K+=Ui>@TfhrBJ%NR3d{vx8-^eDPjrSZo%{Oflaz$Y*!7^;+M z)b76^AihCWiq=|2P6(k%p8mkTg@rx{S7?%KO{p>U{O2h3UqA z)F`8WB#?F`5F(;>`-aqXR#(rX-CjhNwYN|x_9Cdc2Exoda@R6{Oo&AIvNU}B!an}_ ztB5CbtkCl9CY+G@&j^jGaoX!rs6M|zP-ry$^@~g!LIS<-@Vbp0-bn*)KmwUaZn3 z3R9QOiS7z$7*=aBHOSnm$qw1(bY(N^oj?F5T$*WCd4w7>kL6mOAs$QCj(#TUm~8AD zsmL(po9Qj6hDeFDATqhp-}?HEZ6KWTOJ7r;M;>Va&{|#0_ee?2N`>o0gf;<)dWn4t(=qKAK!h z;*Hx(oipxJVaF$^_rus1J zWzwWZrZqCL4NlW36Z{q1`u>*!QSf7hj@wPE>F3XAh+HEI4ZT;w8HM}A_19Jp*`3O{ zQ@f30a&iKVdYL7F6QDb|&TxVQR<#MOI)P|}uq8H>M(zI2dguK=3*c}D@t=UKVa;2% z6ivOq>3Id#{cwx@G0h?28z(%Z@Z%5~@ zi9}}gkB<4wK%iEeM61<6nyyI_>2z*Ne@$EaC+wHvW z$6icip<@Jm53I0kkhkPIT4*bCxEu53{&`shSAJ|?TL^tmKF+Noppx&9%L;#7MW~{i zn;-9ieKV-nZ3TGm#|VsDJ=H@Zn*_k$&CEPB773k%E%OXFYR_5CfFBkRKS=>1h8FVn z1I%w?U<_^?xlbiGbCtJuR?m@jw&$dghd%p_$V^=|KiQM}XY|PZT%JPyN+3DZ`|OaV z*P@x5(YRUBk8HCVlxt=y$|?1)ET~zdx~ZcGAx&xZ`ZStUE@$Gn)OLC`uNl%I)7t9l z1sy`YkwAhfO(w9uv`ui;d(RJ(o4EY8#NK+Y!N_g7i7eDfvdrw}oxAYe%C14LM7)&< zk-U8ZrlzJUG?LyxkK56sl!w*}ao8s>9iG4D9DqN?7-37pkeapU+ywA_fOwrsNa98; z1}*H3Fm|;X^$^J7o;=!&o5D0Na36?*Aqv(xH234!byhrEa!;mW{S^A^mn&VKPOy$^+mU18`9m=)o4ODuB zax2bD?WjM(=|h{TjQTRK0YT0K1|(tl9V7YYKFw%_pnx)>3w;;=>-)3!ZX`Ttl%0!L zb=IH#^^9=#%*H zkgT~nj7hju?`{5!H}ltmH8o+fObbzFEYp9*S?vgFYksm-qB(K-k!oC+o1^7XZd^Ob zBg0gQ9^-4_-9*)v#*we+OZjaqVHG3HY?Cu*87DAD8COGovP}sEc9bkZ^-;rXwhs*Km`t{ zzyFw|8Vjwa z+{D)Gn{?>9nk3Qggrm+AI0_TsRSA~?VeOc>e8S)whDREXSxu8h=&zJ~Sdc(43JvA& z3)&jLCT05%6J_V=HWoGRTCI*3JnsQnRC6Ps=jr*+eFi2cT78jiDyzf~1s?_QM;J3~ zTQ*-Kxu5zk{2-8hw-Dl{{ZTw($vJ`(iXZaK%xo;A*=Qc5MdaqSoNt&0kdumy1XHFw zxB7tf5pf@l4WkZ6QQ700+HPj4`CcZ8G&c+K*2zwSsrfVC!-jYJMMHD4*6S!BfO2cb zABuFyyf!&`OOK?JVQuXy*4M8Gl17W$hTj_@>oVZm1jJRlhS&dM&T;*!HnCL$%zC#ivo^s< zVQi1!M3C_cIBb7IB!*UO;DIG1u4U!hF#*rRkeyf0x59}lZ>NxJTiz{QsN(EWCeQz| z=TanBt2I#{QZ?sM@afcL@11IaL*;KMDbyt#Uo+v$*@UJvU&ef94u40BFv~lZBq}wN zGA$)!GUM;f@zKNaL*FM%Jk(D4`t|Fwa&k49bQAa+MTo@uIgc7wtJT71JoIWzPfzuA zcG89(eAP>F=)k_f>C3;f{6z*&HW}3w1@LssMft0$(E_p7f)yg0n)C zbEE8I*1W`==ppbeZ6yG|ei(Dqv(Ivw)*|RZd$Ew;CB4W!mqYoXOTEdQ#t93q^{x-1S48YsIizF_9d ztF!@51Spa~fl#>S!L*x#Db3S>91;p?Kq2!T*NQ@BK>Pj3-}mHGPvZ5j{}ufG-~XMg zF%~AA3JqyXz|YGo$h(@zz4zRW&;6{2Fd@xh-9gXmD_{P6eA0{VM?&2a`8m3vJscC7 z0Pt4;{&HOEp>d2vvPC_)ue09yz?%VF2k<{2#B)`lXKq~c@^VbxjDBd8Wz2>(+uR(k z5$VV;`lx-iIh}clcs#d9y|YyaXu@JMWo#t`C6lZ}-kM!HIRTnRZPIZsJC3?eeR;P0 zcv-NrZ62woNiSc+v^~>v)YN<%R*#y|fb!pwc0(pM3VSZg&oMq6+K#lnawoLe!T;cI zZ%Bf@cXljW$Bt21Wx)T;_fy~9T!VZf=8Tg?ZUx;@?1Av2xXztFi?_VxO?d3%kKydO zvp9P6DDJ)Y>CSWFVi@wTDhPmYQ#Bz_>nZ#HB`r|b;JU8c4W?zI? z|C3Kbv)L$KpWtny7SiJNbZCJAzdtiQtNJBm6x=%o5R^a;)V}w7zZau`T5>e2B}eDe zY~fFkWw#0;o*^gbOn4e7Q*~&mAe!ALgPeR^VS3xF)q<;r{<&miVa0m?vnZSS>(`I~)jM6fbO~>K z)9+w@eqI)_Tw7bi^&2SW0<{52VX4+HqG&E|=X zb%NV2NUI9nx&tawH%P3fA2=c){yl(yn`S8)!9W~oC`MU91-NwgqV z*MnC!FtZi2tv!~}Q2b*Wt-@*?d`EqgI+}{=UlSUmGfofgHL=7)A>m5nQTsJ(lbDb` zjokv9+*uWYrjkbwOM>G2=J!Le&?DDae8F!>#wQrQJ=?BuJpWw$bA|BoT5q@}q>x#u zWSM+PaQX5T{Pyp>5syCls8cPONapLl_UrMCXFRh|WHvA;3>GDmfKsA2%Y2hzM_Cj| z!(Cm$m8)0r)S0umdi5G6S}ojq%V8Wjd;pUZrVd)DmYg>hcAwC@<&@gQ5BTc?q{dJ} z{CCVu&yJqZ?m@E|NwOid(+@VXEd4A%{BVZsnf&hcg{%?roM_C_+>)S8ZK)|C!^_Ed z+gei{BN-<2b7IMJSk!T?K4or5`^oT&G5uM}wBu0E4HI-s;Bm8HZZ2_1ECjVjlZ5KX zRt*>yOk(GSilj~65y=R} znSnE+&;?!1_E)Z6!|%N5O?dwYKA>74d$|9;`|;KP`n6~@nx4syV;EXZnYPE*`PZ8f zPUgSoJ}sVD>*pqg{A}KMv>c#Cq|vOVVpvI>|9)nA_FWh^XtLS%0D&^pll#_%NZtm4 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 A=l}o! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5d9db337cf0e7408bf2b49e1d18c6241ac595f1e GIT binary patch literal 49824 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 ^VdLaKfuBBySF-K$2^? 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�&506V^3XxAgnc z2t6531>!(m)OxQ3C$>4Yg4?SZin{ge1Of`5c6QhSX%8@F;D1%@Z`V^F$w5+})x;XK zcMm_on~d#o!o}a7={#d7h_MeD_lYy|8Vo|=S z42V2OS_Wh>vB0ykzOaxsvXuL7^Hdr;vp|Gs)x9PD7a8rz@CNY!`&+(Lj^WK)6kqB_ z{)pF`Tctg>?j7~siZAksNrYNK2urS3pXuFSzg_GPM>o+0N;2Xt=N$vFKYMUJD9;~- zVmsIMelgN#-~PH=IBuhbf#{fG7np!o;rnU;%uDbEDy^O35r}-B`PD>3TN>vFv~Sv*x5Cfx7*iO zHj!UFPh5(;EsMV{Hil>%rgb7S52BDWU!F+mRgDR#@FdhL zNMdZ<{eA%Xs}$ypMJFj9JA-HeJ;o==0&?s~Q@=y55M7S8(M&<)i7aERHubv+iTMOgP=&<;)^~UJ-SfX_1w&Adi|~46 zABwN=M1FO4x|nj>V)1TQ0WUXL2f{3|qulhv=;4cu0`IFt4NI3A!vSP2DNj+?IXo>N z)?a3eVTF5JNj3H*0^6zmW^N46$B})&kB@a3XJ zfdM7gH(Fi-$s_vN&nb<=XjHnJg#y=<0#rOi)g8!@OKVoC7Kb3rX^7w8jT41 zekB2^$CKBkKHSgca)*kt=Ha#i?08w=VLj!P)7KbadB~B2qQ~ekF5-pHWyTQwu0rjC zu-xVffG-aRG0a08!dQSeV~40n8QNiH=c?s_G8&_gXq=`X_Jk9P4QS&W;T%~J|4rPxgGVTl^1tjdeZE|1$Aof3*-tqp(|aP;p~8w)!5*L2m?Bg+k|i{!6Uc+E&8 z22qjT@M^1zA;pgt_`qEig>piN@$Tt`5a^vSV(}b;%VTX#s&dY#+*3t(v2QyZN|$ft zHsI#X$2cK$;M$qF=FPCjL`zY4spFvIV_@`2;)a~l^R-x#GIf1To4+4Pti+{!=}yEU z3kC|EL`!qSY}iX%c!R1Xd?dRg?z}7rPU%$@k4`Ic{Id6Gb zpZw2TQ_AAZTtjeYjB)tVx~v`Z=lmjLokr~&ZCl=18MIOCn|Ii(oC8F%Z<^N+dl(8> zGP=?Rs7>LbkOPpKS%x#AjfQ?l<_M6l_-ZwhQEt}e-H;-FxT^^7;Jp8+l1{B5Q6~ci zOo*P>17S8Vs>j}pj{@^)z|>&cL0iTv1)8OQOC%_V9z$LM_1k;f0W7iS!4+V#!*Pz!5VbQI%p{jc}Gh)ZopxN_KklZ?MF&J$$H zLeVe`?EUqAGL1VniGBPyTb(WD;s58?chv?{zrYeak^3aKwV@7buN5_te~ZT2rxJy$ z*{tN0bL?|1d;(9yx~ZpO4KfJu66|BH+O_F#xY`Q!ghh#t0CKoDIby)#{r#VdJv=m+lMK&|mQoWKs~Qe`wBDuxD*h2+%&2pUCmNbedO@Y+9%i+)F0f69aPt@Q&osW6SVGGb~TAu`d;KC*O zEMj5K2LNsrIm#Xo8V}?O+J;FVjQQT_^sW=E4&4e*Idpz?7V;mRx3mW%+42y^+qv#SoV2MT#Nd$DMTj%uS&<4>60iJoPzj|LM&f+JAxPC9 zDV&%c)&%lg4Gf2Y6K$J3SNt>=e>M(xEB;%ZgSHE(8|em_;;g=OTN{_{;vdM)$4p?E zs=Z!kd<=<_erjv42$uo=X?v_wfnK+Sk$%&{3n?soCt*lLWRp0btvl^(4w+2DFD++{ zu3qw3ne?8ipjMR!IyO=f)G`kZXZ|EwJpZrw)VgC$8lE9>;e4VE7(aQxP_B!If z>>WioUo*C?IjbOEYJ2{l;9Hinc>tk>nLNlLQz7ct_!lj{<3Tga=SYSwjSieD^)nux z%hTa!sGgJ`Auo@=!X=M8Ab`jq;_{!CM1!{G9Ua!=P*+zFG+n!Vm-OaK!5)%iT%1n~$Zc=NxJCVA z_Va--_lE+`h9j(FdmZaLhusTr@3Y}Pbj#ndu2ZuHK^cz#_|Ce8)MV-j$e%53nES6U z+T$5V@CL9#6 zKpy+S4e-kfhiP|G0c#0(dHlbZKiR+I2Cd}N*8avWHmz=QYT2=VWx21!sG*&}{u>Rz zLYTHsY>|$|{yGBU2eE*wQsuA=YeMf$=5YW@s|*&Z{pcUdZu!5$cEb8k*jQTLZ3&~yJy}r1jz3&( zOJ7j?DB<%y*1_d>j&yAdu9Z%KQS49Z=4PSvd;CyAv=?M(C^H}a9`m}YPzZ1pD4_ra z9fp)7gs({4I|^WeWi)NNEYqBZVJ=o0*^;`2pBHPZY!s(K%fmh+@)c?K z+|7JuJZ3s$FJ#F>sBc!3^+If*Lh(6KiMk*OfkI|-zxad+1IjTi$Ug-<>Y2V7;r>ti zUnl;fo8YTk7Y748#Zr>p3*(l6M}L_>G(AlRj@0RrMr!0R5Iz4mJw5S$m^|{*5af?6 zL+xQgp!{3ll8JUEYgHG;mjgitSfa8CtUE(PQD1h7@@uOAzIzI*ad@}h3t{ltcYgNF zgi|2BP!xN0*1Z8f0ar2N%G3NS6?2jn-L|Dm#W-7M?v(v>df7;v)Ajxq+r{T7NfDOL zC!!2~=lnaxT=C9!Ev55zrEH!1`#5Fz#QfI(o;J0RO9ebH_WDJTuK=PNrKY&QV#Zcb z=3)wnctjl}fLdy6(*#BK#%qB9pA*{g8R+2+U|0x>f26|vd-C>Dwfw*WUjHQ${9gZQ zY+=As876AOAoe~a2%GQlIZ8>@d{@`^+GT+d;7Kiy3Evgq^^uYvsVM$EZT9abEC?9| zAne&F;va)1WEbOC!gfnZ*0fU3zbKSx*Hb5Fh`4T6K(jB7M}4XOR`dxV`pgf9*#8O6 zCHp_#;R5ka&`iFND_GA+G-@huV!z@G;}Gqj25_+&uxkR-Xud>RmP-~qieR$Ce% z--n3@6cV(5dLmQiAM25?`{E(0Epc*Ww31NWSEzUc| zaKT6J2B(;XA&P&WKEg~(QTiT+MW|K3@Hr1A2+2QK>4@Dedt+;B%Q|FRF#L?SdiCz+ z46qZ}>q*_}o1;W+9=-#w|B)^{fsOT-f8SHgdj_JJp{{mVpBd-2*?W6)OT6DNnfvMy zYj0TdSt6O{95 zl3ZMx#Ex9N2C0w5IS~a2T{2u{tYB0k2*fbXE&%L8bVi64GuDv zb`FCiU)qUyq&R4{(?Uw=CA`tUdK}Ss_M}0Om4NB21}AqmkWqX!qX6Y1UfuXh`3V$~ z^r*iJpzQP2z6nHQCCMt@D{G4ini#yy)e*kEI_d-RXM7S862y}dDoW)~!g7&8SyM>|tKaTx|44Tf z$eo766q3_Kcn6A52i)8SkR4<4XDFEk)b|4U5&!#;*R>~k)22nPE|R4syC4pFaN7Ot zm5PIvw|5!o4sofL1vQ(cX^UI4A!pq;HV}|+xczhSmK8~&^!M@uf#-o-6~|7Ief89l zDz&_g=yM=RKA?$rEWV*G)Uumt`4 z2Km|(KasB}t^cLZND_c-Bnp{am=l=I2-(6&Pqf&A9EHCFEjlbN0Z_`5%*TR;j68pV z7L_wBlxN>nK<57r1US z=|jt?t5+rZbwWsmKtad9hA)XUYq~68(G-kid!Sz9W~RugFR+(ci;_ds(RF2($1qh} zrtn1`9~lv@pFKCJ{wf`LK!14yF^~-&&?q`!h)XJ^v~m$7S7C#Z0Vu~nBZ<1`zw!x2 zz=ZBYloQFlAP8R6V+@cZ?^7RL)WPVRR382_MNoDs5XItxK7Wh<|G(a=1e!u2G^a0S zvMOIvO>xVo`pMo}Mp*ofIRDyZClGsE_wT1~wNh{UI@IeQH~A2h)@KV=pW8*mACPM1 zv9<{}=9x)eHkXCGvCq?*`2Sv1^7!PgkJn8E^y1Q+4ZJhzi!=)yXCxpetYZ(*GT!R$ zzN#5R=oleDhlq%XK=VV!$HpR8w?ou-CRanvpw;!3si9wgepoPH;$Yceh}HB)9UP|D5ig z+kJ1(otZVPRcqDv)oc6RuRL3b(NL4eKqWy1000<@3Nl&%01OlK^9mUe`UZ~Uco_gd z6|$3-)^OL7m!eXXmF5Ep^6{~Aaex2-g_wM88y)Rq0?~(!?^4Rq!D%!8x>UgmC|wE- zB{->o;&39IMnV}K7a8p23S=&(t>98xDzGdbTA1Y}99MMJ?iLdto3rd9N#MtWE`I0x zBf&V1HX}UyJcA5B>G{@b0o3`kR(t6RNU$enn$~U-n_!% zEhe?T@@teBKviUDWzd0Mp{1C+Is&0p3<98PXA(y;L!!?gU~W=x%mnC_0u%xD=_cTM z>jA-<6(Y*;!CiWL)+oM$0D&B-Oq3@(fPgTdNU@Z*1JF(kpffzt5BQ~T%wWu-7UDfF%&U?`RV|I3gJ3+p%4#a#Bc0aevMP$gWY?WBQB{rYvfhLxj42l zG&Z)t#B)C#2z*P3hwr<+;T(vbN8S3bD~F ze~_1TtMT|$^3|hM>s?{gBPHXnJK;K-2xh5jjS0dJVA+0=WP$X_Ndhz(F3r{~cAVdg zxW6q{3a@#LW?v<{I&&{+CnVYLX&)jIQtIZ9)e}} z4nb=)th^BwwU~&nPp>M}&^DOCHsa?DOw=}fUgWI{c74AiQ8=~_`tB7D12#N0jYoKh zEsb9^$TCKNip+{(CoY^SSphaBzKq6FCXr26k9J6bSu0|JT6OeclzI=|jeuCfiAH*q z=tlU4U`T^xT*gxacY$EP$kY}{k}xHyJ&SN;*$h~Z)+@R@GHoUo4JnXsCBhh+?wjSFN z+V|Tr+F8~pjD_owyove4Nk;;E=dV|Mls~hIy`oG~A3EGdyWW44dy#x$e$jnF{zV@m zVUDeWG8GPxf~C%H3~vl+%%71HP$Qy3$7P6?DPfpRIZ{KXy@}?sq;5)S&-97+i4l+r zP%TOMu7F7=u0^A$GR~NunxiPMn5j@&ruYGMIqOUblNN0>Zj^ox(T(s4);C;4QL{Wt zYmIp}&10NJ386gQE!HjSCQqrHM8l*CTL;^dz9sZil3%2dOrW%O>Fb&HX1bib*5uY) zuRO2R`#iPE+!@j17!Tq=+OC+Ol%S+vhB3ufrFd0z8ivI>?-%Ml6pyR9UN0LgEBn^p zSnj*+!;KTB6;{2|Rnci@5lP=?`N|TdQ(vo6#ietuJD~GXhrf!iwm>(qN=}Dc^HvwP z3cu=HOSqbEqL(NVog-}8VA9q+&oUl}qM~h0#hw3p;w5o;noM6`yRe?3wp*ji@MrW7 zDl~9*Bkb$!I-GAo-*~^RG7D*cu0XD=E1VPBpG(sx(dHH54JVN$aU{{_-LU__8_3Je z!~e$1cH?Wm-D_J}+kLz98KLq!D;D>fRh?f8CDqTB8*_3k?9H?-nYNd67xM)(vtO?l zlaF*x@)mqczRcY%s4lY4i`EENjLhU#GtRNj3s*cuzn{9dwE4igBzoo===c*5*b$T; z)bpz>q*^jwvRg8@&qO+wdIUJsIMKN3vZgT2v|wAU88^!g{s0;gnDohYZMbH|)lL;i zZDAm1z*6N^fm0>SZOz&MmFD_pu>))QJ1y0KhgmjR&ip;LA%+5mWv_LNj2mUx^VwuG zR@ss2uJkzcENlI0PrvcJ;XE}yB{-!f%}|Y2?N*JN)Hb4Bm06u^?P{ewBRl6hCpmLk ztzAZ1K3w%){(WJ--RoWD)y|b5HtD<)YnMM?pxTzlk=G6Wfgv9WuG%#maTmVHk{q6rF)-$R3~?VuxZFAa(7)gKg;bwWKl?OjadWM1oog*;oni`@Pn|al z1TU@k<{z3KiV38Oa{C@kWrtDxpb$y&OL{JdEP$Pso7UuXZ}4d7GF9(7|6IO-)Me&s z(($EzBoIjXwveVU$c%TBXp?2rKL|0XI*2`J_gCUC<(DL+Ex1rPJ|ttT1(ZD`K7>Rh zK_qsx6O;-Bc?5lgO{^y5M7#LkaARVcViIC-KL*-3=ZmK7W$9#jI3^zFMYvB*ea*=gd>9qk_x0|NPZ)5PK_lfs*M@Hrb2&qHTxm*(P7l{6+W&*+M?z~X1vdJKls<%dv{nwHOhjc; z_jh@Mg85t(Dm165xnql?QEoN|dWYRpag)N08lJ^I7MnD2vZsUz9QQ^q=QwLPzfvyB ze<5k#fgj0=suy4P-K@g~AEG;4Q+RWJqkL;tmuR%ETcq9i!Q3a| zcTR>>xs=`ZU5nG3H;dcfV!quq6t;G|{fwfKob9me+_~BDCVeEG3IzXF9pb0tuW&fy zbY)p?ifL5*e7s_vH0iITt4x@Q>5Q^IXFANm%HrSTx$*7e{`{9WZJMoPZ5VptIv#o? zx=u9#`ssR}Ei0X$-ph4b2TsXfSLVByQpmY;pkomeiI&A-fFq$eeZ`%1jZ0yUp~EFn5Q5LS`%$}z8`x`)%TUTdQIMtDOt9H2hO!kcVYwvNsFR1d@J=zqZ5a%c3Gm7j>2vnpZ%Awul14Yhm1R2MUQ`MVe}2>xUAb3o?o6YP%w$&qVU=s03gKq^MSD` zoP%Oblvq0*0}lgL6(O**BZq~Rvn7PX$I%5!4FHIU`?y$u-#|R5EFm^_PNFoIojo*E zc2=S^`n;+@RTpW9t(}6O8${brO$Y4v1}tbrBQ8cI;v)nV;0W=spz?8aaB>&&5vBP{ zt`PM5pVyo;RDY3pyb-05{G*V{KvjcE+Sv_4CCC9{2LplJRQ!S*f_#EN3juy>FdG#Y zkc*cS$ju3a!d{>dFHi`?NA>p~4OF3zTjNISqpiiPSwjLfXLY$o5-rgME+#Jqs zHk=?qK|xL+7bh1NJCuUm-Pg&(!iU|-o%Wv+WFYQfH#-*(J7*`VKN2l0ojpB7X`sgb zV-$`q{}Suu{ZUyFqI6xdBP9E-1TF}2} zU2L5_oZW4m{|nLo`uRU7fO=O|^tQXWw+JBD5-9roVZ~pvmutUlJVs;Udc7s@WIJ@aMJ3IVyqBZ_GUMenr4j>hi zss-52>5m7P|LFUh1tMeN0THEvPC6eu5X27T)dBGd@p20R`B{O2LO|d@NL8J!?5utN zCsHmhAzqLW5cD5Np+RS5;bHN=CAI0EE z0>}-OX8}ziE)csQFApEPpoIk=J6I6J%MY;t@>m0T|0zw~%?_H<77qVj>L07Dpc1(u zKnpMzzXiL1H8+r*2Lj|}7ZBhPWas7Pvf$?9;|BskkUv@TS0D;0*ttVz+4t`})P}hJ z{msFS>aW-nvH<_dDN!2mpLl>+(fs||?mzLze@pY<{NA<@sL=mKl>9@xyR)^2w}l%- z(gy0g|3(~g{#WL^TX_AijR#wTxCI43V0L~!umw905W>%HDPU>IZoy~C#|5#5Dbl`|9Uw8Ar1dO9}d{r$qVB4@50N)!p#EOZ6I#$qBPcS z&W=+pTSN2eOI8*&w(*Kukvx2zU{ku5$_gemC0@eSE`u?|3 z_unfa|36F<=bt9}k8;5If2|b%c$WExFXjHs&R=~-=s&9o|J^u*HXb2pMf_Kd{J&Mr z{?_mO|FkOpiJQL${_oJlKVLy1%Ac?Qf=AGYe*r9r6Es`hpzuj8CXO5c;JH_nk<{_Y zJJ0uxhdg;MP6s~g-I<=h(f52{yU4y?0*5=237b2FvWKTosi6O2+@_?B4r9Z`Q#Pkf zq1vGWz9OcM8s&RM)*HD+Z7x3yv$_j8vl6+;Hmz7Zsi_b2fBwZG)Z-n*+h*2v-tGP1 zFQ%pH`LN(td-Cm}VDoji{IX~_3i<$$uXrX-iE}6YF>WRFor(SsFunmE8D%c+r41P{ z0n^I^;DIqWL%0I~SZY?ZUKbHXf=HOfs@QP>Kmpb-|31k;NTFhB?v0KoIiYAYh# zkyM>fRuu&iN{C`G!K5R^T4ZU?$jNHrWab`{Q3K%*Q@I-TZ;MBbT>uA^9G z&54@?3LhZNIkZH_0IGyoFJ8mAACGtvt``U8Rkv|jCcaQ>u{lOD=Xrp3t**AAJvT1u z;08vln+c2$g2b|Qh$T^lzEwC4w_+%5k{y$jI-nQ9FS`VprObDeS`*|D>h}?TQV*kK zTTug#UX_y4?6Oe;6fdsH(;!U(%3-87EX_-fPt+*l`tF5TB&F;qRbCJw_Yr;UhzuSt zTbvL(M*#H$l%XeO5p_S*S|Og}9nsjRZN!a&ozRr$7;yp;#YxU2OPYNYjbBbQyEN6S z?^agD_ef~Y2%*f%;#45uumu>U_R*T4*(bp6u?iQ}U}Qx% zNC;#B_V4DYE6ONRBwXQ#fE0?R@`~|RR^d_}F{a{j=`Dgn!^acƃYdgS9R;8Fe7 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 literal 0 HcmV?d00001 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..a44cc7f9 --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + \ 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..bf50bfc6 --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ 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..3bdf5dc2 --- /dev/null +++ b/app/src/main/res/layout/dialog_detect_abusive_content_success.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 543e889d..1e815895 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,6 +11,7 @@ #F2F2F2 #D41A1A + #E14444 #FFD9D9D9 #F4F3F3 From 9882be23213b2e5a51be4488d089d2939c4694f5 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 2 Feb 2026 11:38:41 +0900 Subject: [PATCH 13/55] =?UTF-8?q?style(letter):=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD=20-=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EC=86=8D=EC=84=B1=EC=9D=84=20=EA=B0=80?= =?UTF-8?q?=EC=A7=84=20style=20=EC=82=AD=EC=A0=9C=20-=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=EC=9D=84=20=EA=B0=80?= =?UTF-8?q?=EC=A7=84=20drawable=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/dialog_delete_friend.xml | 2 +- app/src/main/res/layout/dialog_letter_send.xml | 2 +- app/src/main/res/layout/dialog_square_add_friend.xml | 2 +- app/src/main/res/layout/fragment_letter_write.xml | 10 +++++----- app/src/main/res/values/styles.xml | 4 ---- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/dialog_delete_friend.xml b/app/src/main/res/layout/dialog_delete_friend.xml index 876f99c2..6e6a02b7 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 @@ + android:background="@drawable/bg_round_and_white_dialog"> diff --git a/app/src/main/res/layout/fragment_letter_write.xml b/app/src/main/res/layout/fragment_letter_write.xml index 8543ea95..cb97411a 100644 --- a/app/src/main/res/layout/fragment_letter_write.xml +++ b/app/src/main/res/layout/fragment_letter_write.xml @@ -42,7 +42,7 @@ app:strokeColor="@color/selector_letter_paper_color_stroke" app:strokeWidth="2dp" app:contentPadding="8dp" - app:shapeAppearanceOverlay="@style/CircleShape" + app:shapeAppearanceOverlay="@style/Circle" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/ll_letter_header"> @@ -83,7 +83,7 @@ app:strokeColor="@color/selector_letter_paper_color_stroke" app:strokeWidth="2dp" app:contentPadding="8dp" - app:shapeAppearanceOverlay="@style/CircleShape" + app:shapeAppearanceOverlay="@style/Circle" app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_pink" app:layout_constraintStart_toEndOf="@id/cv_letter_color_pink" app:layout_constraintTop_toTopOf="@id/cv_letter_color_pink"> @@ -104,7 +104,7 @@ app:strokeColor="@color/selector_letter_paper_color_stroke" app:strokeWidth="2dp" app:contentPadding="8dp" - app:shapeAppearanceOverlay="@style/CircleShape" + app:shapeAppearanceOverlay="@style/Circle" app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_leaf" app:layout_constraintStart_toEndOf="@id/cv_letter_color_leaf" app:layout_constraintTop_toTopOf="@id/cv_letter_color_leaf"> @@ -125,7 +125,7 @@ app:strokeColor="@color/selector_letter_paper_color_stroke" app:strokeWidth="2dp" app:contentPadding="8dp" - app:shapeAppearanceOverlay="@style/CircleShape" + app:shapeAppearanceOverlay="@style/Circle" app:layout_constraintBottom_toBottomOf="@id/cv_letter_color_mint" app:layout_constraintStart_toEndOf="@id/cv_letter_color_mint" app:layout_constraintTop_toTopOf="@id/cv_letter_color_mint"> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e52e2609..6052d9b8 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -185,8 +185,4 @@ @color/cos_black - - \ No newline at end of file From 846a93debdca7662800583b08c7aa74d3e399c60 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 2 Feb 2026 14:42:33 +0900 Subject: [PATCH 14/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=98=EA=B8=B0=20API=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20-=20AI=20=EA=B2=80=EC=82=AC=20=EC=84=B1=EA=B3=B5/=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20-=20AI=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EC=84=9C=EB=B2=84=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=84=B1=EA=B3=B5/=EC=8B=A4=ED=8C=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/letter/LetterBackgroundColor.kt | 2 +- .../view/DetectAbusiveContentFailureDialog.kt | 55 +++++++++++ .../view/DetectAbusiveContentLoadingDialog.kt | 29 ++++++ .../view/DetectAbusiveContentSuccessDialog.kt | 41 +++++++++ .../app/ui/square/view/LetterSendDialog.kt | 92 ++++++++++++++++++- .../app/ui/square/view/LetterWriteFragment.kt | 33 +++++-- 6 files changed, 240 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentFailureDialog.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentLoadingDialog.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt index 76e843c0..41c3ce3c 100644 --- a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt @@ -1,6 +1,6 @@ package com.egobook.app.ui.square.model.letter -enum class LetterBackgroundColor(val text: String) { +enum class LetterBackgroundColor(val value: String) { BEIGE("BEIGE"), PINK("PINK"), LEAF("LEAF"), 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..e763325c --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt @@ -0,0 +1,41 @@ +package com.egobook.app.ui.square.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.navigation.fragment.findNavController +import com.egobook.app.R +import com.egobook.app.databinding.DialogDetectAbusiveContentSuccessBinding +import com.egobook.app.removeScreenBlur + +class DetectAbusiveContentSuccessDialog: 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) + initListeners() + } + + private fun initListeners() = with(binding) { + btnDetectAbusiveSuccess.setOnClickListener { + Toast.makeText(context, "잉크 1개를 획득하였습니다.", Toast.LENGTH_SHORT).show() + 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/LetterSendDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterSendDialog.kt index 2792a519..df32eb26 100644 --- 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 @@ -6,15 +6,29 @@ 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.ui.square.model.letter.LetterBackgroundColor +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 type: LetterMode, private val friendInfo: FriendModel? = null): DialogFragment(R.layout.dialog_letter_send) { +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 { @@ -27,10 +41,11 @@ class LetterSendDialog(private val type: LetterMode, private val friendInfo: Fri binding = DialogLetterSendBinding.bind(view) initViews() initListeners() + initObservers() } private fun initViews() = with(binding) { - when(type) { + when(mode) { LetterMode.FRIEND -> { tvLetterSendTitle.text = "${friendInfo?.name}에게\n편지를 보낼까요?" tvLetterSendDescription.text = "상대에게 내 이름이 보여요" @@ -48,11 +63,80 @@ class LetterSendDialog(private val type: LetterMode, private val friendInfo: Fri dismiss() } btnLetterSendApply.setOnClickListener { - removeScreenBlur() - dismiss() + 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().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" } 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 index 2fed824e..0ccb9e5f 100644 --- 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 @@ -24,6 +24,7 @@ import com.egobook.app.databinding.LayoutPopupFriendListBinding 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.LetterMode +import com.egobook.app.ui.square.model.letter.LetterBackgroundColor import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.util.UiState import com.google.android.material.card.MaterialCardView @@ -40,6 +41,7 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { private val viewModel: LetterViewModel by activityViewModels() private lateinit var friendList: List + private var letterColor: LetterBackgroundColor = LetterBackgroundColor.BEIGE override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -70,11 +72,26 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { clickedView.isSelected = true (clickedView as MaterialCardView).getChildAt(0).isVisible = true when(clickedView.id) { - R.id.cv_letter_color_beige -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null)} - R.id.cv_letter_color_pink -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null)} - R.id.cv_letter_color_leaf -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null)} - R.id.cv_letter_color_mint -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null)} - R.id.cv_letter_color_lavender -> { cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null)} + R.id.cv_letter_color_beige -> { + cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null) + this@LetterWriteFragment.letterColor = LetterBackgroundColor.BEIGE + } + R.id.cv_letter_color_pink -> { + cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null) + this@LetterWriteFragment.letterColor = LetterBackgroundColor.PINK + } + R.id.cv_letter_color_leaf -> { + cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null) + this@LetterWriteFragment.letterColor = LetterBackgroundColor.LEAF + } + R.id.cv_letter_color_mint -> { + cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null) + this@LetterWriteFragment.letterColor = LetterBackgroundColor.MINT + } + R.id.cv_letter_color_lavender -> { + cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null) + this@LetterWriteFragment.letterColor = LetterBackgroundColor.LAVENDER + } } } } @@ -102,10 +119,12 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { } btnLetterSendAnonymous.setOnClickListener { applyScreenBlur(BlurLevel.BASE) - val dialog = LetterSendDialog(LetterMode.RANDOM, friendInfo = null).apply { + 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 또다른 고북이" } } @@ -131,7 +150,7 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { tvLetterWriteReceiver.text = "To ${friendInfo.name}" tvLetterWriteSender.text = "From 로그인한 유저" // TODO: 나중에 유저 정보 받아오기 popupWindow.dismiss() - val dialog = LetterSendDialog(type = LetterMode.FRIEND, friendInfo = friendInfo).apply { isCancelable = false } + 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) } From a4d3130345bab6b5abb5ee54dc3e9123ce5217ec Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 3 Feb 2026 13:47:00 +0900 Subject: [PATCH 15/55] =?UTF-8?q?feat(letter):=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=ED=95=B4=EC=95=BC=20=ED=95=A0=20'=EB=8F=84?= =?UTF-8?q?=EC=B0=A9=20=ED=8E=B8=EC=A7=80'=201=EA=B1=B4=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EA=B8=B0=20API=20=EC=97=B0=EA=B2=B0=20-=20mo?= =?UTF-8?q?ck=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=8F=84=20=EA=B0=99=EC=9D=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20-=20=ED=8C=9D=EC=97=85=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=9D=84=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 5 ++ .../letter/ArrivedPendingLetterResponse.kt | 43 ++++++++++ .../data/repository/LetterRepositoryImpl.kt | 58 +++++++++++-- .../data/repository/QuestionRepositoryImpl.kt | 5 +- .../square/letter/ArrivedPendingLetter.kt | 15 ++++ .../app/domain/repository/LetterRepository.kt | 2 + .../letter/GetArrivedPendingLetterUseCase.kt | 9 +++ .../model/letter/ArrivedPendingLetterModel.kt | 35 ++++++++ .../view/ArrivedPendingLetterPopupDialog.kt | 73 +++++++++++++++++ .../app/ui/square/view/LetterReplyFragment.kt | 1 + .../app/ui/square/view/SquareFragment.kt | 28 ++++++- .../ui/square/viewmodel/LetterViewModel.kt | 19 ++++- .../dialog_arrived_pending_letter_popup.xml | 81 +++++++++++++++++++ .../main/res/layout/dialog_letter_arrived.xml | 77 ------------------ 14 files changed, 359 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/ArrivedPendingLetterResponse.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/ArrivedPendingLetter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/GetArrivedPendingLetterUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterPopupDialog.kt create mode 100644 app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml delete mode 100644 app/src/main/res/layout/dialog_letter_arrived.xml 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 index 9f2ff811..ba034fb4 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -1,12 +1,17 @@ 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.SendLetterRequest import com.egobook.app.data.model.square.letter.SendLetterResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST interface LetterApiService { @POST("/plaza/letters") suspend fun sendLetter(@Body request: SendLetterRequest): ApiResponse + + @GET("/plaza/letters/inbox/next") + suspend fun fetchArrivedPendingLetter(): ApiResponse } \ No newline at end of file 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..f50d6ebd --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/square/letter/ArrivedPendingLetterResponse.kt @@ -0,0 +1,43 @@ +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.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 +) + +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 +) 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 index 79a854bc..19b0dc94 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -6,6 +6,10 @@ import com.egobook.app.data.model.square.letter.DetectAbusiveContentRequest import com.egobook.app.data.model.square.letter.toData import com.egobook.app.data.model.square.letter.toDomain 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.LetterMode +import com.egobook.app.domain.model.square.letter.LetterStatus import com.egobook.app.domain.model.square.letter.SendLetter import com.egobook.app.domain.repository.LetterRepository import javax.inject.Inject @@ -13,10 +17,10 @@ import javax.inject.Inject class LetterRepositoryImpl @Inject constructor( private val letterApiService: LetterApiService, private val aiApiService: AIApiService -): LetterRepository { +) : LetterRepository { override suspend fun sendLetter(letter: SendLetter): Result = try { val response = letterApiService.sendLetter(request = letter.toData()) - if(response.status == 200) { + if (response.status == 200) { Result.success(Unit) // 나중에 구현하면서 응답 필요할 때 변경 } else { Result.failure(Exception("Error: ${response.status}")) @@ -26,12 +30,50 @@ class LetterRepositoryImpl @Inject constructor( } 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 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 = """ + 안녕하세요! 요즘 날씨가 부쩍 추워졌는데 잘 지내고 계신가요? + 오늘 우연히 당신의 이야기를 듣고 문득 위로의 말을 전하고 싶어 펜을 들었습니다. + 누구나 가끔은 마음이 무겁고 모든 게 버겁게 느껴지는 날이 있잖아요. + 그럴 때일수록 스스로를 너무 다그치지 말고, 따뜻한 차 한 잔 마시며 쉬어갔으면 좋겠어요. + 당신은 충분히 잘해내고 있고, 존재만으로도 소중한 사람이라는 걸 잊지 마세요. + 내일은 오늘보다 조금 더 웃을 수 있는 여유가 생기길 진심으로 응원하겠습니다! + 답장 기다릴게요.""".trimIndent(), + arrivedAt = "2026-02-03T10:35:00+09:00", + replyDeadlineAt = "2026-02-04T21:40:00+09:00" + ) + ) + Result.success(mockData) } catch (e: Exception) { Result.failure(e) } 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/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..541fd811 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/square/letter/ArrivedPendingLetter.kt @@ -0,0 +1,15 @@ +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 arrivedAt: String, + val replyDeadlineAt: String +) \ 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 index 2070ccf9..b14e8b86 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -1,9 +1,11 @@ package com.egobook.app.domain.repository 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.SendLetter interface LetterRepository { suspend fun sendLetter(letter: SendLetter): Result suspend fun detectAbusiveContent(text: String): Result + suspend fun fetchArrivedPendingLetter(): Result } \ 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/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..6dba140a --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt @@ -0,0 +1,35 @@ +package com.egobook.app.ui.square.model.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 + +data class ArrivedPendingLetterModel( + val letter: ArrivedPendingLetterItemModel? = null +) + +data class ArrivedPendingLetterItemModel( + val letterId: Long, + val status: LetterStatus, + val mode: LetterMode, + val fromLabel: String, + val content: String, + val arrivedAt: String, + val replyDeadlineAt: String +) + +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 + ) 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..32bfbb32 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterPopupDialog.kt @@ -0,0 +1,73 @@ +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.BlurLevel +import com.egobook.app.R +import com.egobook.app.applyScreenBlur +import com.egobook.app.databinding.DialogArrivedPendingLetterPopupBinding +import com.egobook.app.ui.square.model.letter.ArrivedPendingLetterItemModel +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 + + 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() + } + + private fun initViews() = with(binding) { + tvArrivedPendingLetterTitle.text = "낯선 고북이의\n편지가 도착했어요" // TODO: 친구인지 익명인지 구분 필요 + tvArrivedPendingLetterReplyDeadline.text = getRemainingTime(replyDeadlineAt = letterInfo.replyDeadlineAt) + } + + private fun initListeners() = with(binding) { + btnArrivedPendingLetterReplyLater.setOnClickListener { + // TODO: 로직 작성하기 (상태 변경, API 연동 등) + } + btnArrivedPendingLetterConfirm.setOnClickListener { + val dialog = ArrivedPendingLetterDialog(letterInfo = letterInfo).apply { isCancelable = false } + dialog.show(parentFragmentManager, ArrivedPendingLetterDialog.TAG) + applyScreenBlur(BlurLevel.BASE) + 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/LetterReplyFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterReplyFragment.kt index 08d76654..47c824cb 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 @@ -8,6 +8,7 @@ import com.egobook.app.databinding.FragmentLetterReplyBinding class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { private lateinit var binding: FragmentLetterReplyBinding + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLetterReplyBinding.bind(view) 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 1154f84b..b114b702 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,18 @@ 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.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,6 +34,7 @@ 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 // 오늘의 질문 답변 제출할 때 필요한 변수 @@ -54,6 +58,7 @@ class SquareFragment : Fragment(R.layout.fragment_square) { private fun fetchData() { questionViewModel.getTodayQuestion() questionViewModel.getTodayFriendsReplies(size = 3) + letterViewModel.getArrivedPendingLetter() } private fun initViews() = with(binding) { @@ -128,7 +133,9 @@ class SquareFragment : Fragment(R.layout.fragment_square) { /** * 1. PopupWindow 생성자 * 두번째, 세번째 인자: 팝업창의 너비와 높이 - * 네번째 인자: true로 설정하면 팝업이 포커스를 가진다. 팝업이 포커스를 가지게 되면 팝업 바깥 영역을 터치했을 때 팝업이 자동으로 닫힌다. + * 네번째 인자: true로 설정하면 팝업이 포커스를 가진다. + * 팝업이 포커스를 가지게 되면 팝업 바깥 영역을 터치했을 때 팝업이 자동으로 닫힌다. + * 그리고 버튼을 다시 클릭해도 팝업창이 그대로 열린게 유지되는게 아니라 닫힌다. * 2. 뷰가 화면에 실제로 그려지기 전에 높이가 얼마인 지 미리 계산하는 함수이다. * 뷰의 크기는 화면에 그려진 후에야 알 수 있다. 하지만 팝업을 띄우기 전에 팝업의 높이를 알아야 적절한 위치에 띄울 수 있기 때문에, 먼저 계산해야 한다. * UNSPECIFIED 는 부모 뷰의 제약 없이 뷰가 원하는 만큼의 크기를 계산하라는 모드이다. @@ -252,6 +259,23 @@ 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) + } + } + } + } + } } } } 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 index e58b3baa..f1f2163c 100644 --- 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 @@ -4,10 +4,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.GetFriendListUseCase import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase +import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase 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.SendLetterModel import com.egobook.app.ui.square.model.letter.toDomain import com.egobook.app.ui.square.model.letter.toPresentation @@ -24,7 +26,8 @@ import javax.inject.Inject class LetterViewModel @Inject constructor( private val getFriendListUseCase: GetFriendListUseCase, private val sendLetterUseCase: SendLetterUseCase, - private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase + private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase, + private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -68,4 +71,18 @@ class LetterViewModel @Inject constructor( } } } + + 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) + } + } + } } \ 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..3d1058b6 --- /dev/null +++ b/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + \ 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 From 7ceeb86f28f14e4d6cde6cf6368fe22873e29c13 Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 3 Feb 2026 16:07:32 +0900 Subject: [PATCH 16/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=ED=8C=9D=EC=97=85=20=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=B0=8F=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20-=20=ED=8C=9D=EC=97=85=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=E2=86=92=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EB=82=B4=EC=9A=A9=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=E2=86=92=20=EB=8B=B5=EC=9E=A5=20=ED=8F=AC?= =?UTF-8?q?=EA=B8=B0=20=ED=8C=9D=EC=97=85=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20or=20=ED=8E=B8=EC=A7=80=20=EB=8B=B5?= =?UTF-8?q?=EC=9E=A5=20=ED=94=84=EB=9E=98=EA=B7=B8=EB=A8=BC=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20-=20mock=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=ED=8E=B8=EC=A7=80=EC=A7=80=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../letter/ArrivedPendingLetterResponse.kt | 8 +- .../data/repository/LetterRepositoryImpl.kt | 15 ++-- .../square/letter/ArrivedPendingLetter.kt | 3 + .../model/letter/ArrivedPendingLetterModel.kt | 4 +- .../square/view/ArrivedPendingLetterDialog.kt | 81 +++++++++++++++++++ .../view/GiveUpReplyLetterPopupDialog.kt | 31 +++++++ ....xml => dialog_arrived_pending_letter.xml} | 56 +++++++------ ...ly.xml => dialog_give_up_reply_letter.xml} | 31 ++++--- .../main/res/navigation/bottom_navigation.xml | 3 + 9 files changed, 181 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt rename app/src/main/res/layout/{dialog_letter.xml => dialog_arrived_pending_letter.xml} (62%) rename app/src/main/res/layout/{dialog_discard_reply.xml => dialog_give_up_reply_letter.xml} (61%) 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 index f50d6ebd..03ac8cc4 100644 --- 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 @@ -4,6 +4,7 @@ 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.ui.square.model.letter.LetterBackgroundColor import com.google.gson.annotations.SerializedName data class ArrivedPendingLetterResponse( @@ -25,7 +26,9 @@ data class ArrivedPendingLetterItemResponse( @SerializedName("arrivedAt") val arrivedAt: String, @SerializedName("replyDeadlineAt") - val replyDeadlineAt: String + val replyDeadlineAt: String, + @SerializedName("letterColor") + val letterColor: LetterBackgroundColor // TODO: 백엔드한테 필드 넣어달라고 하기 ) fun ArrivedPendingLetterResponse.toDomain(): ArrivedPendingLetter = ArrivedPendingLetter( @@ -39,5 +42,6 @@ fun ArrivedPendingLetterItemResponse.toDomain(): ArrivedPendingLetterItem = Arri fromLabel = fromLabel, content = content, arrivedAt = arrivedAt, - replyDeadlineAt = replyDeadlineAt + replyDeadlineAt = replyDeadlineAt, + letterColor = letterColor ) 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 index 19b0dc94..e196231f 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -12,6 +12,7 @@ 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.SendLetter import com.egobook.app.domain.repository.LetterRepository +import com.egobook.app.ui.square.model.letter.LetterBackgroundColor import javax.inject.Inject class LetterRepositoryImpl @Inject constructor( @@ -61,16 +62,10 @@ class LetterRepositoryImpl @Inject constructor( status = LetterStatus.ARRIVED, mode = LetterMode.RANDOM, fromLabel = "익명", - content = """ - 안녕하세요! 요즘 날씨가 부쩍 추워졌는데 잘 지내고 계신가요? - 오늘 우연히 당신의 이야기를 듣고 문득 위로의 말을 전하고 싶어 펜을 들었습니다. - 누구나 가끔은 마음이 무겁고 모든 게 버겁게 느껴지는 날이 있잖아요. - 그럴 때일수록 스스로를 너무 다그치지 말고, 따뜻한 차 한 잔 마시며 쉬어갔으면 좋겠어요. - 당신은 충분히 잘해내고 있고, 존재만으로도 소중한 사람이라는 걸 잊지 마세요. - 내일은 오늘보다 조금 더 웃을 수 있는 여유가 생기길 진심으로 응원하겠습니다! - 답장 기다릴게요.""".trimIndent(), - arrivedAt = "2026-02-03T10:35:00+09:00", - replyDeadlineAt = "2026-02-04T21:40:00+09:00" + content = "안녕하세요! 요즘 날씨가 부쩍 추워졌는데 잘 지내고 계신가요? 오늘 우연히 당신의 이야기를 듣고 문득 위로의 말을 전하고 싶어 펜을 들었습니다. 누구나 가끔은 마음이 무겁고 모든 게 버겁게 느껴지는 날이 있잖아요. 그럴 때일수록 스스로를 너무 다그치지 말고, 따뜻한 차 한 잔 마시며 쉬어갔으면 좋겠어요. 당신은 충분히 잘해내고 있고, 존재만으로도 소중한 사람이라는 걸 잊지 마세요. 내일은 오늘보다 조금 더 웃을 수 있는 여유가 생기길 진심으로 응원하겠습니다! 답장 기다릴게요.", + arrivedAt = "2026-02-02T10:35:00+09:00", + replyDeadlineAt = "2026-02-04T21:40:00+09:00", + letterColor = LetterBackgroundColor.BEIGE ) ) Result.success(mockData) 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 index 541fd811..0a4fbc23 100644 --- 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 @@ -1,5 +1,7 @@ package com.egobook.app.domain.model.square.letter +import com.egobook.app.ui.square.model.letter.LetterBackgroundColor + data class ArrivedPendingLetter( val letter: ArrivedPendingLetterItem? = null ) @@ -10,6 +12,7 @@ data class ArrivedPendingLetterItem( 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/ui/square/model/letter/ArrivedPendingLetterModel.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/ArrivedPendingLetterModel.kt index 6dba140a..b723614e 100644 --- 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 @@ -15,6 +15,7 @@ data class ArrivedPendingLetterItemModel( val mode: LetterMode, val fromLabel: String, val content: String, + val letterColor: LetterBackgroundColor, val arrivedAt: String, val replyDeadlineAt: String ) @@ -31,5 +32,6 @@ fun ArrivedPendingLetterItem.toPresentation(): ArrivedPendingLetterItemModel = fromLabel = fromLabel, content = content, arrivedAt = arrivedAt, - replyDeadlineAt = replyDeadlineAt + replyDeadlineAt = replyDeadlineAt, + letterColor = letterColor ) 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..524a8a08 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt @@ -0,0 +1,81 @@ +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.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.ui.square.model.letter.LetterBackgroundColor +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 + + 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() + } + + 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.BEIGE -> resources.getColorStateList(R.color.letter_bg_beige, null) + LetterBackgroundColor.PINK -> resources.getColorStateList(R.color.letter_bg_pink, null) + LetterBackgroundColor.LEAF -> resources.getColorStateList(R.color.letter_bg_leaf, null) + LetterBackgroundColor.MINT -> resources.getColorStateList(R.color.letter_bg_mint, null) + LetterBackgroundColor.LAVENDER -> resources.getColorStateList(R.color.letter_bg_lavender, null) + } + } + + private fun initListeners() = with(binding) { + ivArrivedPendingLetterCancel.setOnClickListener { + + } + ivArrivedPendingLetterReport.setOnClickListener { + + } + btnArrivedPendingLetterGiveUp.setOnClickListener { + dismiss() + val dialog = GiveUpReplyLetterPopupDialog().apply { isCancelable = false } + dialog.show(parentFragmentManager, GiveUpReplyLetterPopupDialog.TAG) + } + btnArrivedPendingLetterReply.setOnClickListener { + dismiss() + removeScreenBlur() + findNavController().navigate(R.id.action_menu_square_to_letterReplyFragment) + } + } + + /** + * "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/GiveUpReplyLetterPopupDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt new file mode 100644 index 00000000..0bb102a6 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt @@ -0,0 +1,31 @@ +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.DialogGiveUpReplyLetterBinding + +class GiveUpReplyLetterPopupDialog: DialogFragment(R.layout.dialog_give_up_reply_letter) { + private lateinit var binding: DialogGiveUpReplyLetterBinding + + 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) + } + + companion object { + const val TAG = "GiveUpReplyLetterPopupDialog" + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_letter.xml b/app/src/main/res/layout/dialog_arrived_pending_letter.xml similarity index 62% rename from app/src/main/res/layout/dialog_letter.xml rename to app/src/main/res/layout/dialog_arrived_pending_letter.xml index f9256809..e2ce7341 100644 --- a/app/src/main/res/layout/dialog_letter.xml +++ b/app/src/main/res/layout/dialog_arrived_pending_letter.xml @@ -1,49 +1,50 @@ + app:layout_constraintTop_toBottomOf="@id/iv_arrived_pending_letter_cancel"> + 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_discard_reply.xml b/app/src/main/res/layout/dialog_give_up_reply_letter.xml similarity index 61% 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..e15fae48 100644 --- a/app/src/main/res/layout/dialog_discard_reply.xml +++ b/app/src/main/res/layout/dialog_give_up_reply_letter.xml @@ -5,10 +5,10 @@ android:layout_height="wrap_content" android:paddingVertical="24dp" android:paddingHorizontal="16dp" - android:background="@color/white"> + android:background="@drawable/bg_white_and_radius"> + 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_reply_later" + app:layout_constraintTop_toTopOf="@+id/btn_give_up_reply_letter_reply_later" /> \ 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 b793ce8c..fa74bca5 100644 --- a/app/src/main/res/navigation/bottom_navigation.xml +++ b/app/src/main/res/navigation/bottom_navigation.xml @@ -68,6 +68,9 @@ + From 3a29f2b9c1d2fab62f8546ff456444efbfdeea99 Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 3 Feb 2026 17:32:45 +0900 Subject: [PATCH 17/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20=ED=8E=B8=EC=A7=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20-=20=ED=8E=B8=EC=A7=80=EC=A7=80=20?= =?UTF-8?q?=EC=83=89=EA=B9=94=20=EB=B3=80=EA=B2=BD=20-=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EB=8B=B5=EC=9E=A5=20=EA=B8=B8=EC=9D=B4=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C(360=EC=9E=90)=20=EB=B0=8F=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=A0=9C=EC=96=B4=20-=20?= =?UTF-8?q?=ED=88=B4=ED=8C=81=20=ED=8C=9D=EC=97=85=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/letter/ArrivedPendingLetterModel.kt | 5 +- .../square/view/ArrivedPendingLetterDialog.kt | 3 +- .../app/ui/square/view/LetterReplyFragment.kt | 107 +++++++++++ .../app/ui/square/view/LetterWriteFragment.kt | 2 +- .../main/res/layout/fragment_letter_reply.xml | 168 ++++++++++++------ .../layout/layout_letter_tooltip_popup.xml | 19 ++ .../main/res/navigation/bottom_navigation.xml | 7 +- 7 files changed, 256 insertions(+), 55 deletions(-) create mode 100644 app/src/main/res/layout/layout_letter_tooltip_popup.xml 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 index b723614e..a32aebe5 100644 --- 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 @@ -1,14 +1,17 @@ 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.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, @@ -18,7 +21,7 @@ data class ArrivedPendingLetterItemModel( val letterColor: LetterBackgroundColor, val arrivedAt: String, val replyDeadlineAt: String -) +): Parcelable fun ArrivedPendingLetter.toPresentation(): ArrivedPendingLetterModel = ArrivedPendingLetterModel( letter = letter?.toPresentation() 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 index 524a8a08..2e48e8d3 100644 --- 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 @@ -62,7 +62,8 @@ class ArrivedPendingLetterDialog( btnArrivedPendingLetterReply.setOnClickListener { dismiss() removeScreenBlur() - findNavController().navigate(R.id.action_menu_square_to_letterReplyFragment) + val action = SquareFragmentDirections.actionMenuSquareToLetterReplyFragment(letterItem = letterInfo) + findNavController().navigate(action) } } 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 47c824cb..230bfc3a 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,17 +1,124 @@ 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.navigation.fragment.navArgs import com.egobook.app.R import com.egobook.app.databinding.FragmentLetterReplyBinding +import com.egobook.app.databinding.LayoutLetterTooltipPopupBinding +import com.egobook.app.ui.square.model.letter.LetterBackgroundColor +import com.google.android.material.card.MaterialCardView class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { private lateinit var binding: FragmentLetterReplyBinding + private val letterItem by lazy { + val args: LetterReplyFragmentArgs by navArgs() + args.letterItem + } + private val letterColorList by lazy { + listOf(binding.cvLetterReplyColorBeige, binding.cvLetterReplyColorPink, binding.cvLetterReplyColorLeaf, binding.cvLetterReplyColorMint, binding.cvLetterReplyColorLavender) + } + + private var letterColor: LetterBackgroundColor = LetterBackgroundColor.BEIGE override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentLetterReplyBinding.bind(view) + initViews() + initListeners() + } + + 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) + } + letterColorList.forEach { letterColor -> + letterColor.setOnClickListener { clickedView -> + letterColorList.forEach { card -> + card.isSelected = false + card.getChildAt(0).isVisible = false + } + clickedView.isSelected = true + (clickedView as MaterialCardView).getChildAt(0).isVisible = true + when(clickedView.id) { + R.id.cv_letter_reply_color_beige -> { + cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null) + this@LetterReplyFragment.letterColor = LetterBackgroundColor.BEIGE + } + R.id.cv_letter_reply_color_pink -> { + cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null) + this@LetterReplyFragment.letterColor = LetterBackgroundColor.PINK + } + R.id.cv_letter_reply_color_leaf -> { + cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null) + this@LetterReplyFragment.letterColor = LetterBackgroundColor.LEAF + } + R.id.cv_letter_reply_color_mint -> { + cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null) + this@LetterReplyFragment.letterColor = LetterBackgroundColor.MINT + } + R.id.cv_letter_reply_color_lavender -> { + cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null) + this@LetterReplyFragment.letterColor = LetterBackgroundColor.LAVENDER + } + } + } + } + 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) + } + } + + 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 + ) + } + + companion object { + private const val MAX_LENGTH = 360 + } } \ 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 index 0ccb9e5f..95f40944 100644 --- 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 @@ -21,9 +21,9 @@ 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.LetterMode import com.egobook.app.ui.square.model.letter.LetterBackgroundColor import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.util.UiState diff --git a/app/src/main/res/layout/fragment_letter_reply.xml b/app/src/main/res/layout/fragment_letter_reply.xml index 2106bec5..d01d4d3b 100644 --- a/app/src/main/res/layout/fragment_letter_reply.xml +++ b/app/src/main/res/layout/fragment_letter_reply.xml @@ -55,6 +55,7 @@ app:layout_constraintTop_toTopOf="parent" /> @@ -76,13 +78,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_leaf" + 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_mint" + 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_leaf" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_leaf" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_leaf" > + + - + app:cardBackgroundColor="@color/letter_bg_lavender" + 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_mint" + app:layout_constraintStart_toEndOf="@id/cv_letter_reply_color_mint" + app:layout_constraintTop_toTopOf="@id/cv_letter_reply_color_mint"> + + - + 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/navigation/bottom_navigation.xml b/app/src/main/res/navigation/bottom_navigation.xml index fa74bca5..caf3b68e 100644 --- a/app/src/main/res/navigation/bottom_navigation.xml +++ b/app/src/main/res/navigation/bottom_navigation.xml @@ -167,7 +167,12 @@ android:id="@+id/letterReplyFragment" android:name="com.egobook.app.ui.square.view.LetterReplyFragment" android:label="fragment_letter_reply" - tools:layout="@layout/fragment_letter_reply" /> + tools:layout="@layout/fragment_letter_reply"> + + Date: Wed, 4 Feb 2026 13:59:01 +0900 Subject: [PATCH 18/55] =?UTF-8?q?feat(letter):=20=EC=9C=A0=EB=A3=8C=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80=EC=A7=80=20=EC=84=A0=ED=83=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20=EC=9C=A0=EB=A3=8C=20=ED=8E=B8=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=84=A0=ED=83=9D=20UI,=20=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EC=96=BC=EB=A1=9C=EA=B7=B8,=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=ED=8E=B8=EC=A7=80=20=EC=93=B0?= =?UTF-8?q?=EA=B8=B0,=20=ED=8E=B8=EC=A7=80=20=EB=8B=B5=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20-=20=EC=9C=A0=EC=A0=80=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=95=84=EC=A7=81=20X?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/square/view/LetterReplyFragment.kt | 46 +++---- .../app/ui/square/view/LetterWriteFragment.kt | 44 ++----- .../ui/square/view/PremiumLetterBuyDialog.kt | 60 ++++++++++ app/src/main/res/drawable/img_letter_blue.png | Bin 0 -> 34348 bytes .../main/res/drawable/img_letter_green.png | Bin 0 -> 74408 bytes app/src/main/res/drawable/img_letter_pink.png | Bin 0 -> 34591 bytes .../main/res/drawable/img_letter_purple.png | Bin 0 -> 60891 bytes .../res/layout/dialog_premium_letter_buy.xml | 113 ++++++++++++++++++ .../main/res/layout/fragment_letter_reply.xml | 24 ++-- .../main/res/layout/fragment_letter_write.xml | 32 ++--- 10 files changed, 227 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/ui/square/view/PremiumLetterBuyDialog.kt create mode 100644 app/src/main/res/drawable/img_letter_blue.png create mode 100644 app/src/main/res/drawable/img_letter_green.png create mode 100644 app/src/main/res/drawable/img_letter_pink.png create mode 100644 app/src/main/res/drawable/img_letter_purple.png create mode 100644 app/src/main/res/layout/dialog_premium_letter_buy.xml 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 230bfc3a..3c91b914 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 @@ -9,11 +9,12 @@ import android.widget.PopupWindow import androidx.core.view.isVisible import androidx.fragment.app.Fragment 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.ui.square.model.letter.LetterBackgroundColor -import com.google.android.material.card.MaterialCardView +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { private lateinit var binding: FragmentLetterReplyBinding @@ -21,8 +22,8 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { val args: LetterReplyFragmentArgs by navArgs() args.letterItem } - private val letterColorList by lazy { - listOf(binding.cvLetterReplyColorBeige, binding.cvLetterReplyColorPink, binding.cvLetterReplyColorLeaf, binding.cvLetterReplyColorMint, binding.cvLetterReplyColorLavender) + private val premiumLetterColorList by lazy { + listOf(binding.cvLetterReplyColorPink, binding.cvLetterReplyColorGreen, binding.cvLetterReplyColorBlue, binding.cvLetterReplyColorPurple) } private var letterColor: LetterBackgroundColor = LetterBackgroundColor.BEIGE @@ -46,36 +47,17 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { val chevron = if(tvLetterReplyReceivedContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down ivLetterReplyChevron.setImageResource(chevron) } - letterColorList.forEach { letterColor -> + premiumLetterColorList.forEach { letterColor -> letterColor.setOnClickListener { clickedView -> - letterColorList.forEach { card -> - card.isSelected = false - card.getChildAt(0).isVisible = false - } - clickedView.isSelected = true - (clickedView as MaterialCardView).getChildAt(0).isVisible = true - when(clickedView.id) { - R.id.cv_letter_reply_color_beige -> { - cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null) - this@LetterReplyFragment.letterColor = LetterBackgroundColor.BEIGE - } - R.id.cv_letter_reply_color_pink -> { - cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null) - this@LetterReplyFragment.letterColor = LetterBackgroundColor.PINK - } - R.id.cv_letter_reply_color_leaf -> { - cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null) - this@LetterReplyFragment.letterColor = LetterBackgroundColor.LEAF - } - R.id.cv_letter_reply_color_mint -> { - cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null) - this@LetterReplyFragment.letterColor = LetterBackgroundColor.MINT - } - R.id.cv_letter_reply_color_lavender -> { - cvLetterReplyContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null) - this@LetterReplyFragment.letterColor = LetterBackgroundColor.LAVENDER - } + 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 { 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 index 95f40944..d68bc8fc 100644 --- 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 @@ -24,18 +24,17 @@ 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.ui.square.model.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.util.UiState -import com.google.android.material.card.MaterialCardView 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 letterColorList by lazy { - listOf(binding.cvLetterColorBeige, binding.cvLetterColorPink, binding.cvLetterColorLeaf, binding.cvLetterColorMint, binding.cvLetterColorLavender) + private val premiumLetterColorList by lazy { + listOf(binding.cvLetterWriteColorPink, binding.cvLetterWriteColorGreen, binding.cvLetterWriteColorBlue, binding.cvLetterWriteColorPurple) } private val viewModel: LetterViewModel by activityViewModels() @@ -63,36 +62,17 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { private fun initListeners() = with(binding) { ivLetterWriteBack.setOnClickListener { findNavController().popBackStack() } - letterColorList.forEach { letterColor -> + premiumLetterColorList.forEach { letterColor -> letterColor.setOnClickListener { clickedView -> - letterColorList.forEach { card -> - card.isSelected = false - card.getChildAt(0).isVisible = false - } - clickedView.isSelected = true - (clickedView as MaterialCardView).getChildAt(0).isVisible = true - when(clickedView.id) { - R.id.cv_letter_color_beige -> { - cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_beige, null) - this@LetterWriteFragment.letterColor = LetterBackgroundColor.BEIGE - } - R.id.cv_letter_color_pink -> { - cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_pink, null) - this@LetterWriteFragment.letterColor = LetterBackgroundColor.PINK - } - R.id.cv_letter_color_leaf -> { - cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_leaf, null) - this@LetterWriteFragment.letterColor = LetterBackgroundColor.LEAF - } - R.id.cv_letter_color_mint -> { - cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_mint, null) - this@LetterWriteFragment.letterColor = LetterBackgroundColor.MINT - } - R.id.cv_letter_color_lavender -> { - cvLetterContainer.backgroundTintList = resources.getColorStateList(R.color.letter_bg_lavender, null) - this@LetterWriteFragment.letterColor = LetterBackgroundColor.LAVENDER - } + 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 { 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/res/drawable/img_letter_blue.png b/app/src/main/res/drawable/img_letter_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..45ba33f7156036b5fa3d868a8a46a2625cf1d764 GIT binary patch literal 34348 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 u%VmJh(cv<>6h$-xXk|9m~$f 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
u#8l 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 V;w^#?9@k`7;zY-x>a53)sD<#FlDNxfMuL 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 KdUZO^(f literal 0 HcmV?d00001 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..053a5930 --- /dev/null +++ b/app/src/main/res/layout/dialog_premium_letter_buy.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 d01d4d3b..783441e6 100644 --- a/app/src/main/res/layout/fragment_letter_reply.xml +++ b/app/src/main/res/layout/fragment_letter_reply.xml @@ -139,11 +139,11 @@ + 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: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"> + app:layout_constraintBottom_toBottomOf="@id/cv_letter_write_color_pink" + app:layout_constraintStart_toEndOf="@id/cv_letter_write_color_pink" + app:layout_constraintTop_toTopOf="@id/cv_letter_write_color_pink"> + app:layout_constraintBottom_toBottomOf="@id/cv_letter_write_color_green" + app:layout_constraintStart_toEndOf="@id/cv_letter_write_color_green" + app:layout_constraintTop_toTopOf="@id/cv_letter_write_color_green"> + app:layout_constraintBottom_toBottomOf="@id/cv_letter_write_color_blue" + app:layout_constraintStart_toEndOf="@id/cv_letter_write_color_blue" + app:layout_constraintTop_toTopOf="@id/cv_letter_write_color_blue"> Date: Wed, 4 Feb 2026 14:00:21 +0900 Subject: [PATCH 19/55] =?UTF-8?q?rename(letter):=20=ED=8E=B8=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=83=89=EC=83=81=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99,=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20-=20UI=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=E2=86=92=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A0=88=EC=9D=B4=EC=96=B4=20-=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/square/letter/ArrivedPendingLetterResponse.kt | 2 +- .../app/data/model/square/letter/SendLetterRequest.kt | 2 +- .../egobook/app/data/repository/LetterRepositoryImpl.kt | 2 +- .../domain/model/square/letter/ArrivedPendingLetter.kt | 2 -- .../domain/model/square/letter/LetterBackgroundColor.kt | 9 +++++++++ .../egobook/app/domain/model/square/letter/SendLetter.kt | 2 -- .../ui/square/model/letter/ArrivedPendingLetterModel.kt | 1 + .../app/ui/square/model/letter/LetterBackgroundColor.kt | 9 --------- .../app/ui/square/model/letter/SendLetterModel.kt | 1 + .../app/ui/square/view/ArrivedPendingLetterDialog.kt | 8 ++++---- .../com/egobook/app/ui/square/view/LetterSendDialog.kt | 2 +- app/src/main/res/values/colors.xml | 6 +++--- 12 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/LetterBackgroundColor.kt delete mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt 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 index 03ac8cc4..59cb9973 100644 --- 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 @@ -4,7 +4,7 @@ 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.ui.square.model.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor import com.google.gson.annotations.SerializedName data class ArrivedPendingLetterResponse( 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 index ef1ae0bd..b7a9e442 100644 --- 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 @@ -1,6 +1,6 @@ package com.egobook.app.data.model.square.letter -import com.egobook.app.ui.square.model.letter.LetterBackgroundColor +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 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 index e196231f..5eb3a053 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -12,7 +12,7 @@ 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.SendLetter import com.egobook.app.domain.repository.LetterRepository -import com.egobook.app.ui.square.model.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor import javax.inject.Inject class LetterRepositoryImpl @Inject constructor( 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 index 0a4fbc23..12bbcbe1 100644 --- 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 @@ -1,7 +1,5 @@ package com.egobook.app.domain.model.square.letter -import com.egobook.app.ui.square.model.letter.LetterBackgroundColor - data class ArrivedPendingLetter( val letter: ArrivedPendingLetterItem? = null ) 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..1cbc3a5c --- /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) { + BEIGE("BEIGE"), + 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/SendLetter.kt b/app/src/main/java/com/egobook/app/domain/model/square/letter/SendLetter.kt index 1b2722ac..2929d42f 100644 --- 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 @@ -1,7 +1,5 @@ package com.egobook.app.domain.model.square.letter -import com.egobook.app.ui.square.model.letter.LetterBackgroundColor - data class SendLetter( val mode: LetterMode, val receiverId: Long? = null, 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 index a32aebe5..18987347 100644 --- 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 @@ -3,6 +3,7 @@ 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 diff --git a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt b/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt deleted file mode 100644 index 41c3ce3c..00000000 --- a/app/src/main/java/com/egobook/app/ui/square/model/letter/LetterBackgroundColor.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.egobook.app.ui.square.model.letter - -enum class LetterBackgroundColor(val value: String) { - BEIGE("BEIGE"), - PINK("PINK"), - LEAF("LEAF"), - MINT("MINT"), - LAVENDER("LAVENDER") -} \ No newline at end of file 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 index 910f831f..953fe401 100644 --- 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 @@ -1,5 +1,6 @@ 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 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 index 2e48e8d3..c074fe47 100644 --- 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 @@ -11,7 +11,7 @@ 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.ui.square.model.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor import java.time.OffsetDateTime import java.time.format.DateTimeFormatter @@ -41,9 +41,9 @@ class ArrivedPendingLetterDialog( when(letterInfo.letterColor) { LetterBackgroundColor.BEIGE -> resources.getColorStateList(R.color.letter_bg_beige, null) LetterBackgroundColor.PINK -> resources.getColorStateList(R.color.letter_bg_pink, null) - LetterBackgroundColor.LEAF -> resources.getColorStateList(R.color.letter_bg_leaf, null) - LetterBackgroundColor.MINT -> resources.getColorStateList(R.color.letter_bg_mint, null) - LetterBackgroundColor.LAVENDER -> resources.getColorStateList(R.color.letter_bg_lavender, 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) } } 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 index df32eb26..31cb401c 100644 --- 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 @@ -18,7 +18,7 @@ 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.ui.square.model.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.LetterBackgroundColor import com.egobook.app.ui.square.model.letter.SendLetterModel import com.egobook.app.ui.square.viewmodel.LetterViewModel import com.egobook.app.util.UiState diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1e815895..8543899e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -46,9 +46,9 @@ #FCF8E8 #FFC5C5 - #D0DEAD - #BFFBEA - #E4D4FF + #D0DEAD + #BFFBEA + #E4D4FF #DBDBDB From f68a10ccbe49184c2da04b8522457ec22c90f669 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 4 Feb 2026 16:14:45 +0900 Subject: [PATCH 20/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=ED=95=98=EA=B8=B0=20API=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20-=20=EA=B3=B5=ED=86=B5=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=82=AC=EC=9A=A9=20=E2=86=92=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EA=B5=AC=EB=B6=84=20-=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=80=20=ED=8E=B8=EC=A7=80=20=EC=93=B0=EA=B8=B0=EC=99=80=20?= =?UTF-8?q?=EC=9C=A0=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 9 ++ .../model/square/letter/ReplyLetterRequest.kt | 7 ++ .../square/letter/ReplyLetterResponse.kt | 42 +++++++ .../data/repository/LetterRepositoryImpl.kt | 19 +++ .../model/square/letter/LetterStatus.kt | 2 +- .../domain/model/square/letter/ReplyLetter.kt | 16 +++ .../domain/model/square/letter/ReplyReward.kt | 6 + .../app/domain/repository/LetterRepository.kt | 2 + .../usecase/letter/ReplyLetterUseCase.kt | 9 ++ .../square/model/letter/ReplyLetterModel.kt | 32 +++++ .../view/DetectAbusiveContentSuccessDialog.kt | 31 ++++- .../app/ui/square/view/LetterReplyFragment.kt | 113 ++++++++++++++++-- .../app/ui/square/view/LetterSendDialog.kt | 3 +- .../ui/square/viewmodel/LetterViewModel.kt | 19 ++- .../dialog_detect_abusive_content_success.xml | 5 +- 15 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterRequest.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/ReplyLetterResponse.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyLetter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/ReplyReward.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/ReplyLetterUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/ReplyLetterModel.kt 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 index ba034fb4..65982779 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -2,11 +2,14 @@ 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.SendLetterRequest import com.egobook.app.data.model.square.letter.SendLetterResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Path interface LetterApiService { @POST("/plaza/letters") @@ -14,4 +17,10 @@ interface LetterApiService { @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 } \ 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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index 5eb3a053..b6833c9d 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.egobook.app.data.repository import com.egobook.app.data.api.AIApiService import com.egobook.app.data.api.LetterApiService import com.egobook.app.data.model.square.letter.DetectAbusiveContentRequest +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.domain.model.square.letter.AbusiveContentAnalysis @@ -13,6 +14,7 @@ import com.egobook.app.domain.model.square.letter.LetterStatus import com.egobook.app.domain.model.square.letter.SendLetter import com.egobook.app.domain.repository.LetterRepository import com.egobook.app.domain.model.square.letter.LetterBackgroundColor +import com.egobook.app.domain.model.square.letter.ReplyLetter import javax.inject.Inject class LetterRepositoryImpl @Inject constructor( @@ -68,8 +70,25 @@ class LetterRepositoryImpl @Inject constructor( letterColor = LetterBackgroundColor.BEIGE ) ) + val emptyMockData = ArrivedPendingLetter( + letter = null + ) Result.success(mockData) } 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) + } } \ 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 index fc139b9f..8cedfff0 100644 --- 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 @@ -3,8 +3,8 @@ package com.egobook.app.domain.model.square.letter enum class LetterStatus(val value: String) { SENT("SENT"), ARRIVED("ARRIVED"), - DEFERRED("DEFERRED"), 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..10f63780 --- /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", "잉크"), + SINCERITY("SINCERITY", "공감성") +} \ 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 index b14e8b86..e1fdcca1 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -2,10 +2,12 @@ package com.egobook.app.domain.repository 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.SendLetter 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 } \ 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/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/view/DetectAbusiveContentSuccessDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/DetectAbusiveContentSuccessDialog.kt index e763325c..5d5e697e 100644 --- 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 @@ -10,9 +10,12 @@ 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: DialogFragment(R.layout.dialog_detect_abusive_content_success) { +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 { @@ -24,12 +27,36 @@ class DetectAbusiveContentSuccessDialog: DialogFragment(R.layout.dialog_detect_a 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.SINCERITY -> { + rewardList.add("${ReplyReward.SINCERITY.label} ${reward.amount}") // 공감성 1 + } + } + } + val totalRewards = rewardList.joinToString(", ") // 잉크 1, 공감성 1 + btnDetectAbusiveSuccess.text = "$totalRewards 획득!" // 잉크 1, 공감성 1 획득! + } + else -> {} + } + } + private fun initListeners() = with(binding) { btnDetectAbusiveSuccess.setOnClickListener { - Toast.makeText(context, "잉크 1개를 획득하였습니다.", Toast.LENGTH_SHORT).show() removeScreenBlur() findNavController().popBackStack() } 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 3c91b914..a857aad3 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 @@ -8,6 +8,10 @@ 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.navArgs import com.egobook.app.BlurLevel import com.egobook.app.R @@ -15,24 +19,38 @@ 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) + listOf( + binding.cvLetterReplyColorPink, + binding.cvLetterReplyColorGreen, + binding.cvLetterReplyColorBlue, + binding.cvLetterReplyColorPurple + ) } - private var letterColor: LetterBackgroundColor = LetterBackgroundColor.BEIGE + 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) { @@ -44,23 +62,25 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { 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 + 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) { + 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 } + val dialog = + PremiumLetterBuyDialog(letterColor = letterColor).apply { isCancelable = false } dialog.show(childFragmentManager, PremiumLetterBuyDialog.TAG) applyScreenBlur(BlurLevel.BASE) } } - etLetterReplyContent.addTextChangedListener(object: TextWatcher { + etLetterReplyContent.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p0: Editable?) = Unit override fun beforeTextChanged( p0: CharSequence?, @@ -68,6 +88,7 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { p2: Int, p3: Int ) = Unit + override fun onTextChanged( text: CharSequence?, start: Int, @@ -81,12 +102,18 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { ivLetterReplyTooltip.setOnClickListener { showTooltipPopup(anchorView = ivLetterReplyTooltip) } + btnLetterReply.setOnClickListener { + viewModel.detectAbusiveContent(text = etLetterReplyContent.text.toString()) + } } 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, + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + true ) popupBinding.root.measure( View.MeasureSpec.UNSPECIFIED, @@ -100,6 +127,78 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { ) } + 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) + } + } + } + } + } + } + } + + 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 = 360 } 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 index 31cb401c..96b80b05 100644 --- 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 @@ -19,6 +19,7 @@ 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 @@ -108,7 +109,7 @@ class LetterSendDialog(private val mode: LetterMode, private val friendInfo: Fri UiState.Idle -> {} UiState.Loading -> {} is UiState.Success -> { - val dialog = DetectAbusiveContentSuccessDialog().apply { + val dialog = DetectAbusiveContentSuccessDialog(status = LetterStatus.SENT).apply { isCancelable = false } dialog.show(parentFragmentManager, DetectAbusiveContentSuccessDialog.TAG) 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 index f1f2163c..241ab511 100644 --- 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 @@ -5,11 +5,13 @@ import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.GetFriendListUseCase import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase +import com.egobook.app.domain.usecase.letter.ReplyLetterUseCase 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.SendLetterModel import com.egobook.app.ui.square.model.letter.toDomain import com.egobook.app.ui.square.model.letter.toPresentation @@ -27,7 +29,8 @@ class LetterViewModel @Inject constructor( private val getFriendListUseCase: GetFriendListUseCase, private val sendLetterUseCase: SendLetterUseCase, private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase, - private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase + private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase, + private val replyLetterUseCase: ReplyLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -85,4 +88,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ 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 index 3bdf5dc2..98e59073 100644 --- a/app/src/main/res/layout/dialog_detect_abusive_content_success.xml +++ b/app/src/main/res/layout/dialog_detect_abusive_content_success.xml @@ -5,7 +5,8 @@ android:background="@drawable/bg_round_and_white_dialog" android:paddingVertical="24dp" android:paddingHorizontal="16dp" - xmlns:app="http://schemas.android.com/apk/res-auto"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> Date: Wed, 4 Feb 2026 17:14:42 +0900 Subject: [PATCH 21/55] =?UTF-8?q?rename(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=EB=B3=B4=EC=83=81=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20-=20SINCERITY=20=E2=86=92=20EM?= =?UTF-8?q?PATHY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/domain/model/square/letter/ReplyReward.kt | 2 +- .../app/ui/square/view/DetectAbusiveContentSuccessDialog.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) 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 index 10f63780..468d4a88 100644 --- 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 @@ -2,5 +2,5 @@ package com.egobook.app.domain.model.square.letter enum class ReplyReward(val value: String, val label: String) { INK("INK", "잉크"), - SINCERITY("SINCERITY", "공감성") + EMPATHY("EMPATHY", "공감성") } \ 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 index 5d5e697e..ea20c18d 100644 --- 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 @@ -4,7 +4,6 @@ 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.navigation.fragment.findNavController @@ -43,8 +42,8 @@ class DetectAbusiveContentSuccessDialog(private val status: LetterStatus, privat ReplyReward.INK -> { rewardList.add("${ReplyReward.INK.label} ${reward.amount}") // 잉크 1 } - ReplyReward.SINCERITY -> { - rewardList.add("${ReplyReward.SINCERITY.label} ${reward.amount}") // 공감성 1 + ReplyReward.EMPATHY -> { + rewardList.add("${ReplyReward.EMPATHY.label} ${reward.amount}") // 공감성 1 } } } From c362f29b8f2e61f6f20b9fe372c157229a63b75d Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 4 Feb 2026 17:48:09 +0900 Subject: [PATCH 22/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=EC=A7=80=EC=97=B0=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?API=20=EC=97=B0=EA=B2=B0=20-=20id=EB=AA=85=20=EC=A2=80=20?= =?UTF-8?q?=EB=8D=94=20=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(reply=5Flater=20=E2=86=92=20defer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 5 +++ .../data/repository/LetterRepositoryImpl.kt | 11 +++++ .../app/domain/repository/LetterRepository.kt | 1 + .../usecase/letter/DeferReplyLetterUseCase.kt | 10 +++++ .../square/view/ArrivedPendingLetterDialog.kt | 33 +++++++++++++-- .../view/ArrivedPendingLetterPopupDialog.kt | 31 +++++++++++++- .../view/GiveUpReplyLetterPopupDialog.kt | 41 ++++++++++++++++++- .../ui/square/viewmodel/LetterViewModel.kt | 18 +++++++- .../layout/dialog_arrived_pending_letter.xml | 4 +- .../layout/dialog_give_up_reply_letter.xml | 8 ++-- 10 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/DeferReplyLetterUseCase.kt 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 index 65982779..f57a9f36 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -23,4 +23,9 @@ interface LetterApiService { @Path("letterId") letterId: Long, @Body request: ReplyLetterRequest ): ApiResponse + + @POST("/plaza/letters/{letterId}/defer") + suspend fun deferReplyLetter( + @Path("letterId") letterId: Long + ): ApiResponse } \ No newline at end of file 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 index b6833c9d..39805ee4 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -91,4 +91,15 @@ class LetterRepositoryImpl @Inject constructor( } 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) + } } \ 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 index e1fdcca1..fe1b3dfa 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -10,4 +10,5 @@ interface LetterRepository { suspend fun detectAbusiveContent(text: String): Result suspend fun fetchArrivedPendingLetter(): Result suspend fun replyLetter(letterId: Long, text: String): Result + suspend fun deferReplyLetter(letterId: Long): Result } \ No newline at end of file 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/ui/square/view/ArrivedPendingLetterDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt index c074fe47..5d1e156b 100644 --- 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 @@ -6,12 +6,19 @@ 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 @@ -19,6 +26,7 @@ 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 { @@ -31,6 +39,7 @@ class ArrivedPendingLetterDialog( binding = DialogArrivedPendingLetterBinding.bind(view) initViews() initListeners() + initObservers() } private fun initViews() = with(binding) { @@ -48,15 +57,15 @@ class ArrivedPendingLetterDialog( } private fun initListeners() = with(binding) { - ivArrivedPendingLetterCancel.setOnClickListener { - + ivArrivedPendingLetterDefer.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterInfo.letterId) } ivArrivedPendingLetterReport.setOnClickListener { } btnArrivedPendingLetterGiveUp.setOnClickListener { dismiss() - val dialog = GiveUpReplyLetterPopupDialog().apply { isCancelable = false } + val dialog = GiveUpReplyLetterPopupDialog(letterInfo = letterInfo).apply { isCancelable = false } dialog.show(parentFragmentManager, GiveUpReplyLetterPopupDialog.TAG) } btnArrivedPendingLetterReply.setOnClickListener { @@ -67,6 +76,24 @@ class ArrivedPendingLetterDialog( } } + 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 로 변환해주는 메소드 */ 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 index 32bfbb32..0d8cb416 100644 --- 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 @@ -6,11 +6,19 @@ 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 @@ -18,6 +26,7 @@ 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 { @@ -29,6 +38,7 @@ class ArrivedPendingLetterPopupDialog(private val letterInfo: ArrivedPendingLett binding = DialogArrivedPendingLetterPopupBinding.bind(view) initViews() initListeners() + initObservers() } private fun initViews() = with(binding) { @@ -38,16 +48,33 @@ class ArrivedPendingLetterPopupDialog(private val letterInfo: ArrivedPendingLett private fun initListeners() = with(binding) { btnArrivedPendingLetterReplyLater.setOnClickListener { - // TODO: 로직 작성하기 (상태 변경, API 연동 등) + viewModel.deferReplyLetter(letterId = letterInfo.letterId) } btnArrivedPendingLetterConfirm.setOnClickListener { val dialog = ArrivedPendingLetterDialog(letterInfo = letterInfo).apply { isCancelable = false } dialog.show(parentFragmentManager, ArrivedPendingLetterDialog.TAG) - applyScreenBlur(BlurLevel.BASE) 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 = 타임존 오프셋 정보 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 index 0bb102a6..254f5ba1 100644 --- 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 @@ -7,11 +7,24 @@ 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: DialogFragment(R.layout.dialog_give_up_reply_letter) { +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 { @@ -22,6 +35,32 @@ class GiveUpReplyLetterPopupDialog: DialogFragment(R.layout.dialog_give_up_reply 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) + } + } + + 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() + } + } + } + } + } } 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 index 241ab511..6686836e 100644 --- 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 @@ -3,6 +3,7 @@ package com.egobook.app.ui.square.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.egobook.app.domain.usecase.GetFriendListUseCase +import com.egobook.app.domain.usecase.letter.DeferReplyLetterUseCase import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase import com.egobook.app.domain.usecase.letter.ReplyLetterUseCase @@ -30,7 +31,8 @@ class LetterViewModel @Inject constructor( private val sendLetterUseCase: SendLetterUseCase, private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase, private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase, - private val replyLetterUseCase: ReplyLetterUseCase + private val replyLetterUseCase: ReplyLetterUseCase, + private val deferReplyLetterUseCase: DeferReplyLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -102,4 +104,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_arrived_pending_letter.xml b/app/src/main/res/layout/dialog_arrived_pending_letter.xml index e2ce7341..7c5517c0 100644 --- a/app/src/main/res/layout/dialog_arrived_pending_letter.xml +++ b/app/src/main/res/layout/dialog_arrived_pending_letter.xml @@ -7,7 +7,7 @@ android:background="@android:color/transparent"> + app:layout_constraintTop_toBottomOf="@id/iv_arrived_pending_letter_defer"> + 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 From 14f57acbb1af9165e6eadb2ddf5f3b33e65800cc Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 4 Feb 2026 18:08:04 +0900 Subject: [PATCH 23/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=ED=8F=AC=EA=B8=B0=20API=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20-=20=EB=8B=B5=EC=9E=A5=20=EC=A7=80=EC=97=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=8F=84=20=EC=9D=BC=EB=B6=80=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 5 +++ .../data/repository/LetterRepositoryImpl.kt | 11 ++++++ .../app/domain/repository/LetterRepository.kt | 1 + .../letter/GiveUpReplyLetterUseCase.kt | 10 ++++++ .../view/GiveUpReplyLetterPopupDialog.kt | 34 ++++++++++++++----- .../app/ui/square/view/LetterReplyFragment.kt | 16 +++++++++ .../ui/square/viewmodel/LetterViewModel.kt | 18 +++++++++- .../main/res/layout/fragment_letter_reply.xml | 1 + 8 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/GiveUpReplyLetterUseCase.kt 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 index f57a9f36..821aa949 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -28,4 +28,9 @@ interface LetterApiService { suspend fun deferReplyLetter( @Path("letterId") letterId: Long ): ApiResponse + + @POST("/plaza/letters/{letterId}/give-up") + suspend fun giveUpReplyLetter( + @Path("letterId") letterId: Long + ): ApiResponse } \ No newline at end of file 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 index 39805ee4..74006cf6 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -102,4 +102,15 @@ class LetterRepositoryImpl @Inject constructor( } 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) + } } \ 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 index fe1b3dfa..d75f2634 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -11,4 +11,5 @@ interface LetterRepository { suspend fun fetchArrivedPendingLetter(): Result suspend fun replyLetter(letterId: Long, text: String): Result suspend fun deferReplyLetter(letterId: Long): Result + suspend fun giveUpReplyLetter(letterId: Long): Result } \ 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/ui/square/view/GiveUpReplyLetterPopupDialog.kt b/app/src/main/java/com/egobook/app/ui/square/view/GiveUpReplyLetterPopupDialog.kt index 254f5ba1..a5bf2841 100644 --- 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 @@ -43,19 +43,37 @@ class GiveUpReplyLetterPopupDialog( 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) { - viewModel.deferReplyLetterResult.collect { state -> - when(state) { - is UiState.Failure -> {} - UiState.Idle -> {} - UiState.Loading -> {} - is UiState.Success -> { - removeScreenBlur() - dismiss() + 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() + } } } } 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 a857aad3..5e9c030d 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 @@ -12,6 +12,7 @@ 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 @@ -105,6 +106,9 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { btnLetterReply.setOnClickListener { viewModel.detectAbusiveContent(text = etLetterReplyContent.text.toString()) } + ivLetterReplyBack.setOnClickListener { + viewModel.deferReplyLetter(letterId = letterItem.letterId) + } } private fun showTooltipPopup(anchorView: View) { @@ -179,6 +183,18 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { } } } + launch { + viewModel.deferReplyLetterResult.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + findNavController().popBackStack() + } + } + } + } } } } 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 index 6686836e..00ee1a56 100644 --- 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 @@ -6,6 +6,7 @@ import com.egobook.app.domain.usecase.GetFriendListUseCase import com.egobook.app.domain.usecase.letter.DeferReplyLetterUseCase import com.egobook.app.domain.usecase.letter.DetectAbusiveContentUseCase import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase +import com.egobook.app.domain.usecase.letter.GiveUpReplyLetterUseCase import com.egobook.app.domain.usecase.letter.ReplyLetterUseCase import com.egobook.app.domain.usecase.letter.SendLetterUseCase import com.egobook.app.ui.square.model.friend.FriendModel @@ -32,7 +33,8 @@ class LetterViewModel @Inject constructor( private val detectAbusiveContentUseCase: DetectAbusiveContentUseCase, private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase, private val replyLetterUseCase: ReplyLetterUseCase, - private val deferReplyLetterUseCase: DeferReplyLetterUseCase + private val deferReplyLetterUseCase: DeferReplyLetterUseCase, + private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -118,4 +120,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ 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 783441e6..595c9c5e 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"> Date: Thu, 5 Feb 2026 18:12:57 +0900 Subject: [PATCH 24/55] =?UTF-8?q?feat(letter):=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B8=20=ED=8E=B8=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=97=B0=EA=B2=B0=20-=20Paging3=20=EC=82=AC=EC=9A=A9=20-?= =?UTF-8?q?=20ListAdapter=20=E2=86=92=20PagingDataAdapter=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20=EB=8D=94=EB=AF=B8=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 8 ++ .../model/square/letter/SentLetterResponse.kt | 49 ++++++++++ .../data/repository/LetterRepositoryImpl.kt | 26 ++++- .../paging/SentLettersPagingSource.kt | 29 ++++++ .../domain/model/square/letter/SentLetter.kt | 17 ++++ .../app/domain/repository/LetterRepository.kt | 4 + .../usecase/letter/GetSentLettersUseCase.kt | 13 +++ .../app/ui/square/adapter/MyLettersAdapter.kt | 53 ---------- .../ui/square/adapter/MySentLettersAdapter.kt | 64 +++++++++++++ .../ui/square/model/letter/SentLetterModel.kt | 23 +++++ .../app/ui/square/view/MyLettersFragment.kt | 96 +++++++------------ .../app/ui/square/view/SquareFragment.kt | 23 ++++- .../ui/square/viewmodel/LetterViewModel.kt | 20 +++- .../main/res/layout/fragment_my_letters.xml | 2 +- app/src/main/res/layout/fragment_square.xml | 27 ++---- .../main/res/navigation/bottom_navigation.xml | 8 +- 16 files changed, 319 insertions(+), 143 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/repository/paging/SentLettersPagingSource.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLettersUseCase.kt delete mode 100644 app/src/main/java/com/egobook/app/ui/square/adapter/MyLettersAdapter.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/adapter/MySentLettersAdapter.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterModel.kt 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 index 821aa949..56aacb48 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -6,10 +6,12 @@ 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.SendLetterRequest import com.egobook.app.data.model.square.letter.SendLetterResponse +import com.egobook.app.data.model.square.letter.SentLetterResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query interface LetterApiService { @POST("/plaza/letters") @@ -33,4 +35,10 @@ interface LetterApiService { suspend fun giveUpReplyLetter( @Path("letterId") letterId: Long ): ApiResponse + + @GET("/plaza/letters/sent") + suspend fun fetchSentLetters( + @Query("page") page: Int, + @Query("size") size: Int + ): ApiResponse } \ No newline at end of file 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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index 74006cf6..0506b4eb 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -1,20 +1,25 @@ 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.DetectAbusiveContentRequest 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.SendLetter +import com.egobook.app.domain.model.square.letter.SentLetterItem import com.egobook.app.domain.repository.LetterRepository -import com.egobook.app.domain.model.square.letter.LetterBackgroundColor -import com.egobook.app.domain.model.square.letter.ReplyLetter +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class LetterRepositoryImpl @Inject constructor( @@ -73,7 +78,7 @@ class LetterRepositoryImpl @Inject constructor( val emptyMockData = ArrivedPendingLetter( letter = null ) - Result.success(mockData) + Result.success(emptyMockData) } catch (e: Exception) { Result.failure(e) } @@ -113,4 +118,17 @@ class LetterRepositoryImpl @Inject constructor( } 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 + } } \ No newline at end of file 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/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/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt index d75f2634..12c8a721 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -1,9 +1,12 @@ 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.SendLetter +import com.egobook.app.domain.model.square.letter.SentLetterItem +import kotlinx.coroutines.flow.Flow interface LetterRepository { suspend fun sendLetter(letter: SendLetter): Result @@ -12,4 +15,5 @@ interface LetterRepository { 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> } \ No newline at end of file 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/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/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/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/SquareFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/SquareFragment.kt index b114b702..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 @@ -20,6 +20,7 @@ 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.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 @@ -38,10 +39,17 @@ class SquareFragment : Fragment(R.layout.fragment_square) { 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 @@ -59,10 +67,12 @@ class SquareFragment : Fragment(R.layout.fragment_square) { 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) { @@ -241,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) } } } @@ -276,6 +286,13 @@ class SquareFragment : Fragment(R.layout.fragment_square) { } } } + launch { + letterViewModel.sentLetters.collectLatest { pagingData -> + if(pagingData != null) { + sentLetterAdapter.submitData(lifecycle, pagingData) + } + } + } } } } 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 index 00ee1a56..51f55428 100644 --- 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 @@ -2,10 +2,14 @@ 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.DetectAbusiveContentUseCase import com.egobook.app.domain.usecase.letter.GetArrivedPendingLetterUseCase +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.SendLetterUseCase @@ -15,6 +19,7 @@ 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.SendLetterModel +import com.egobook.app.ui.square.model.letter.SentLetterModel import com.egobook.app.ui.square.model.letter.toDomain import com.egobook.app.ui.square.model.letter.toPresentation import com.egobook.app.util.UiState @@ -23,6 +28,7 @@ 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 @@ -34,7 +40,8 @@ class LetterViewModel @Inject constructor( private val getArrivedPendingLetterUseCase: GetArrivedPendingLetterUseCase, private val replyLetterUseCase: ReplyLetterUseCase, private val deferReplyLetterUseCase: DeferReplyLetterUseCase, - private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase + private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase, + private val getSentLettersUseCase: GetSentLettersUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -134,4 +141,15 @@ class LetterViewModel @Inject constructor( } } } + + 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() } + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_my_letters.xml b/app/src/main/res/layout/fragment_my_letters.xml index bb8f1aa1..3b993c79 100644 --- a/app/src/main/res/layout/fragment_my_letters.xml +++ b/app/src/main/res/layout/fragment_my_letters.xml @@ -32,7 +32,7 @@ - - - - - - - - - - + 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"> + @@ -197,9 +200,8 @@ android:label="MyLetterDetailFragment" tools:layout="@layout/fragment_my_letter_detail"> + android:name="letterId" + app:argType="long" /> Date: Thu, 5 Feb 2026 18:15:14 +0900 Subject: [PATCH 25/55] =?UTF-8?q?refactor(di):=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20di=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20-=20module,=20qualifer=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/interceptor/TokenAuthenticator.kt | 2 +- .../java/com/egobook/app/di/ServiceModule.kt | 46 ------------- .../app/di/{ => module}/DataStoreModule.kt | 2 +- .../egobook/app/di/module/NetworkModule.kt | 64 ++++++++----------- .../egobook/app/di/module/ServiceModule.kt | 14 +++- .../com/egobook/app/di/qualifier/Qualifier.kt | 5 ++ 6 files changed, 45 insertions(+), 88 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/di/ServiceModule.kt rename app/src/main/java/com/egobook/app/di/{ => module}/DataStoreModule.kt (93%) 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 2aa83fc4..fee56ace 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 diff --git a/app/src/main/java/com/egobook/app/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/ServiceModule.kt deleted file mode 100644 index dbf031db..00000000 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.egobook.app.di - -import com.egobook.app.data.api.AuthApiService -import com.egobook.app.data.api.CounselingApiService -import com.egobook.app.data.api.FriendsApiService -import com.egobook.app.data.api.NotificationApiService -import com.egobook.app.data.api.QuestionApiService -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object ServiceModule { - @Provides - @Singleton - fun provideCounselingService(retrofit: Retrofit): CounselingApiService { - return retrofit.create(CounselingApiService::class.java) - } - - @Provides - @Singleton - fun provideNotificationService(retrofit: Retrofit): NotificationApiService { - return retrofit.create(NotificationApiService::class.java) - } - - @Provides - @Singleton - fun provideFriendsService(retrofit: Retrofit): FriendsApiService { - return retrofit.create(FriendsApiService::class.java) - } - - @Provides - @Singleton - fun provideQuestionService(retrofit: Retrofit): QuestionApiService { - return retrofit.create(QuestionApiService::class.java) - } - - @Provides - @Singleton - fun provideAuthService(retrofit: Retrofit): AuthApiService = - retrofit.create(AuthApiService::class.java) -} \ 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/module/NetworkModule.kt b/app/src/main/java/com/egobook/app/di/module/NetworkModule.kt index f79d8349..7cc18f22 100644 --- a/app/src/main/java/com/egobook/app/di/module/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,16 +16,12 @@ 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 @BackendApi @@ -52,12 +50,21 @@ object NetworkModule { .build() } + /** + * 토큰 갱신용 Retrofit + */ @Provides @Singleton - fun provideGsonConverterFactory(): GsonConverterFactory { - return GsonConverterFactory.create( - GsonBuilder().create() - ) + @AuthRetrofit + fun provideAuthRetrofit( + @AuthRetrofit client: OkHttpClient, + gsonConverterFactory: GsonConverterFactory + ): Retrofit { + return Retrofit.Builder() + .baseUrl(BuildConfig.BACKEND_BASE_URL) + .addConverterFactory(gsonConverterFactory) + .client(client) + .build() } /** @@ -84,35 +91,6 @@ object NetworkModule { }.build() } - /** - * 토큰 갱신용 Retrofit - */ - @Provides - @Singleton - @AuthRetrofit - fun provideAuthRetrofit( - @AuthRetrofit client: OkHttpClient, - gsonConverterFactory: GsonConverterFactory - ): Retrofit { - return Retrofit.Builder() - .baseUrl(BuildConfig.BACKEND_BASE_URL) - .addConverterFactory(gsonConverterFactory) - .client(client) - .build() - } - - /** - * 토큰 갱신용 AuthApiService - */ - @Provides - @Singleton - @AuthRetrofit - fun provideAuthApiService( - @AuthRetrofit retrofit: Retrofit - ): AuthApiService { - return retrofit.create(AuthApiService::class.java) - } - /** * 일반 API 호출용 OkHttpClient (Authenticator 포함) */ @@ -154,4 +132,12 @@ object NetworkModule { }.build() } + @Provides + @Singleton + fun provideGsonConverterFactory(): GsonConverterFactory { + return GsonConverterFactory.create( + GsonBuilder().create() + ) + } + } diff --git a/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt index d55cdf8b..080a53f1 100644 --- a/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt @@ -1,12 +1,14 @@ package com.egobook.app.di.module import com.egobook.app.data.api.AIApiService +import com.egobook.app.data.api.AuthApiService import com.egobook.app.data.api.CounselingApiService 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 @@ -48,10 +50,20 @@ object ServiceModule { return retrofit.create(LetterApiService::class.java) } + /** + * 토큰 갱신용 AuthApiService + */ + @Provides + @Singleton + fun provideAuthApiService( + @AuthRetrofit retrofit: Retrofit + ): AuthApiService { + return retrofit.create(AuthApiService::class.java) + } + @Provides @Singleton fun provideAIService(@AIApi retrofit: Retrofit): AIApiService { return retrofit.create(AIApiService::class.java) } - } \ No newline at end of file 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 index 2f8d48de..624adbb1 100644 --- a/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt +++ b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt @@ -1,9 +1,14 @@ package com.egobook.app.di.qualifier import jakarta.inject.Qualifier +// import javax.inject.Qualifier @Qualifier annotation class BackendApi @Qualifier annotation class AIApi + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class AuthRetrofit From f88e7840be847eb7d375b6d944f8713ea5964ef4 Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 6 Feb 2026 12:52:09 +0900 Subject: [PATCH 26/55] =?UTF-8?q?refactor(di):=20di=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20-=20AuthApiService=EB=8A=94=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=B4=20=ED=95=98=EB=82=98=EC=9D=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=95=8C=EB=AC=B8=EC=97=90=20@Qualifier=EB=A1=9C=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=B0=94=EC=9D=B8=EB=94=A9=EC=9D=84=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EC=95=84=EB=8F=84=20?= =?UTF-8?q?=EB=90=A8=20-=20NetworkStoreRepository=EC=97=90=EC=84=9C=20Retr?= =?UTF-8?q?ofit=EC=9D=84=20=EC=A3=BC=EC=9E=85=EB=B0=9B=EC=9D=84=EB=95=8C?= =?UTF-8?q?=20=ED=83=80=EC=9E=85=EC=9D=B4=20=ED=95=84=EC=9A=94=ED=95=A8=20?= =?UTF-8?q?-=20import=EC=97=90=EC=84=9C=20=EC=9E=98=EB=AA=BB=EB=90=9C=20Qu?= =?UTF-8?q?alifier=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/data/interceptor/TokenAuthenticator.kt | 2 +- app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt | 4 ++-- app/src/main/java/com/egobook/app/ui/shop/StoreRepository.kt | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) 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 fee56ace..6db3618e 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 @@ -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/di/qualifier/Qualifier.kt b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt index 624adbb1..15a7de70 100644 --- a/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt +++ b/app/src/main/java/com/egobook/app/di/qualifier/Qualifier.kt @@ -1,7 +1,6 @@ package com.egobook.app.di.qualifier -import jakarta.inject.Qualifier -// import javax.inject.Qualifier +import javax.inject.Qualifier @Qualifier annotation class BackendApi @@ -12,3 +11,4 @@ annotation class AIApi @Qualifier @Retention(AnnotationRetention.BINARY) annotation class AuthRetrofit + 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 fbfc1b75..ea3ebb52 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 @@ -55,7 +56,7 @@ data class CustomItemGroupDto( @Singleton class NetworkStoreRepository @Inject constructor( - private val retrofit: Retrofit + @BackendApi private val retrofit: Retrofit ) : StoreRepository { private val storeService by lazy { From b01edaef633169da394f851f11ec08b46986ad02 Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 6 Feb 2026 14:28:05 +0900 Subject: [PATCH 27/55] =?UTF-8?q?feat(letter):=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B8=20=ED=8E=B8=EC=A7=80=20+=20=EB=8B=B5?= =?UTF-8?q?=EC=9E=A5=20=EB=82=B4=EC=9A=A9=20=ED=95=A8=EA=BB=98=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20API=20=EC=97=B0=EA=B2=B0=20-=20AI=20=EB=8C=80?= =?UTF-8?q?=EB=8B=B5=20=EC=9C=A0=EB=AC=B4=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?UI=20=EB=B3=80=EA=B2=BD=20=EC=B6=94=EA=B0=80=20-=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 6 + .../letter/SentLetterWithReplyResponse.kt | 62 ++++++++ .../data/repository/LetterRepositoryImpl.kt | 12 ++ .../square/letter/SentLetterWithReply.kt | 21 +++ .../app/domain/repository/LetterRepository.kt | 2 + .../letter/GetSentLetterWithReplyUseCase.kt | 8 + .../model/letter/SentLetterWithReplyModel.kt | 48 ++++++ .../ui/square/view/MyLetterDetailFragment.kt | 86 ++++++++-- .../ui/square/viewmodel/LetterViewModel.kt | 19 ++- .../res/layout/fragment_my_letter_detail.xml | 149 ++++++++++-------- app/src/main/res/values/colors.xml | 1 + 11 files changed, 338 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/SentLetterWithReplyResponse.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/SentLetterWithReply.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/GetSentLetterWithReplyUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/SentLetterWithReplyModel.kt 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 index 56aacb48..09750cc4 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -7,6 +7,7 @@ import com.egobook.app.data.model.square.letter.ReplyLetterResponse 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.GET import retrofit2.http.POST @@ -41,4 +42,9 @@ interface LetterApiService { @Query("page") page: Int, @Query("size") size: Int ): ApiResponse + + @GET("/plaza/letters/{letterId}") + suspend fun fetchSentLetterWithReply( + @Path("letterId") letterId: Long + ): ApiResponse } \ 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/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index 0506b4eb..be8299a7 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -18,6 +18,7 @@ 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.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 @@ -131,4 +132,15 @@ class LetterRepositoryImpl @Inject constructor( } ).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}")) + } + } catch (e: Exception) { + Result.failure(e) + } } \ 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/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt index 12c8a721..c5d063e5 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -6,6 +6,7 @@ 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.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 { @@ -16,4 +17,5 @@ interface LetterRepository { suspend fun deferReplyLetter(letterId: Long): Result suspend fun giveUpReplyLetter(letterId: Long): Result fun fetchSentLetters(size: Int): Flow> + suspend fun fetchSentLetterWithReply(letterId: Long): Result } \ 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/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/MyLetterDetailFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt index 21a961dc..1fe9d672 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,103 @@ package com.egobook.app.ui.square.view import android.os.Bundle import android.view.View +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.R 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 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 + val chevronImage = if(cvMyLetterDetailSentContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down + ivMyLetterDetailSentChevron.setImageResource(chevronImage) + } + } + + private fun initObservers() = with(binding) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.sentLetterWithReply.collect { state -> + when(state) { + is UiState.Failure -> {} + UiState.Idle -> {} + UiState.Loading -> {} + is UiState.Success -> { + val data = state.data + val sentCardBackgroundColor = when(data.backgroundColor) { + LetterBackgroundColor.BEIGE -> 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 ) { + 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 + } + } + } + } + } + } + } + + 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/viewmodel/LetterViewModel.kt b/app/src/main/java/com/egobook/app/ui/square/viewmodel/LetterViewModel.kt index 51f55428..90b4b24b 100644 --- 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 @@ -9,6 +9,7 @@ import com.egobook.app.domain.usecase.GetFriendListUseCase import com.egobook.app.domain.usecase.letter.DeferReplyLetterUseCase 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 @@ -20,6 +21,7 @@ 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.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 @@ -41,7 +43,8 @@ class LetterViewModel @Inject constructor( private val replyLetterUseCase: ReplyLetterUseCase, private val deferReplyLetterUseCase: DeferReplyLetterUseCase, private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase, - private val getSentLettersUseCase: GetSentLettersUseCase + private val getSentLettersUseCase: GetSentLettersUseCase, + private val getSentLetterWithReplyUseCase: GetSentLetterWithReplyUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -152,4 +155,18 @@ class LetterViewModel @Inject constructor( } } } + + 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) + } + } + } } \ 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..2af72845 100644 --- a/app/src/main/res/layout/fragment_my_letter_detail.xml +++ b/app/src/main/res/layout/fragment_my_letter_detail.xml @@ -31,11 +31,11 @@ android:src="@drawable/ic_chevron_left" /> + tools:text="2025.12.25" /> @@ -92,82 +93,98 @@ android:id="@+id/tv_my_letter_detail_sent_content" android:layout_width="match_parent" android:layout_height="match_parent" - android:text="사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 + tools:text="사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용" /> - + app:layout_constraintTop_toBottomOf="@id/cv_my_letter_detail_sent_content"> - + - - - - - - - + + + + + + + + - - - - - + 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용" /> + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f0bfe99a..a52d3a2f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,6 +15,7 @@ #FFD9D9D9 #F4F3F3 + #AEAEAE #FF95BB2D #FFE8EED9 From a38949506731125097828890f73ba3abf39c059b Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 6 Feb 2026 14:52:06 +0900 Subject: [PATCH 28/55] =?UTF-8?q?fix(letter):=20UI=20anchorView=20?= =?UTF-8?q?=EB=B7=B0=EC=83=81=ED=83=9C=20=EB=8F=99=EC=A0=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20constraintLayout=20=EA=B8=B0=EB=B0=98=EC=97=90?= =?UTF-8?q?=EC=84=9C=20anchorView=EC=9D=98=20=EB=B7=B0=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EA=B0=80=20=EB=8F=99=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EB=90=A0=20=EA=B2=BD=EC=9A=B0,=20=EC=98=81?= =?UTF-8?q?=ED=96=A5=EB=B0=9B=EB=8A=94=20=EB=B7=B0=EC=9D=98=20anchor?= =?UTF-8?q?=EC=A0=90=EC=9D=84=20=EB=B3=80=EA=B2=BD=ED=95=B4=EC=95=BC?= =?UTF-8?q?=ED=95=A8=20-=20layoutParams=20=EC=82=AC=EC=9A=A9=20-=20?= =?UTF-8?q?=EC=98=81=ED=96=A5=EC=9D=84=20=EB=8D=9C=EB=B0=9B=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EC=88=98=ED=8F=89=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9D=84=20=EB=B6=80=EB=AA=A8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/square/view/MyLetterDetailFragment.kt | 17 +++++++++++++++-- .../res/layout/fragment_my_letter_detail.xml | 6 +++--- 2 files changed, 18 insertions(+), 5 deletions(-) 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 1fe9d672..17cea76e 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 @@ -48,8 +48,21 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { } ivMyLetterDetailSentChevron.setOnClickListener { cvMyLetterDetailSentContent.isVisible = !cvMyLetterDetailSentContent.isVisible - val chevronImage = if(cvMyLetterDetailSentContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down - ivMyLetterDetailSentChevron.setImageResource(chevronImage) + 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 + } + } } } 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 2af72845..14ad7677 100644 --- a/app/src/main/res/layout/fragment_my_letter_detail.xml +++ b/app/src/main/res/layout/fragment_my_letter_detail.xml @@ -106,15 +106,15 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:orientation="vertical" - app:layout_constraintEnd_toEndOf="@id/cv_my_letter_detail_sent_content" - app:layout_constraintStart_toStartOf="@id/cv_my_letter_detail_sent_content" + android:layout_marginHorizontal="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cv_my_letter_detail_sent_content"> Date: Fri, 6 Feb 2026 19:43:12 +0900 Subject: [PATCH 29/55] =?UTF-8?q?feat(letter):=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8B=B5=EC=9E=A5=20=EC=8B=A0=EA=B3=A0=ED=95=98=EA=B8=B0=20API?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20-=20=EB=82=B4=EA=B0=80=20=EC=93=B4=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80=20=EC=83=81=EC=84=B8=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20-=20=EB=8B=B5=EC=9E=A5=20=EC=98=A8=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80=20=EC=8B=A0=EA=B3=A0=20-=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=20=EC=8B=A0=EA=B3=A0=EB=90=9C=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EB=B6=88=EA=B0=80=EB=8A=A5=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20-=20=EB=AA=A9=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 7 ++ .../square/letter/ReportLetterRequest.kt | 17 ++++ .../data/repository/LetterRepositoryImpl.kt | 36 ++++++++- .../model/square/letter/ReportLetter.kt | 6 ++ .../model/square/letter/ReportLetterType.kt | 8 ++ .../app/domain/repository/LetterRepository.kt | 2 + .../letter/ReportRepliedLetterUseCase.kt | 11 +++ .../square/model/letter/ReportLetterModel.kt | 16 ++++ .../ui/square/view/MyLetterDetailFragment.kt | 17 +++- .../app/ui/square/view/SquareReportDialog.kt | 79 ++++++++++++++++--- .../ui/square/viewmodel/LetterViewModel.kt | 19 ++++- .../main/res/layout/dialog_square_report.xml | 1 + 12 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/square/letter/ReportLetterRequest.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetter.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/square/letter/ReportLetterType.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/ReportRepliedLetterUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/square/model/letter/ReportLetterModel.kt 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 index 09750cc4..0b8f31c8 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -4,6 +4,7 @@ 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 @@ -47,4 +48,10 @@ interface LetterApiService { suspend fun fetchSentLetterWithReply( @Path("letterId") letterId: Long ): ApiResponse + + @POST("/plaza/letters/{replyId}/report") + suspend fun reportRepliedLetter( + @Path("replyId") replyId: Long, + @Body request: ReportLetterRequest + ): ApiResponse } \ No newline at end of file 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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index be8299a7..9fc53754 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -14,8 +14,10 @@ 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.LetterReply 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 @@ -134,9 +136,39 @@ class LetterRepositoryImpl @Inject constructor( } override suspend fun fetchSentLetterWithReply(letterId: Long): Result = try { - val response = letterApiService.fetchSentLetterWithReply(letterId = letterId) +// 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.BEIGE, + 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(response.data.toDomain()) + Result.success(Unit) } else { Result.failure(Exception("Error: ${response.status}")) } 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/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt index c5d063e5..8acc6f57 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -4,6 +4,7 @@ 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 @@ -18,4 +19,5 @@ interface LetterRepository { 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 } \ 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/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/view/MyLetterDetailFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt index 17cea76e..c3190f8f 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,6 +2,7 @@ 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 @@ -11,7 +12,9 @@ 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 @@ -28,6 +31,8 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { val args: MyLetterDetailFragmentArgs by navArgs() args.letterId } + private var replyId: Long? = null + private var isReplyReported: Boolean? = null private val viewModel: LetterViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -64,6 +69,14 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { } } } + 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) + } + } } private fun initObservers() = with(binding) { @@ -86,7 +99,9 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { tvMyLetterDetailSentAt.text = formatDate(createdDateTime = data.createdAt) tvMyLetterDetailSentContent.text = data.sentContent cvMyLetterDetailSentContent.backgroundTintList = resources.getColorStateList(sentCardBackgroundColor, null) - if(data.reply != 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: 실제 유저 닉네임으로 변경하기 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 72c68316..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 @@ -6,18 +6,35 @@ 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 clickContentList by lazy { - listOf(binding.tvSquareReportAbuse, binding.tvSquareReportSpam, binding.tvSquareReportInappropriate, binding.tvSquareReportEtcNotClicked) + 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 { @@ -29,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) { @@ -36,20 +54,30 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { removeScreenBlur() dismiss() } - clickContentList.forEach { content -> + reportReasonsWithoutEtc.forEach { content -> content.setOnClickListener { - clickContentList.forEach { it.isSelected = false } + reportReasonsWithoutEtc.forEach { it.isSelected = false } + tvSquareReportEtcNotClicked.isSelected = false + llSquareReportEtcClicked.isVisible = false + tvSquareReportEtcNotClicked.isVisible = true content.isSelected = true - if(tvSquareReportEtcNotClicked.isSelected) { - llSquareReportEtcClicked.isVisible = true - tvSquareReportEtcNotClicked.isVisible = false - } else { - llSquareReportEtcClicked.isVisible = false - tvSquareReportEtcNotClicked.isVisible = true + btnSquareReportSubmit.isEnabled = true + reportType = when(content) { + tvSquareReportAbuse -> ReportLetterType.ABUSE + tvSquareReportSpam -> ReportLetterType.SPAM + else -> ReportLetterType.INAPPROPRIATE } } } - etSquareReportEtcReason.addTextChangedListener(object: TextWatcher { + 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?, @@ -57,6 +85,7 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { p2: Int, p3: Int ) = Unit + override fun onTextChanged( text: CharSequence?, start: Int, @@ -66,6 +95,32 @@ class SquareReportDialog: DialogFragment(R.layout.dialog_square_report) { 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 index 90b4b24b..a0b7ad57 100644 --- 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 @@ -13,12 +13,14 @@ 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 @@ -44,7 +46,8 @@ class LetterViewModel @Inject constructor( private val deferReplyLetterUseCase: DeferReplyLetterUseCase, private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase, private val getSentLettersUseCase: GetSentLettersUseCase, - private val getSentLetterWithReplyUseCase: GetSentLetterWithReplyUseCase + private val getSentLetterWithReplyUseCase: GetSentLetterWithReplyUseCase, + private val reportRepliedLetterUseCase: ReportRepliedLetterUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -169,4 +172,18 @@ class LetterViewModel @Inject constructor( } } } + + 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)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_square_report.xml b/app/src/main/res/layout/dialog_square_report.xml index 9d85c924..2b8e8aec 100644 --- a/app/src/main/res/layout/dialog_square_report.xml +++ b/app/src/main/res/layout/dialog_square_report.xml @@ -32,6 +32,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:gravity="center" android:background="@color/selector_square_report_textview_bg" android:paddingHorizontal="16dp" android:paddingVertical="12dp" From f910481812b8187ec6e29f682243d40312cedf30 Mon Sep 17 00:00:00 2001 From: se05503 Date: Fri, 6 Feb 2026 21:45:23 +0900 Subject: [PATCH 30/55] =?UTF-8?q?feat(letter):=20=EB=8B=B5=EC=9E=A5=20?= =?UTF-8?q?=EC=8A=A4=EB=A0=88=EB=93=9C=20=EC=82=AD=EC=A0=9C(=EB=82=B4=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80+=EB=8B=B5=EC=9E=A5=20=EC=82=AD=EC=A0=9C)=20A?= =?UTF-8?q?PI=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/LetterApiService.kt | 6 ++ .../data/repository/LetterRepositoryImpl.kt | 61 ++++++++------ .../app/domain/repository/LetterRepository.kt | 1 + .../letter/DeleteLetterThreadUseCase.kt | 8 ++ .../ui/square/view/MyLetterDetailFragment.kt | 82 ++++++++++++------- .../ui/square/viewmodel/LetterViewModel.kt | 18 +++- .../res/layout/fragment_my_letter_detail.xml | 1 + 7 files changed, 120 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/letter/DeleteLetterThreadUseCase.kt 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 index 0b8f31c8..aa058c2d 100644 --- a/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/LetterApiService.kt @@ -10,6 +10,7 @@ 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 @@ -54,4 +55,9 @@ interface LetterApiService { @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/repository/LetterRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt index 9fc53754..4aeab884 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -136,31 +136,31 @@ class LetterRepositoryImpl @Inject constructor( } 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.BEIGE, - 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) + 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.BEIGE, +// 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) } @@ -175,4 +175,15 @@ class LetterRepositoryImpl @Inject constructor( } 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/domain/repository/LetterRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt index 8acc6f57..976623f6 100644 --- a/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt +++ b/app/src/main/java/com/egobook/app/domain/repository/LetterRepository.kt @@ -20,4 +20,5 @@ interface LetterRepository { 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/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/ui/square/view/MyLetterDetailFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/MyLetterDetailFragment.kt index c3190f8f..6e379eb1 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 @@ -32,6 +32,7 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { args.letterId } private var replyId: Long? = null + private var threadId: Long? = null private var isReplyReported: Boolean? = null private val viewModel: LetterViewModel by activityViewModels() @@ -77,44 +78,63 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { applyScreenBlur(BlurLevel.BASE) } } + btnDeleteLetterThread.setOnClickListener { + viewModel.deleteLetterThread(threadId = threadId ?: -1L) + } } private fun initObservers() = with(binding) { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.sentLetterWithReply.collect { state -> - when(state) { - is UiState.Failure -> {} - UiState.Idle -> {} - UiState.Loading -> {} - is UiState.Success -> { - val data = state.data - val sentCardBackgroundColor = when(data.backgroundColor) { - LetterBackgroundColor.BEIGE -> 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() + 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.BEIGE -> 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 { - // TODO: 친구/익명 유무에 따라 보낸이 받는이 이름 변경하기 - tvMyLetterDetailAiGeneratedDescription.isVisible = false + llMyLetterDetailReplied.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() } } } 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 index a0b7ad57..cfe7054f 100644 --- 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 @@ -7,6 +7,7 @@ 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 @@ -47,7 +48,8 @@ class LetterViewModel @Inject constructor( private val giveUpReplyLetterUseCase: GiveUpReplyLetterUseCase, private val getSentLettersUseCase: GetSentLettersUseCase, private val getSentLetterWithReplyUseCase: GetSentLetterWithReplyUseCase, - private val reportRepliedLetterUseCase: ReportRepliedLetterUseCase + private val reportRepliedLetterUseCase: ReportRepliedLetterUseCase, + private val deleteLetterThreadUseCase: DeleteLetterThreadUseCase ): ViewModel() { private val _friendList = MutableStateFlow>>(UiState.Idle) @@ -186,4 +188,18 @@ class LetterViewModel @Inject constructor( } } } + + 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/layout/fragment_my_letter_detail.xml b/app/src/main/res/layout/fragment_my_letter_detail.xml index 14ad7677..12eb2320 100644 --- a/app/src/main/res/layout/fragment_my_letter_detail.xml +++ b/app/src/main/res/layout/fragment_my_letter_detail.xml @@ -38,6 +38,7 @@ tools:text="2025.12.25" /> Date: Mon, 9 Feb 2026 14:36:58 +0900 Subject: [PATCH 31/55] =?UTF-8?q?chore:=20=EA=B8=B0=ED=83=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=88=98=EC=A0=95=20-=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20=EC=B6=A9=EB=8F=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/repository/auth/AuthRepositoryImpl.kt | 6 +++--- .../app/data/repository/paging/FriendRepliesPagingSource.kt | 2 +- .../com/egobook/app/ui/home/repository/UserRepository.kt | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) 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 40deaaeb..4ea67830 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,15 +3,15 @@ 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.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/FriendRepliesPagingSource.kt b/app/src/main/java/com/egobook/app/data/repository/paging/FriendRepliesPagingSource.kt index 8a23d86b..51615724 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 } 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 4c1ff678..65e8a880 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 { private val userService by lazy { retrofit.create(NetworkUserService::class.java) } private val tendencyLevelService by lazy { retrofit.create(NetworkTendencyLevelService::class.java) } From fa1b60769400a7aac51d611575524951c34c1753 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 15:05:48 +0900 Subject: [PATCH 32/55] =?UTF-8?q?style(letter):=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20=EB=B0=B1=EC=97=94=EB=93=9C=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=84=9C=20=ED=99=95=EC=9D=B8=20-=20BEIGE=20?= =?UTF-8?q?=E2=86=92=20WHITE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/egobook/app/data/repository/LetterRepositoryImpl.kt | 5 ++--- .../app/domain/model/square/letter/LetterBackgroundColor.kt | 2 +- .../egobook/app/ui/square/view/ArrivedPendingLetterDialog.kt | 2 +- .../com/egobook/app/ui/square/view/LetterWriteFragment.kt | 2 +- .../com/egobook/app/ui/square/view/MyLetterDetailFragment.kt | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) 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 index 4aeab884..a6717aa4 100644 --- a/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt +++ b/app/src/main/java/com/egobook/app/data/repository/LetterRepositoryImpl.kt @@ -14,7 +14,6 @@ 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.LetterReply 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 @@ -75,7 +74,7 @@ class LetterRepositoryImpl @Inject constructor( content = "안녕하세요! 요즘 날씨가 부쩍 추워졌는데 잘 지내고 계신가요? 오늘 우연히 당신의 이야기를 듣고 문득 위로의 말을 전하고 싶어 펜을 들었습니다. 누구나 가끔은 마음이 무겁고 모든 게 버겁게 느껴지는 날이 있잖아요. 그럴 때일수록 스스로를 너무 다그치지 말고, 따뜻한 차 한 잔 마시며 쉬어갔으면 좋겠어요. 당신은 충분히 잘해내고 있고, 존재만으로도 소중한 사람이라는 걸 잊지 마세요. 내일은 오늘보다 조금 더 웃을 수 있는 여유가 생기길 진심으로 응원하겠습니다! 답장 기다릴게요.", arrivedAt = "2026-02-02T10:35:00+09:00", replyDeadlineAt = "2026-02-04T21:40:00+09:00", - letterColor = LetterBackgroundColor.BEIGE + letterColor = LetterBackgroundColor.WHITE ) ) val emptyMockData = ArrivedPendingLetter( @@ -148,7 +147,7 @@ class LetterRepositoryImpl @Inject constructor( // status = LetterStatus.ARRIVED, // 답장이 도착함 // mode = LetterMode.RANDOM, // sentContent = "안녕하세요, 고민이 있어 편지를 보냅니다. 요즘 업무량이 너무 많아서 번아웃이 온 것 같아요. 어떻게 극복하면 좋을까요?", -// backgroundColor = LetterBackgroundColor.BEIGE, +// backgroundColor = LetterBackgroundColor.WHITE, // createdAt = "2026-02-06T03:42:43.162307Z", // arrivedAt = "2026-02-06T03:42:43.162307Z", // // 답장 데이터 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 index 1cbc3a5c..5174b2c2 100644 --- 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 @@ -1,7 +1,7 @@ package com.egobook.app.domain.model.square.letter enum class LetterBackgroundColor(val value: String) { - BEIGE("BEIGE"), + WHITE("WHITE"), PINK("PINK"), GREEN("GREEN"), BLUE("BLUE"), 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 index 5d1e156b..1f5e91cf 100644 --- 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 @@ -48,7 +48,7 @@ class ArrivedPendingLetterDialog( tvArrivedPendingLetterFromLabel.text = "From ${letterInfo.fromLabel}" cvArrivedPendingLetterContainer.backgroundTintList = when(letterInfo.letterColor) { - LetterBackgroundColor.BEIGE -> resources.getColorStateList(R.color.letter_bg_beige, null) + 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) 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 index d68bc8fc..ee7603d4 100644 --- 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 @@ -40,7 +40,7 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { private val viewModel: LetterViewModel by activityViewModels() private lateinit var friendList: List - private var letterColor: LetterBackgroundColor = LetterBackgroundColor.BEIGE + private var letterColor: LetterBackgroundColor = LetterBackgroundColor.WHITE override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) 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 6e379eb1..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 @@ -96,7 +96,7 @@ class MyLetterDetailFragment : Fragment(R.layout.fragment_my_letter_detail) { val data = state.data threadId = data.threadId val sentCardBackgroundColor = when(data.backgroundColor) { - LetterBackgroundColor.BEIGE -> R.color.letter_bg_beige + 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 From 821a77a2baa4e7488796abb0e6b83c4e5041ee67 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 16:00:25 +0900 Subject: [PATCH 33/55] =?UTF-8?q?refactor(daily):=20=EC=9D=BC=EA=B0=84=20?= =?UTF-8?q?=EC=B9=AD=EC=B0=AC=EC=84=9C=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20-=20Paging3=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 10 ++- .../model/counseling/PraiseDailyResponse.kt | 30 ++++++++ .../model/counseling/PraiseMessageResponse.kt | 11 --- .../repository/CounselingRepositoryImpl.kt | 53 +++++--------- .../paging/DailyPraisePagingSource.kt | 29 ++++++++ .../model/counseling/PraiseDailyItem.kt | 7 ++ .../domain/repository/CounselingRepository.kt | 5 +- .../domain/usecase/GetDailyPraiseUseCase.kt | 5 +- .../adapter/CounselingDailyPraiseAdapter.kt | 20 +++--- .../ui/counseling/model/PraiseDailyModel.kt | 15 ++++ .../view/EgoRoomDailyPraiseFragment.kt | 70 ++++++++++++------- .../viewmodel/DailyPraiseViewModel.kt | 20 +++--- 12 files changed, 178 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt delete mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/PraiseMessageResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/repository/paging/DailyPraisePagingSource.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/PraiseDailyItem.kt create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/model/PraiseDailyModel.kt 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..f3bac06b 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,6 +1,7 @@ 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.PraiseDailyResponse import com.egobook.app.data.model.counseling.StatisticsResponse import com.egobook.app.data.model.counseling.WeeklyReportResponse import com.egobook.app.data.model.counseling.WeeklyReportStyleResponse @@ -11,8 +12,11 @@ import retrofit2.http.POST import retrofit2.http.Query interface CounselingApiService { - @GET("api/praise/daily") - suspend fun fetchDailyPraise(): Response> + @GET("/ego-room/praise/daily") + suspend fun fetchDailyPraise( + @Query("page") page: Int, + @Query("size") size: Int + ): ApiResponse @GET("api/reports/weekly") suspend fun fetchWeeklyReports(): Response> diff --git a/app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt new file mode 100644 index 00000000..13dc4b34 --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt @@ -0,0 +1,30 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.PraiseDailyItem +import com.google.gson.annotations.SerializedName + +data class PraiseDailyResponse( + @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(): PraiseDailyItem = PraiseDailyItem( + 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/repository/CounselingRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt index 122f48fa..837ae7c7 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,6 +1,10 @@ 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.repository.paging.DailyPraisePagingSource import com.egobook.app.domain.model.DailyData import com.egobook.app.domain.model.EmotionType import com.egobook.app.domain.model.MonthData @@ -11,49 +15,24 @@ 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.PraiseDailyItem import com.egobook.app.domain.repository.CounselingRepository +import kotlinx.coroutines.flow.Flow 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" - ), - PraiseMessage( - 3, - "작은 일에도 최선을 다하는 당신이 자랑스러워요. 때로는 쉬어가는 것도 용기라는 것을 잊지 마세요. 칭찬서의 내용이 적히는 자리입니다. 충분히 잘하고 있고, 앞으로도 당신의 걸음을 응원하겠습니다.", - "2026.01.01" + override fun getDailyPraise(size: Int): Flow> { + return Pager( + config = PagingConfig( + pageSize = size, + initialLoadSize = size, + enablePlaceholders = false ), - PraiseMessage( - 4, - "실패를 두려워하지 않는 용기가 멋집니다. 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리", - "2026.01.02" - ), - PraiseMessage( - 5, - "오늘 하루도 정말 고생 많으셨습니다. 푹 쉬세요! 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리 칭찬서의 내용이 적히는 자리", - "2026.01.03" - ) - ) - Result.success(dummyData) - } catch (e: Exception) { - Result.failure(e) + pagingSourceFactory = { + DailyPraisePagingSource(apiService = apiService) + } + ).flow } override suspend fun getWeeklyReport(): Result> = try { 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..ad811943 --- /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.PraiseDailyItem + +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.fetchDailyPraise(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/domain/model/counseling/PraiseDailyItem.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/PraiseDailyItem.kt new file mode 100644 index 00000000..47b882ce --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/PraiseDailyItem.kt @@ -0,0 +1,7 @@ +package com.egobook.app.domain.model.counseling + +data class PraiseDailyItem( + val id: Int, + val diaryDate: String, + val isRead: Boolean +) 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..ef044fde 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,13 +1,16 @@ package com.egobook.app.domain.repository +import androidx.paging.PagingData import com.egobook.app.domain.model.PraiseMessage import com.egobook.app.domain.model.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.PraiseDailyItem +import kotlinx.coroutines.flow.Flow interface CounselingRepository { - suspend fun getDailyPraise(): Result> + fun getDailyPraise(size: Int): Flow> suspend fun getWeeklyReport(): Result> suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result 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 index 907eb8da..3b5226e9 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt @@ -1,11 +1,14 @@ package com.egobook.app.domain.usecase +import androidx.paging.PagingData import com.egobook.app.domain.model.PraiseMessage +import com.egobook.app.domain.model.counseling.PraiseDailyItem import com.egobook.app.domain.repository.CounselingRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class GetDailyPraiseUseCase @Inject constructor( private val repository: CounselingRepository ) { - suspend operator fun invoke(): Result> = repository.getDailyPraise() + 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/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt index a4a86e62..0edd769b 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 @@ -3,20 +3,21 @@ package com.egobook.app.ui.counseling.adapter import android.view.LayoutInflater import android.view.ViewGroup 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.PraiseDailyModel import com.egobook.app.ui.counseling.model.PraiseMessageModel -class CounselingDailyPraiseAdapter: ListAdapter(diffUtil) { +class CounselingDailyPraiseAdapter: PagingDataAdapter(diffUtil) { 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 root.setOnClickListener { cvCounselingDailyPraiseContent.isVisible = !cvCounselingDailyPraiseContent.isVisible ivCounselingDailyPraiseToggle.setImageResource(if(cvCounselingDailyPraiseContent.isVisible) R.drawable.ic_chevron_up else R.drawable.ic_chevron_down) @@ -35,16 +36,19 @@ 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/model/PraiseDailyModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/PraiseDailyModel.kt new file mode 100644 index 00000000..58bca05a --- /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.PraiseDailyItem + +data class PraiseDailyModel( + val id: Int, + val diaryDate: String, + val isRead: Boolean +) + +fun PraiseDailyItem.toPresentation(): PraiseDailyModel = PraiseDailyModel( + id = id, + diaryDate = diaryDate, + isRead = isRead +) 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..8242d5f4 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 @@ -10,6 +10,7 @@ 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 @@ -53,10 +54,16 @@ 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.updateNotificationStatus( + type = NotificationType.DAILY_PRAISE, + isEnabled = false + ) } else { - viewModel.updateNotificationStatus(type = NotificationType.DAILY_PRAISE, isEnabled = true) + viewModel.updateNotificationStatus( + type = NotificationType.DAILY_PRAISE, + isEnabled = true + ) } } } @@ -65,25 +72,23 @@ 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 { + 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.notificationStatus.collect { state -> - when(state) { + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} @@ -96,14 +101,15 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } launch { viewModel.updateNotificationResult.collect { state -> - when(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() } } @@ -115,17 +121,29 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra 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.fetchDailyPraise(size = 10) viewModel.fetchNotificationStatus() } } \ 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..d54bc715 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,9 +2,12 @@ package com.egobook.app.ui.counseling.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.model.NotificationType import com.egobook.app.domain.usecase.GetDailyPraiseUseCase -import com.egobook.app.ui.counseling.model.PraiseMessageModel +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 @@ -14,6 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -23,17 +27,13 @@ class DailyPraiseViewModel @Inject constructor( private val notificationDelegate: NotificationDelegate ): ViewModel() { - private val _dailyPraiseList = MutableStateFlow>>(UiState.Idle) - val dailyPraise = _dailyPraiseList.asStateFlow() + private val _dailyPraiseList = MutableStateFlow>(PagingData.empty()) + val dailyPraiseList = _dailyPraiseList.asStateFlow() - fun fetchDailyPraise() { + fun fetchDailyPraise(size: Int) { viewModelScope.launch { - _dailyPraiseList.value = UiState.Loading - getDailyPraiseUseCase().onSuccess { domainList -> - val uiList = domainList.map { it.toPresentation() } - _dailyPraiseList.value = UiState.Success(uiList) - }.onFailure { error -> - _dailyPraiseList.value = UiState.Failure(error.message) + getDailyPraiseUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> + _dailyPraiseList.value = pagingData.map { it.toPresentation() } } } } From aacbfa6167398baec4a62d0a35668c157fb6bec5 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 18:04:31 +0900 Subject: [PATCH 34/55] =?UTF-8?q?feat(daily):=20=EC=9D=BC=EA=B0=84=20?= =?UTF-8?q?=EC=B9=AD=EC=B0=AC=EC=84=9C=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20-=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20-=20=EC=96=B4=EB=8C=91?= =?UTF-8?q?=ED=84=B0=EC=97=90=20=EB=82=B4=EC=9E=A5=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 13 ++++-- .../model/counseling/DailyPraiseResponse.kt | 42 ++++++++++++++++++ ...ilyResponse.kt => DailyPraisesResponse.kt} | 6 +-- .../repository/CounselingRepositoryImpl.kt | 21 ++++++--- .../paging/DailyPraisePagingSource.kt | 10 ++--- .../model/counseling/CounselingRewardType.kt | 5 +++ .../{PraiseDailyItem.kt => DailyPraise.kt} | 2 +- .../model/counseling/DailyPraiseDetail.kt | 15 +++++++ .../domain/repository/CounselingRepository.kt | 7 +-- .../egoroom/GetDailyPraiseByDateUseCase.kt | 8 ++++ .../{ => egoroom}/GetDailyPraiseUseCase.kt | 7 ++- .../adapter/CounselingDailyPraiseAdapter.kt | 44 ++++++++++++++++--- .../model/DailyPraiseDetailModel.kt | 33 ++++++++++++++ .../ui/counseling/model/PraiseDailyModel.kt | 4 +- .../view/EgoRoomDailyPraiseFragment.kt | 20 ++++++++- .../viewmodel/DailyPraiseViewModel.kt | 23 +++++++++- .../main/res/layout/item_square_letter.xml | 3 +- 17 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/DailyPraiseResponse.kt rename app/src/main/java/com/egobook/app/data/model/counseling/{PraiseDailyResponse.kt => DailyPraisesResponse.kt} (77%) create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/CounselingRewardType.kt rename app/src/main/java/com/egobook/app/domain/model/counseling/{PraiseDailyItem.kt => DailyPraise.kt} (80%) create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraiseDetail.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseByDateUseCase.kt rename app/src/main/java/com/egobook/app/domain/usecase/{ => egoroom}/GetDailyPraiseUseCase.kt (50%) create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/model/DailyPraiseDetailModel.kt 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 f3bac06b..96a51312 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,7 +1,8 @@ package com.egobook.app.data.api import com.egobook.app.data.model.ApiResponse -import com.egobook.app.data.model.counseling.PraiseDailyResponse +import com.egobook.app.data.model.counseling.DailyPraiseResponse +import com.egobook.app.data.model.counseling.DailyPraisesResponse import com.egobook.app.data.model.counseling.StatisticsResponse import com.egobook.app.data.model.counseling.WeeklyReportResponse import com.egobook.app.data.model.counseling.WeeklyReportStyleResponse @@ -9,14 +10,20 @@ import com.egobook.app.domain.model.ReportStyle import retrofit2.Response import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Path import retrofit2.http.Query interface CounselingApiService { @GET("/ego-room/praise/daily") - suspend fun fetchDailyPraise( + suspend fun fetchDailyPraises( @Query("page") page: Int, @Query("size") size: Int - ): ApiResponse + ): ApiResponse + + @GET("/ego-room/praise/daily/{date}") + suspend fun fetchDailyPraiseByDate( + @Path("date") date: String + ): Response @GET("api/reports/weekly") suspend fun fetchWeeklyReports(): Response> 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/PraiseDailyResponse.kt b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt similarity index 77% rename from app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt rename to app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt index 13dc4b34..ae8607de 100644 --- a/app/src/main/java/com/egobook/app/data/model/counseling/PraiseDailyResponse.kt +++ b/app/src/main/java/com/egobook/app/data/model/counseling/DailyPraisesResponse.kt @@ -1,9 +1,9 @@ package com.egobook.app.data.model.counseling -import com.egobook.app.domain.model.counseling.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise import com.google.gson.annotations.SerializedName -data class PraiseDailyResponse( +data class DailyPraisesResponse( @SerializedName("content") val content: List, @SerializedName("page") @@ -23,7 +23,7 @@ data class PraiseDailyItemResponse( val isRead: Boolean ) -fun PraiseDailyItemResponse.toDomain(): PraiseDailyItem = PraiseDailyItem( +fun PraiseDailyItemResponse.toDomain(): DailyPraise = DailyPraise( id = id, diaryDate = diaryDate, isRead = isRead 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 837ae7c7..24325437 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 @@ -4,25 +4,25 @@ 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.toDomain import com.egobook.app.data.repository.paging.DailyPraisePagingSource 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.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise +import com.egobook.app.domain.model.counseling.DailyPraiseDetail import com.egobook.app.domain.repository.CounselingRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class CounselingRepositoryImpl @Inject constructor(private val apiService: CounselingApiService) : - CounselingRepository { - override fun getDailyPraise(size: Int): Flow> { +class CounselingRepositoryImpl @Inject constructor(private val apiService: CounselingApiService) : CounselingRepository { + override fun getDailyPraise(size: Int): Flow> { return Pager( config = PagingConfig( pageSize = size, @@ -35,6 +35,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns ).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 getWeeklyReport(): Result> = try { // val response = apiService.fetchWeeklyReports() // if(response.isSuccessful && response.body() != null) { 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 index ad811943..b0d4a787 100644 --- 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 @@ -4,19 +4,19 @@ 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.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise class DailyPraisePagingSource(private val apiService: CounselingApiService) : - PagingSource() { - override fun getRefreshKey(state: PagingState): Int { + PagingSource() { + override fun getRefreshKey(state: PagingState): Int { return 1 } - override suspend fun load(params: LoadParams): LoadResult { + override suspend fun load(params: LoadParams): LoadResult { return try { val page = params.key ?: 1 val size = params.loadSize - val result = apiService.fetchDailyPraise(page = page, size = size).data + 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, 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/PraiseDailyItem.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt similarity index 80% rename from app/src/main/java/com/egobook/app/domain/model/counseling/PraiseDailyItem.kt rename to app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt index 47b882ce..307cf768 100644 --- a/app/src/main/java/com/egobook/app/domain/model/counseling/PraiseDailyItem.kt +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/DailyPraise.kt @@ -1,6 +1,6 @@ package com.egobook.app.domain.model.counseling -data class PraiseDailyItem( +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/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index ef044fde..ea53105a 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,16 +1,17 @@ package com.egobook.app.domain.repository import androidx.paging.PagingData -import com.egobook.app.domain.model.PraiseMessage import com.egobook.app.domain.model.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.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise +import com.egobook.app.domain.model.counseling.DailyPraiseDetail import kotlinx.coroutines.flow.Flow interface CounselingRepository { - fun getDailyPraise(size: Int): Flow> + fun getDailyPraise(size: Int): Flow> + suspend fun getDailyPraiseByDate(date: String): Result suspend fun getWeeklyReport(): Result> suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result 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/GetDailyPraiseUseCase.kt b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt similarity index 50% rename from app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt rename to app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt index 3b5226e9..82b627ec 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetDailyPraiseUseCase.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyPraiseUseCase.kt @@ -1,8 +1,7 @@ -package com.egobook.app.domain.usecase +package com.egobook.app.domain.usecase.egoroom import androidx.paging.PagingData -import com.egobook.app.domain.model.PraiseMessage -import com.egobook.app.domain.model.counseling.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise import com.egobook.app.domain.repository.CounselingRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -10,5 +9,5 @@ import javax.inject.Inject class GetDailyPraiseUseCase @Inject constructor( private val repository: CounselingRepository ) { - operator fun invoke(size: Int): Flow> = repository.getDailyPraise(size = size) + 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/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt b/app/src/main/java/com/egobook/app/ui/counseling/adapter/CounselingDailyPraiseAdapter.kt index 0edd769b..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,25 +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.DailyPraiseDetailModel import com.egobook.app.ui.counseling.model.PraiseDailyModel -import com.egobook.app.ui.counseling.model.PraiseMessageModel -class CounselingDailyPraiseAdapter: PagingDataAdapter(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: 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) + } } } @@ -42,6 +67,15 @@ class CounselingDailyPraiseAdapter: PagingDataAdapter() { override fun areItemsTheSame(oldItem: PraiseDailyModel, newItem: PraiseDailyModel): Boolean { 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 index 58bca05a..5f062cb8 100644 --- 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 @@ -1,6 +1,6 @@ package com.egobook.app.ui.counseling.model -import com.egobook.app.domain.model.counseling.PraiseDailyItem +import com.egobook.app.domain.model.counseling.DailyPraise data class PraiseDailyModel( val id: Int, @@ -8,7 +8,7 @@ data class PraiseDailyModel( val isRead: Boolean ) -fun PraiseDailyItem.toPresentation(): PraiseDailyModel = PraiseDailyModel( +fun DailyPraise.toPresentation(): PraiseDailyModel = PraiseDailyModel( id = id, diaryDate = diaryDate, isRead = isRead 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 8242d5f4..186720f0 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 @@ -17,6 +17,7 @@ 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.DailyPraiseDetailModel import com.egobook.app.ui.counseling.viewmodel.DailyPraiseViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState @@ -28,7 +29,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 @@ -115,6 +118,19 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } } } + 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) + } + } + } + } } } } @@ -143,7 +159,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } private fun fetchData() { - viewModel.fetchDailyPraise(size = 10) + viewModel.fetchDailyPraises(size = 10) viewModel.fetchNotificationStatus() } } \ 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 d54bc715..898b18fc 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 @@ -6,16 +6,20 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map import com.egobook.app.domain.model.NotificationType -import com.egobook.app.domain.usecase.GetDailyPraiseUseCase +import com.egobook.app.domain.usecase.egoroom.GetDailyPraiseByDateUseCase +import com.egobook.app.domain.usecase.egoroom.GetDailyPraiseUseCase +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 @@ -24,13 +28,14 @@ import javax.inject.Inject @HiltViewModel class DailyPraiseViewModel @Inject constructor( private val getDailyPraiseUseCase: GetDailyPraiseUseCase, + private val getDailyPraiseByDateUseCase: GetDailyPraiseByDateUseCase, private val notificationDelegate: NotificationDelegate ): ViewModel() { private val _dailyPraiseList = MutableStateFlow>(PagingData.empty()) val dailyPraiseList = _dailyPraiseList.asStateFlow() - fun fetchDailyPraise(size: Int) { + fun fetchDailyPraises(size: Int) { viewModelScope.launch { getDailyPraiseUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> _dailyPraiseList.value = pagingData.map { it.toPresentation() } @@ -38,6 +43,20 @@ class DailyPraiseViewModel @Inject constructor( } } + 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 -> + _dailyPraiseByDate.emit(UiState.Failure(error.message)) + } + } + } + val notificationStatus: StateFlow> = notificationDelegate.notificationStatus fun fetchNotificationStatus() { diff --git a/app/src/main/res/layout/item_square_letter.xml b/app/src/main/res/layout/item_square_letter.xml index 1ce759a2..880966bb 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,7 @@ 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:layout_marginStart="8dp" app:layout_constraintTop_toTopOf="@id/iv_item_letter" app:layout_constraintStart_toEndOf="@id/iv_item_letter" From 6c0f065562a37d30ad0c75a136c32ce66ef9f2b0 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 19:47:29 +0900 Subject: [PATCH 35/55] =?UTF-8?q?feat(egoroom):=20=EC=9D=BC=EA=B0=84/?= =?UTF-8?q?=EC=A3=BC=EA=B0=84=20=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=8B=A0=20=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 4 +++ .../DailyAndWeeklyNotificationResponse.kt | 18 ++++++++++ .../repository/CounselingRepositoryImpl.kt | 12 +++++++ .../counseling/DailyAndWeeklyNotification.kt | 6 ++++ .../domain/repository/CounselingRepository.kt | 2 ++ .../GetDailyAndWeeklyNotificationUseCase.kt | 8 +++++ .../model/DailyAndWeeklyNotificationModel.kt | 13 +++++++ .../view/EgoRoomDailyPraiseFragment.kt | 14 +++++--- .../view/EgoRoomWeeklyReportFragment.kt | 15 +++++--- .../viewmodel/CounselingViewModel.kt | 35 +++++++++++++++++++ 10 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/DailyAndWeeklyNotificationResponse.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/DailyAndWeeklyNotification.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetDailyAndWeeklyNotificationUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/model/DailyAndWeeklyNotificationModel.kt create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt 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 96a51312..62a37c3f 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,6 +1,7 @@ package com.egobook.app.data.api import com.egobook.app.data.model.ApiResponse +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.StatisticsResponse @@ -25,6 +26,9 @@ interface CounselingApiService { @Path("date") date: String ): Response + @GET("/ego-room/ai/toggle") + suspend fun fetchDailyAndWeeklyNotification(): ApiResponse + @GET("api/reports/weekly") suspend fun fetchWeeklyReports(): Response> 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/repository/CounselingRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt index 24325437..7c8647b7 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 @@ -15,6 +15,7 @@ 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.repository.CounselingRepository @@ -46,6 +47,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns 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 getWeeklyReport(): Result> = try { // val response = apiService.fetchWeeklyReports() // if(response.isSuccessful && response.body() != null) { 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/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index ea53105a..5590758b 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 @@ -5,6 +5,7 @@ import com.egobook.app.domain.model.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 kotlinx.coroutines.flow.Flow @@ -16,4 +17,5 @@ interface CounselingRepository { suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result suspend fun getStatistics(): Result + suspend fun getDailyAndWeeklyNotification(): Result } \ 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/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/view/EgoRoomDailyPraiseFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt index 186720f0..6b1989a1 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,6 +6,7 @@ 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 @@ -17,7 +18,9 @@ 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.CounselingViewModel import com.egobook.app.ui.counseling.viewmodel.DailyPraiseViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState @@ -29,6 +32,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private lateinit var binding: FragmentEgoRoomDailyPraiseBinding private val viewModel: DailyPraiseViewModel by viewModels() + private val counselingViewModel: CounselingViewModel by activityViewModels() private val counselingDailyPraiseAdapter = CounselingDailyPraiseAdapter { diaryDate -> viewModel.fetchDailyPraiseByData(date = diaryDate) } @@ -90,13 +94,13 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } } launch { - viewModel.notificationStatus.collect { state -> + counselingViewModel.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) } } @@ -136,7 +140,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { - this@EgoRoomDailyPraiseFragment.isNotificationEnabled = isEnabled +// this@EgoRoomDailyPraiseFragment.isNotificationEnabled = isEnabled if (isEnabled) { tvCounselingDailyPraiseNotification.text = getString(R.string.counseling_daily_praise_notification_on) @@ -160,6 +164,6 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private fun fetchData() { viewModel.fetchDailyPraises(size = 10) - viewModel.fetchNotificationStatus() + counselingViewModel.getDailyAndWeeklyNotification() } } \ No newline at end of file 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..1a0045a5 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,6 +6,7 @@ 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 @@ -18,17 +19,21 @@ 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.CounselingViewModel 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.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 counselingViewModel: CounselingViewModel by activityViewModels() private val counselingWeeklyReportAdapter = CounselingWeeklyReportAdapter { item -> val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(weeklyReportItem = item) findNavController().navigate(action) @@ -101,14 +106,14 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } } launch { - viewModel.notificationStatus.collect { state -> + counselingViewModel.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) } } } @@ -160,7 +165,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { - this@EgoRoomWeeklyReportFragment.isNotificationEnabled = isEnabled +// 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)) @@ -177,8 +182,8 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } private fun fetchData() { + counselingViewModel.getDailyAndWeeklyNotification() viewModel.fetchWeeklyReport() - viewModel.fetchNotificationStatus() viewModel.fetchWeeklyReportStyle() } } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt new file mode 100644 index 00000000..c26c24f4 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt @@ -0,0 +1,35 @@ +package com.egobook.app.ui.counseling.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification +import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel +import com.egobook.app.ui.counseling.model.toPresentation +import com.egobook.app.util.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CounselingViewModel @Inject constructor( + private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase +): ViewModel() { + + 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) + } + } + } +} + From 9b2677eb9b0e4d989d3633315e3d182a321a6d1c Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 20:10:42 +0900 Subject: [PATCH 36/55] =?UTF-8?q?feat(daily):=20=EC=9D=BC=EA=B0=84?= =?UTF-8?q?=EC=B9=AD=EC=B0=AC=EC=84=9C=20=EC=88=98=EC=8B=A0=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=84=A4=EC=A0=95=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 9 +++++++ .../CounselingNotificationRequest.kt | 8 +++++++ .../repository/CounselingRepositoryImpl.kt | 12 ++++++++++ .../domain/repository/CounselingRepository.kt | 1 + .../UpdateDailyPraiseNotificationUseCase.kt | 8 +++++++ .../view/EgoRoomDailyPraiseFragment.kt | 13 ++++------ .../viewmodel/DailyPraiseViewModel.kt | 24 +++++++++---------- 7 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/CounselingNotificationRequest.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateDailyPraiseNotificationUseCase.kt 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 62a37c3f..49e0a39f 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,6 +1,7 @@ package com.egobook.app.data.api 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 @@ -9,7 +10,9 @@ import com.egobook.app.data.model.counseling.WeeklyReportResponse import com.egobook.app.data.model.counseling.WeeklyReportStyleResponse import com.egobook.app.domain.model.ReportStyle 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 @@ -29,6 +32,12 @@ interface CounselingApiService { @GET("/ego-room/ai/toggle") suspend fun fetchDailyAndWeeklyNotification(): ApiResponse + @PATCH("/ego-room/praise/daily") + suspend fun updateDailyPraiseNotification( + @Body request: CounselingNotificationRequest + ): ApiResponse + + @GET("api/reports/weekly") suspend fun fetchWeeklyReports(): Response> 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..cf97e765 --- /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 enabled: Boolean +) 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 7c8647b7..a55ec354 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 @@ -4,6 +4,7 @@ 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.toDomain import com.egobook.app.data.repository.paging.DailyPraisePagingSource import com.egobook.app.domain.model.DailyData @@ -58,6 +59,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns Result.failure(e) } + override suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result = try { + val response = apiService.updateDailyPraiseNotification(request = CounselingNotificationRequest(enabled = isEnabled)) + 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) { 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 5590758b..c2dc416e 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 @@ -18,4 +18,5 @@ interface CounselingRepository { suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result suspend fun getStatistics(): Result suspend fun getDailyAndWeeklyNotification(): Result + suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result } \ 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/ui/counseling/view/EgoRoomDailyPraiseFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomDailyPraiseFragment.kt index 6b1989a1..f7d0d9bb 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 @@ -62,13 +62,11 @@ 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, + viewModel.updateDailyPraiseNotification( isEnabled = false ) } else { - viewModel.updateNotificationStatus( - type = NotificationType.DAILY_PRAISE, + viewModel.updateDailyPraiseNotification( isEnabled = true ) } @@ -107,7 +105,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } } launch { - viewModel.updateNotificationResult.collect { state -> + viewModel.updateNotificationStatus.collect { state -> when (state) { is UiState.Failure -> {} UiState.Idle -> {} @@ -115,8 +113,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra 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() } } @@ -140,7 +137,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { -// this@EgoRoomDailyPraiseFragment.isNotificationEnabled = isEnabled + this@EgoRoomDailyPraiseFragment.isNotificationEnabled = isEnabled if (isEnabled) { tvCounselingDailyPraiseNotification.text = getString(R.string.counseling_daily_praise_notification_on) 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 898b18fc..c24d3127 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 @@ -8,6 +8,7 @@ import androidx.paging.map import com.egobook.app.domain.model.NotificationType 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.DailyPraiseDetailModel import com.egobook.app.ui.counseling.model.PraiseDailyModel import com.egobook.app.ui.counseling.model.toPresentation @@ -29,7 +30,8 @@ import javax.inject.Inject class DailyPraiseViewModel @Inject constructor( private val getDailyPraiseUseCase: GetDailyPraiseUseCase, private val getDailyPraiseByDateUseCase: GetDailyPraiseByDateUseCase, - private val notificationDelegate: NotificationDelegate + private val notificationDelegate: NotificationDelegate, + private val updateDailyPraiseNotificationUseCase: UpdateDailyPraiseNotificationUseCase ): ViewModel() { private val _dailyPraiseList = MutableStateFlow>(PagingData.empty()) @@ -57,19 +59,17 @@ class DailyPraiseViewModel @Inject constructor( } } - val notificationStatus: StateFlow> = notificationDelegate.notificationStatus + private val _updateNotificationStatus = MutableSharedFlow>() + val updateNotificationStatus = _updateNotificationStatus.asSharedFlow() - fun fetchNotificationStatus() { + fun updateDailyPraiseNotification(isEnabled: Boolean) { viewModelScope.launch { - notificationDelegate.fetchNotificationStatus() - } - } - - val updateNotificationResult: SharedFlow> = notificationDelegate.updateNotificationResult - - fun updateNotificationStatus(type: NotificationType, 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)) + } } } From 1c44f0e3a5778792e26596880d695def8dad11ba Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 20:34:10 +0900 Subject: [PATCH 37/55] =?UTF-8?q?feat(daily):=20=EC=A3=BC=EA=B0=84?= =?UTF-8?q?=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EC=88=98=EC=8B=A0=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=84=A4=EC=A0=95=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 4 ++ .../CounselingNotificationRequest.kt | 2 +- .../repository/CounselingRepositoryImpl.kt | 13 +++++- .../domain/repository/CounselingRepository.kt | 1 + .../UpdateWeeklyReportNotificationUseCase.kt | 8 ++++ .../view/EgoRoomWeeklyReportFragment.kt | 14 ++----- .../viewmodel/DailyPraiseViewModel.kt | 6 --- .../viewmodel/WeeklyReportViewModel.kt | 38 +++++++---------- .../delegate/NotificationDelegate.kt | 42 ------------------- 9 files changed, 45 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/UpdateWeeklyReportNotificationUseCase.kt delete mode 100644 app/src/main/java/com/egobook/app/ui/notification/delegate/NotificationDelegate.kt 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 49e0a39f..67a2dea6 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 @@ -37,6 +37,10 @@ interface CounselingApiService { @Body request: CounselingNotificationRequest ): ApiResponse + @PATCH("/ego-room/counsel/weekly") + suspend fun updateWeeklyReportNotification( + @Body request: CounselingNotificationRequest + ): ApiResponse @GET("api/reports/weekly") suspend fun fetchWeeklyReports(): Response> 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 index cf97e765..0cc85b31 100644 --- 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 @@ -4,5 +4,5 @@ import com.google.gson.annotations.SerializedName data class CounselingNotificationRequest( @SerializedName("enabled") - val enabled: Boolean + val isEnabled: Boolean ) 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 a55ec354..d3ec49da 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 @@ -60,7 +60,7 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns } override suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result = try { - val response = apiService.updateDailyPraiseNotification(request = CounselingNotificationRequest(enabled = isEnabled)) + val response = apiService.updateDailyPraiseNotification(request = CounselingNotificationRequest(isEnabled = isEnabled)) if(response.status == 200) { Result.success(isEnabled) } else { @@ -70,6 +70,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns Result.failure(e) } + 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 suspend fun getWeeklyReport(): Result> = try { // val response = apiService.fetchWeeklyReports() // if(response.isSuccessful && response.body() != null) { 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 c2dc416e..5fc0c17d 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 @@ -19,4 +19,5 @@ interface CounselingRepository { suspend fun getStatistics(): Result suspend fun getDailyAndWeeklyNotification(): Result suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result + suspend fun updateWeeklyReportNotification(isEnabled: Boolean): Result } \ 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/ui/counseling/view/EgoRoomWeeklyReportFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt index 1a0045a5..5e34ae71 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 @@ -72,15 +72,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) } } } @@ -119,7 +113,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } } launch { - viewModel.updateNotificationResult.collect { state -> + viewModel.updateNotificationStatus.collect { state -> when(state) { is UiState.Failure -> {} UiState.Idle -> {} @@ -165,7 +159,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } private fun updateNotificationUi(isEnabled: Boolean) = with(binding) { -// this@EgoRoomWeeklyReportFragment.isNotificationEnabled = isEnabled + 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)) 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 c24d3127..eb875a20 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 @@ -5,21 +5,16 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map -import com.egobook.app.domain.model.NotificationType 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.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 @@ -30,7 +25,6 @@ import javax.inject.Inject class DailyPraiseViewModel @Inject constructor( private val getDailyPraiseUseCase: GetDailyPraiseUseCase, private val getDailyPraiseByDateUseCase: GetDailyPraiseByDateUseCase, - private val notificationDelegate: NotificationDelegate, private val updateDailyPraiseNotificationUseCase: UpdateDailyPraiseNotificationUseCase ): ViewModel() { 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..2fcca025 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,22 +2,18 @@ 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.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.usecase.egoroom.UpdateWeeklyReportNotificationUseCase 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.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.launch @@ -28,7 +24,7 @@ class WeeklyReportViewModel @Inject constructor( private val getWeeklyReportUseCase: GetWeeklyReportUseCase, private val getWeeklyReportStyleUseCase: GetWeeklyReportStyleUseCase, private val updateWeeklyReportStyleUseCase: UpdateWeeklyReportStyleUseCase, - private val notificationDelegate: NotificationDelegate + private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase ): ViewModel() { private val _weeklyReportList = MutableStateFlow>>(UiState.Idle) @@ -45,6 +41,19 @@ class WeeklyReportViewModel @Inject constructor( } } + 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 -> + _updateNotificationStatus.emit(UiState.Failure(error.message)) + } + } + } + private val _weeklyReportStyle = MutableStateFlow>(UiState.Idle) val weeklyReportStyle = _weeklyReportStyle.asStateFlow() @@ -71,21 +80,4 @@ class WeeklyReportViewModel @Inject constructor( } } } - - - val notificationStatus: StateFlow> = notificationDelegate.notificationStatus - - fun fetchNotificationStatus() { - viewModelScope.launch { - notificationDelegate.fetchNotificationStatus() - } - } - - val updateNotificationResult: SharedFlow> = notificationDelegate.updateNotificationResult - - fun updateNotificationStatus(type: NotificationType, isEnabled: Boolean) { - viewModelScope.launch { - notificationDelegate.updateNotificationStatus(type = type, isEnabled = isEnabled) - } - } } \ No newline at end of file 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 From 149cb2ba092679c81a269a7a193d3f1348875491 Mon Sep 17 00:00:00 2001 From: se05503 Date: Mon, 9 Feb 2026 20:39:02 +0900 Subject: [PATCH 38/55] =?UTF-8?q?refactor(egoroom):=20=EB=B7=B0=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EB=B6=84=EB=A6=AC=20-=20usecase=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=81=EA=B0=81=EC=9D=98=20=EB=B7=B0=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=97=90=20=EC=A3=BC=EC=9E=85=ED=95=98=EB=8A=94=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20-=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=EC=9D=B4=20=EA=B0=81=EA=B0=81?= =?UTF-8?q?=EC=9D=98=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=A7=9E=EB=8A=94=20?= =?UTF-8?q?SRP=20=EC=A4=80=EC=88=98=20=EB=AA=A9=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/EgoRoomDailyPraiseFragment.kt | 6 ++-- .../view/EgoRoomWeeklyReportFragment.kt | 6 ++-- .../viewmodel/CounselingViewModel.kt | 35 ------------------- .../viewmodel/DailyPraiseViewModel.kt | 17 +++++++++ .../viewmodel/WeeklyReportViewModel.kt | 17 +++++++++ 5 files changed, 38 insertions(+), 43 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt 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 f7d0d9bb..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 @@ -20,7 +20,6 @@ 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.CounselingViewModel import com.egobook.app.ui.counseling.viewmodel.DailyPraiseViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState @@ -32,7 +31,6 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private lateinit var binding: FragmentEgoRoomDailyPraiseBinding private val viewModel: DailyPraiseViewModel by viewModels() - private val counselingViewModel: CounselingViewModel by activityViewModels() private val counselingDailyPraiseAdapter = CounselingDailyPraiseAdapter { diaryDate -> viewModel.fetchDailyPraiseByData(date = diaryDate) } @@ -92,7 +90,7 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra } } launch { - counselingViewModel.dailyAndWeeklyNotification.collect { state -> + viewModel.dailyAndWeeklyNotification.collect { state -> when (state) { is UiState.Failure -> {} UiState.Idle -> {} @@ -161,6 +159,6 @@ class EgoRoomDailyPraiseFragment : Fragment(R.layout.fragment_ego_room_daily_pra private fun fetchData() { viewModel.fetchDailyPraises(size = 10) - counselingViewModel.getDailyAndWeeklyNotification() + viewModel.getDailyAndWeeklyNotification() } } \ No newline at end of file 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 5e34ae71..b15581b4 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 @@ -21,7 +21,6 @@ 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.CounselingViewModel import com.egobook.app.ui.counseling.viewmodel.WeeklyReportViewModel import com.egobook.app.ui.notification.model.NotificationModel import com.egobook.app.util.UiState @@ -33,7 +32,6 @@ import kotlin.getValue class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_report) { private lateinit var binding: FragmentEgoRoomWeeklyReportBinding private val viewModel: WeeklyReportViewModel by viewModels() - private val counselingViewModel: CounselingViewModel by activityViewModels() private val counselingWeeklyReportAdapter = CounselingWeeklyReportAdapter { item -> val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(weeklyReportItem = item) findNavController().navigate(action) @@ -100,7 +98,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } } launch { - counselingViewModel.dailyAndWeeklyNotification.collect { state -> + viewModel.dailyAndWeeklyNotification.collect { state -> when(state) { is UiState.Failure -> {} UiState.Idle -> {} @@ -176,7 +174,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } private fun fetchData() { - counselingViewModel.getDailyAndWeeklyNotification() + viewModel.getDailyAndWeeklyNotification() viewModel.fetchWeeklyReport() viewModel.fetchWeeklyReportStyle() } diff --git a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt deleted file mode 100644 index c26c24f4..00000000 --- a/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/CounselingViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.egobook.app.ui.counseling.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.egobook.app.domain.model.counseling.DailyAndWeeklyNotification -import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase -import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel -import com.egobook.app.ui.counseling.model.toPresentation -import com.egobook.app.util.UiState -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class CounselingViewModel @Inject constructor( - private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase -): ViewModel() { - - 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) - } - } - } -} - 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 eb875a20..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 @@ -5,9 +5,11 @@ import androidx.lifecycle.viewModelScope 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 @@ -25,6 +27,7 @@ import javax.inject.Inject class DailyPraiseViewModel @Inject constructor( private val getDailyPraiseUseCase: GetDailyPraiseUseCase, private val getDailyPraiseByDateUseCase: GetDailyPraiseByDateUseCase, + private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, private val updateDailyPraiseNotificationUseCase: UpdateDailyPraiseNotificationUseCase ): ViewModel() { @@ -53,6 +56,20 @@ class DailyPraiseViewModel @Inject constructor( } } + 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() 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 2fcca025..a4134798 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 @@ -6,7 +6,9 @@ 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.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase import com.egobook.app.domain.usecase.egoroom.UpdateWeeklyReportNotificationUseCase +import com.egobook.app.ui.counseling.model.DailyAndWeeklyNotificationModel import com.egobook.app.ui.counseling.model.WeeklyReportModel import com.egobook.app.ui.counseling.model.WeeklyReportStyleModel import com.egobook.app.ui.counseling.model.toPresentation @@ -23,6 +25,7 @@ import javax.inject.Inject class WeeklyReportViewModel @Inject constructor( private val getWeeklyReportUseCase: GetWeeklyReportUseCase, private val getWeeklyReportStyleUseCase: GetWeeklyReportStyleUseCase, + private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, private val updateWeeklyReportStyleUseCase: UpdateWeeklyReportStyleUseCase, private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase ): ViewModel() { @@ -41,6 +44,20 @@ class WeeklyReportViewModel @Inject constructor( } } + 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() From e8992469fb0279ab1eb44ded260c426295c16e17 Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 10 Feb 2026 14:50:12 +0900 Subject: [PATCH 39/55] =?UTF-8?q?feat(weekly):=20=EC=A3=BC=EA=B0=84=20?= =?UTF-8?q?=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EC=9E=A0=EA=B8=88=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20-?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B0=8F=20=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EC=96=BC=EB=A1=9C=EA=B7=B8=20=ED=98=95=ED=83=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/WeeklyReportUnlockDialog.kt | 29 ++++++ .../layout/dialog_weekly_report_unlock.xml | 92 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/view/WeeklyReportUnlockDialog.kt create mode 100644 app/src/main/res/layout/dialog_weekly_report_unlock.xml 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..ee38d6a3 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/counseling/view/WeeklyReportUnlockDialog.kt @@ -0,0 +1,29 @@ +package com.egobook.app.ui.counseling.view + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.DialogFragment +import com.egobook.app.R +import com.egobook.app.databinding.DialogWeeklyReportUnlockBinding + +class WeeklyReportUnlockDialog: DialogFragment(R.layout.dialog_weekly_report_unlock) { + private lateinit var binding: DialogWeeklyReportUnlockBinding + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = DialogWeeklyReportUnlockBinding.bind(view) + initListeners() + } + + private fun initListeners() = with(binding) { + btnWeeklyReportUnlockUseInk.setOnClickListener { + // TODO: 잉크 사용 + 주간 리포트 열기 + } + btnWeeklyReportUnlockWatchAdd.setOnClickListener { + // TODO: 에드몹 연결 + 주간 리포트 열기 + } + } + + companion object { + const val TAG = "WeeklyReportUnlockDialog" + } +} \ 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..75b1b780 --- /dev/null +++ b/app/src/main/res/layout/dialog_weekly_report_unlock.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file From d84251cc75e797e612284ff282ba5aa26ee0e8a9 Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 10 Feb 2026 14:51:28 +0900 Subject: [PATCH 40/55] =?UTF-8?q?feat(weekly):=20=EC=A3=BC=EA=B0=84=20?= =?UTF-8?q?=EC=83=81=EB=8B=B4=EC=84=9C=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 9 ++- .../model/counseling/WeeklyReportResponse.kt | 31 --------- .../model/counseling/WeeklyReportsResponse.kt | 37 ++++++++++ .../repository/CounselingRepositoryImpl.kt | 51 ++++---------- .../paging/WeeklyReportsPagingSource.kt | 29 ++++++++ .../egobook/app/domain/model/WeeklyReport.kt | 15 ---- .../model/counseling/WeeklyReportItem.kt | 9 +++ .../domain/repository/CounselingRepository.kt | 4 +- .../domain/usecase/GetWeeklyReportUseCase.kt | 9 --- .../domain/usecase/GetWeeklyReportsUseCase.kt | 11 +++ .../adapter/CounselingWeeklyReportAdapter.kt | 11 ++- .../ui/counseling/model/WeeklyReportModel.kt | 31 +++------ .../view/EgoRoomWeeklyReportFragment.kt | 69 +++++++++++-------- .../viewmodel/WeeklyReportViewModel.kt | 19 ++--- .../square/view/SquareAllRepliesFragment.kt | 5 +- app/src/main/res/drawable/ic_lock_key.xml | 13 ++++ .../layout/item_counseling_weekly_report.xml | 4 +- 17 files changed, 192 insertions(+), 165 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportsResponse.kt create mode 100644 app/src/main/java/com/egobook/app/data/repository/paging/WeeklyReportsPagingSource.kt delete mode 100644 app/src/main/java/com/egobook/app/domain/model/WeeklyReport.kt create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt delete mode 100644 app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt create mode 100644 app/src/main/res/drawable/ic_lock_key.xml 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 67a2dea6..2afacb7b 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 @@ -6,7 +6,7 @@ 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.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 retrofit2.Response @@ -42,8 +42,11 @@ interface CounselingApiService { @Body request: CounselingNotificationRequest ): ApiResponse - @GET("api/reports/weekly") - suspend fun fetchWeeklyReports(): Response> + @GET("/ego-room/counsel/weekly") + suspend fun fetchWeeklyReports( + @Path("page") page: Int, + @Path("size") size: Int + ): ApiResponse @GET("api/reports/weekly/style") suspend fun fetchWeeklyReportStyle(): Response 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 deleted file mode 100644 index 6a2c7293..00000000 --- a/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.egobook.app.data.model.counseling - -import com.egobook.app.domain.model.WeeklyReport -import com.egobook.app.domain.model.WeeklyReportContent - -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 - ) -) - 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..daa9326a --- /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.WeeklyReportItem +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(): WeeklyReportItem = WeeklyReportItem( + id = id, + startDate = startDate, + endDate = endDate, + isRead = isRead, + isLocked = isLocked +) + 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 d3ec49da..e9def3be 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 @@ -7,18 +7,18 @@ import com.egobook.app.data.api.CounselingApiService import com.egobook.app.data.model.counseling.CounselingNotificationRequest 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.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.WeeklyReportItem import com.egobook.app.domain.repository.CounselingRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -81,44 +81,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns 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 = - "이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리 이번 주 분석에 대한 글이 들어가는 자리" - - val dummyWeeklyReports = listOf( - WeeklyReport( - id = 1L, - date = "2025.12.22", - content = WeeklyReportContent( - analysis = "이번 주 분석: $longDummyText", - praisePoint = "칭찬 포인트: $longDummyText", - improvement = "개선할 점: $longDummyText", - management = "관리 및 조언: $longDummyText", - encouragement = "응원 및 격려: $longDummyText" - ) + override fun getWeeklyReports(size: Int): Flow> { + return Pager( + config = PagingConfig( + pageSize = size, + initialLoadSize = size, + enablePlaceholders = false ), - WeeklyReport( - id = 2L, - date = "2025.12.29", - content = WeeklyReportContent( - analysis = "지난 주 분석: $longDummyText", - praisePoint = "지난 주 칭찬: $longDummyText", - improvement = "지난 주 개선: $longDummyText", - management = "지난 주 조언: $longDummyText", - encouragement = "지난 주 격려: $longDummyText" - ) - ) - ) - - Result.success(dummyWeeklyReports) - } catch (e: Exception) { - Result.failure(e) + pagingSourceFactory = { + WeeklyReportsPagingSource(apiService = apiService) + } + ).flow } override suspend fun getWeeklyReportStyle(): Result = try { 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..7d6ba6e7 --- /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.WeeklyReportItem + +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/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/WeeklyReportItem.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt new file mode 100644 index 00000000..d129e309 --- /dev/null +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt @@ -0,0 +1,9 @@ +package com.egobook.app.domain.model.counseling + +data class WeeklyReportItem( + 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/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index 5fc0c17d..5f3d5942 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,7 +1,7 @@ package com.egobook.app.domain.repository import androidx.paging.PagingData -import com.egobook.app.domain.model.WeeklyReport +import com.egobook.app.domain.model.counseling.WeeklyReportItem import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.model.Statistics import com.egobook.app.domain.model.WeeklyReportStyle @@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.Flow interface CounselingRepository { fun getDailyPraise(size: Int): Flow> suspend fun getDailyPraiseByDate(date: String): Result - suspend fun getWeeklyReport(): Result> + fun getWeeklyReports(size: Int): Flow> suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result suspend fun getStatistics(): Result 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..9f0dd84d --- /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.WeeklyReportItem +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/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/WeeklyReportModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportModel.kt index c70d7e72..21490ec9 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.WeeklyReportItem import kotlinx.parcelize.Parcelize @Parcelize data class WeeklyReportModel( val id: Long, - val date: String, - val content: WeeklyReportContentModel + val startDate: String, // 사용 완료 + val endDate: String, // 사용 완료 + val isRead: Boolean, + val isLocked: Boolean // 사용 완료 ): Parcelable -@Parcelize -data class WeeklyReportContentModel( - val analysis: String, - val praisePoint: String, - val improvement: String, - val management: String, - val encouragement: String -): Parcelable - -fun WeeklyReport.toPresentation(): WeeklyReportModel = WeeklyReportModel( +fun WeeklyReportItem.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/EgoRoomWeeklyReportFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt index b15581b4..1b6d7aba 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 @@ -12,6 +12,7 @@ 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 @@ -25,6 +26,7 @@ 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 @@ -33,8 +35,13 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r 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() + dialog.show(childFragmentManager, WeeklyReportUnlockDialog.TAG) + } else { + val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(weeklyReportItem = item) + findNavController().navigate(action) + } } private var isNotificationEnabled: Boolean? = null @@ -81,25 +88,20 @@ 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 { + 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) { + when (state) { is UiState.Failure -> {} UiState.Idle -> {} UiState.Loading -> {} @@ -112,14 +114,15 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r } launch { viewModel.updateNotificationStatus.collect { state -> - when(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() } } @@ -127,7 +130,7 @@ 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 -> {} @@ -140,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 -> {} @@ -158,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 + ) + ) } } @@ -175,7 +190,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r private fun fetchData() { viewModel.getDailyAndWeeklyNotification() - viewModel.fetchWeeklyReport() + viewModel.fetchWeeklyReport(size = 10) viewModel.fetchWeeklyReportStyle() } } \ No newline at end of file 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 a4134798..0cc2d401 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,9 +2,12 @@ package com.egobook.app.ui.counseling.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.model.ReportStyle import com.egobook.app.domain.usecase.GetWeeklyReportStyleUseCase -import com.egobook.app.domain.usecase.GetWeeklyReportUseCase +import com.egobook.app.domain.usecase.GetWeeklyReportsUseCase import com.egobook.app.domain.usecase.UpdateWeeklyReportStyleUseCase import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase import com.egobook.app.domain.usecase.egoroom.UpdateWeeklyReportNotificationUseCase @@ -18,28 +21,26 @@ 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 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 updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase ): 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() }) - }.onFailure { error -> - _weeklyReportList.value = UiState.Failure(error.message) + getWeeklyReportsUseCase(size = size).cachedIn(viewModelScope).collectLatest { pagingData -> + _weeklyReportList.value = pagingData.map { it.toPresentation() } } } } 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/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/layout/item_counseling_weekly_report.xml b/app/src/main/res/layout/item_counseling_weekly_report.xml index 6800dc2d..fe5a475a 100644 --- a/app/src/main/res/layout/item_counseling_weekly_report.xml +++ b/app/src/main/res/layout/item_counseling_weekly_report.xml @@ -16,10 +16,10 @@ app:layout_constraintStart_toStartOf="parent"/> From 0ee6163ffdb4ff99b25134d4d4b8529ed20bd332 Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 10 Feb 2026 15:26:26 +0900 Subject: [PATCH 41/55] =?UTF-8?q?feat(weekly):=20=EC=A3=BC=EA=B0=84=20?= =?UTF-8?q?=EC=83=81=EB=8B=B4=EC=84=9C=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20-=20navigation=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20-=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=9D=BC=EB=B6=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 6 +++ .../model/counseling/WeeklyReportResponse.kt | 34 ++++++++++++ .../model/counseling/WeeklyReportsResponse.kt | 4 +- .../repository/CounselingRepositoryImpl.kt | 16 +++++- .../paging/WeeklyReportsPagingSource.kt | 8 +-- .../{WeeklyReportItem.kt => WeeklyReport.kt} | 2 +- .../model/counseling/WeeklyReportDetail.kt | 12 +++++ .../domain/repository/CounselingRepository.kt | 6 ++- .../domain/usecase/GetWeeklyReportsUseCase.kt | 4 +- .../egoroom/GetWeeklyReportByDateUseCase.kt | 8 +++ .../model/WeeklyReportDetailModel.kt | 25 +++++++++ .../ui/counseling/model/WeeklyReportModel.kt | 4 +- .../view/EgoRoomWeeklyReportDetailFragment.kt | 52 +++++++++++++++---- .../view/EgoRoomWeeklyReportFragment.kt | 2 +- .../viewmodel/WeeklyReportViewModel.kt | 19 ++++++- ...fragment_ego_room_weekly_report_detail.xml | 2 +- .../main/res/navigation/bottom_navigation.xml | 5 +- 17 files changed, 177 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt rename app/src/main/java/com/egobook/app/domain/model/counseling/{WeeklyReportItem.kt => WeeklyReport.kt} (85%) create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportDetail.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/GetWeeklyReportByDateUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/ui/counseling/model/WeeklyReportDetailModel.kt 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 2afacb7b..bb0adf4c 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 @@ -6,6 +6,7 @@ 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.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 @@ -48,6 +49,11 @@ interface CounselingApiService { @Path("size") size: Int ): ApiResponse + @GET("/ego-room/counsel/weekly/{startDate}") + suspend fun fetchWeeklyReportByDate( + @Path("startDate") startDate: String + ): Response + @GET("api/reports/weekly/style") suspend fun fetchWeeklyReportStyle(): Response 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 new file mode 100644 index 00000000..37576b6a --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/counseling/WeeklyReportResponse.kt @@ -0,0 +1,34 @@ +package com.egobook.app.data.model.counseling + +import com.egobook.app.domain.model.counseling.WeeklyReportDetail +import com.google.gson.annotations.SerializedName + +data class WeeklyReportResponse( + @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 index daa9326a..e784b888 100644 --- 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 @@ -1,6 +1,6 @@ package com.egobook.app.data.model.counseling -import com.egobook.app.domain.model.counseling.WeeklyReportItem +import com.egobook.app.domain.model.counseling.WeeklyReport import com.google.gson.annotations.SerializedName data class WeeklyReportsResponse( @@ -27,7 +27,7 @@ data class WeeklyReportsContentResponse( val isLocked: Boolean ) -fun WeeklyReportsContentResponse.toDomain(): WeeklyReportItem = WeeklyReportItem( +fun WeeklyReportsContentResponse.toDomain(): WeeklyReport = WeeklyReport( id = id, startDate = startDate, endDate = endDate, 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 e9def3be..0ed80847 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 @@ -18,7 +18,8 @@ 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.WeeklyReportItem +import com.egobook.app.domain.model.counseling.WeeklyReport +import com.egobook.app.domain.model.counseling.WeeklyReportDetail import com.egobook.app.domain.repository.CounselingRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -81,7 +82,7 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns Result.failure(e) } - override fun getWeeklyReports(size: Int): Flow> { + override fun getWeeklyReports(size: Int): Flow> { return Pager( config = PagingConfig( pageSize = size, @@ -94,6 +95,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns ).flow } + 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()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + override suspend fun getWeeklyReportStyle(): Result = try { // val response = apiService.fetchWeeklyReportStyle() // if(response.isSuccessful && response.body() != null) { 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 index 7d6ba6e7..395e4d5b 100644 --- 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 @@ -4,14 +4,14 @@ 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.WeeklyReportItem +import com.egobook.app.domain.model.counseling.WeeklyReport -class WeeklyReportsPagingSource(private val apiService: CounselingApiService): PagingSource() { - override fun getRefreshKey(state: PagingState): Int { +class WeeklyReportsPagingSource(private val apiService: CounselingApiService): PagingSource() { + override fun getRefreshKey(state: PagingState): Int { return 1 } - override suspend fun load(params: LoadParams): LoadResult { + override suspend fun load(params: LoadParams): LoadResult { return try { val page = params.key ?: 1 val size = params.loadSize diff --git a/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt similarity index 85% rename from app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt rename to app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt index d129e309..36701d08 100644 --- a/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportItem.kt +++ b/app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReport.kt @@ -1,6 +1,6 @@ package com.egobook.app.domain.model.counseling -data class WeeklyReportItem( +data class WeeklyReport( val id: Long, val startDate: String, val endDate: String, 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/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index 5f3d5942..9c38d590 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,23 +1,25 @@ package com.egobook.app.domain.repository import androidx.paging.PagingData -import com.egobook.app.domain.model.counseling.WeeklyReportItem +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 kotlinx.coroutines.flow.Flow interface CounselingRepository { fun getDailyPraise(size: Int): Flow> suspend fun getDailyPraiseByDate(date: String): Result - fun getWeeklyReports(size: Int): Flow> + 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 } \ 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 index 9f0dd84d..0336f541 100644 --- a/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt +++ b/app/src/main/java/com/egobook/app/domain/usecase/GetWeeklyReportsUseCase.kt @@ -1,11 +1,11 @@ package com.egobook.app.domain.usecase import androidx.paging.PagingData -import com.egobook.app.domain.model.counseling.WeeklyReportItem +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) + 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/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/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 21490ec9..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,7 +1,7 @@ package com.egobook.app.ui.counseling.model import android.os.Parcelable -import com.egobook.app.domain.model.counseling.WeeklyReportItem +import com.egobook.app.domain.model.counseling.WeeklyReport import kotlinx.parcelize.Parcelize @Parcelize @@ -13,7 +13,7 @@ data class WeeklyReportModel( val isLocked: Boolean // 사용 완료 ): Parcelable -fun WeeklyReportItem.toPresentation(): WeeklyReportModel = WeeklyReportModel( +fun WeeklyReport.toPresentation(): WeeklyReportModel = WeeklyReportModel( id = id, startDate = startDate, endDate = endDate, 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 1b6d7aba..0b4043de 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 @@ -39,7 +39,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r val dialog = WeeklyReportUnlockDialog() dialog.show(childFragmentManager, WeeklyReportUnlockDialog.TAG) } else { - val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(weeklyReportItem = item) + val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(startDate = item.startDate) findNavController().navigate(action) } } 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 0cc2d401..b0f43ebf 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 @@ -10,8 +10,10 @@ import com.egobook.app.domain.usecase.GetWeeklyReportStyleUseCase import com.egobook.app.domain.usecase.GetWeeklyReportsUseCase import com.egobook.app.domain.usecase.UpdateWeeklyReportStyleUseCase import com.egobook.app.domain.usecase.egoroom.GetDailyAndWeeklyNotificationUseCase + import com.egobook.app.domain.usecase.egoroom.GetWeeklyReportByDateUseCase 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 @@ -31,7 +33,8 @@ class WeeklyReportViewModel @Inject constructor( private val getWeeklyReportStyleUseCase: GetWeeklyReportStyleUseCase, private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, private val updateWeeklyReportStyleUseCase: UpdateWeeklyReportStyleUseCase, - private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase + private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase, + private val getWeeklyReportByDateUseCase: GetWeeklyReportByDateUseCase ): ViewModel() { private val _weeklyReportList = MutableStateFlow>(PagingData.empty()) @@ -72,6 +75,20 @@ class WeeklyReportViewModel @Inject constructor( } } + 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() 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..b2e5dfda 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 @@ -62,7 +62,7 @@ app:layout_constraintStart_toStartOf="parent" app:strokeWidth="0dp"> - + Date: Tue, 10 Feb 2026 16:12:34 +0900 Subject: [PATCH 42/55] =?UTF-8?q?refactor(weekly):=20=EB=8B=A4=EC=9D=8C?= =?UTF-8?q?=EC=A3=BC=20=EC=83=81=EB=8B=B4=20=EB=B6=84=EC=9C=84=EA=B8=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20API=20=EC=97=B0=EA=B2=B0=20-=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 9 ++-- .../model/counseling/ReportStyleRequest.kt | 7 +++ .../repository/CounselingRepositoryImpl.kt | 54 ++++++++++--------- .../egobook/app/domain/model/ReportStyle.kt | 8 +-- .../UpdateWeeklyReportStyleUseCase.kt | 2 +- .../viewmodel/WeeklyReportViewModel.kt | 2 +- 6 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/data/model/counseling/ReportStyleRequest.kt rename app/src/main/java/com/egobook/app/domain/usecase/{ => egoroom}/UpdateWeeklyReportStyleUseCase.kt (89%) 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 bb0adf4c..7de1817a 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 @@ -5,6 +5,7 @@ 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 @@ -54,12 +55,14 @@ interface CounselingApiService { @Path("startDate") startDate: String ): Response + @PATCH("/ego-room/counsel/weekly/next-tone") + suspend fun updateWeeklyReportStyle( + @Body request: ReportStyleRequest + ): Response + @GET("api/reports/weekly/style") suspend fun fetchWeeklyReportStyle(): Response - @POST("api/reports/weekly/style") - suspend fun updateWeeklyReportStyle(@Query("style") reportStyle: ReportStyle): Response - @GET("api/statistics") suspend fun fetchStatistics(): Response } \ No newline at end of file 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/repository/CounselingRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt index 0ed80847..8cb9a6e0 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 @@ -5,6 +5,7 @@ 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 @@ -24,7 +25,8 @@ import com.egobook.app.domain.repository.CounselingRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class CounselingRepositoryImpl @Inject constructor(private val apiService: CounselingApiService) : CounselingRepository { +class CounselingRepositoryImpl @Inject constructor(private val apiService: CounselingApiService) : + CounselingRepository { override fun getDailyPraise(size: Int): Flow> { return Pager( config = PagingConfig( @@ -40,7 +42,7 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns override suspend fun getDailyPraiseByDate(date: String): Result = try { val response = apiService.fetchDailyPraiseByDate(date = date) - if(response.isSuccessful && response.body() != null) { + if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!.toDomain()) } else { Result.failure(Exception("Error: ${response.code()}")) @@ -51,7 +53,7 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns override suspend fun getDailyAndWeeklyNotification(): Result = try { val response = apiService.fetchDailyAndWeeklyNotification() - if(response.status == 200) { + if (response.status == 200) { Result.success(response.data.toDomain()) } else { Result.failure(Exception("Error: ${response.status}")) @@ -61,9 +63,11 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns } override suspend fun updateDailyPraiseNotification(isEnabled: Boolean): Result = try { - val response = apiService.updateDailyPraiseNotification(request = CounselingNotificationRequest(isEnabled = isEnabled)) - if(response.status == 200) { - Result.success(isEnabled) + val response = apiService.updateDailyPraiseNotification( + request = CounselingNotificationRequest(isEnabled = isEnabled) + ) + if (response.status == 200) { + Result.success(isEnabled) } else { Result.failure(Exception("Error: ${response.status}")) } @@ -72,8 +76,10 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns } override suspend fun updateWeeklyReportNotification(isEnabled: Boolean): Result = try { - val response = apiService.updateWeeklyReportNotification(request = CounselingNotificationRequest(isEnabled = isEnabled)) - if(response.status == 200) { + val response = apiService.updateWeeklyReportNotification( + request = CounselingNotificationRequest(isEnabled = isEnabled) + ) + if (response.status == 200) { Result.success(isEnabled) } else { Result.failure(Exception("Error: ${response.status}")) @@ -95,16 +101,17 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns ).flow } - 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()}")) + 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()}")) + } + } catch (e: Exception) { + Result.failure(e) } - } catch (e: Exception) { - Result.failure(e) - } override suspend fun getWeeklyReportStyle(): Result = try { // val response = apiService.fetchWeeklyReportStyle() @@ -121,13 +128,12 @@ 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) } 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/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/ui/counseling/viewmodel/WeeklyReportViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt index b0f43ebf..ad5e7b92 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 @@ -8,7 +8,7 @@ import androidx.paging.map import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.usecase.GetWeeklyReportStyleUseCase import com.egobook.app.domain.usecase.GetWeeklyReportsUseCase -import com.egobook.app.domain.usecase.UpdateWeeklyReportStyleUseCase +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.UpdateWeeklyReportNotificationUseCase From 334d0b6186658be78c1cee566714f7645d755f6c Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 10 Feb 2026 19:30:36 +0900 Subject: [PATCH 43/55] =?UTF-8?q?feat(weekly):=20=EC=A3=BC=EA=B0=84=20?= =?UTF-8?q?=EC=83=81=EB=8B=B4=EC=84=9C=20=EC=9E=A0=EA=B8=88=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C=20API=20=EC=97=B0=EA=B2=B0=20(=EC=9E=89=ED=81=AC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9)=20-=20=EC=9E=89=ED=81=AC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/api/CounselingApiService.kt | 8 +++ .../repository/CounselingRepositoryImpl.kt | 15 +++++ .../counseling/WeeklyReportUnlockType.kt | 6 ++ .../domain/repository/CounselingRepository.kt | 2 + .../app/domain/usecase/GetUserInfoUseCase.kt | 16 +++++ .../egoroom/UnlockWeeklyReportUseCase.kt | 9 +++ .../view/EgoRoomWeeklyReportFragment.kt | 2 +- .../view/WeeklyReportUnlockDialog.kt | 58 ++++++++++++++++++- .../viewmodel/WeeklyReportViewModel.kt | 36 +++++++++++- 9 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/egobook/app/domain/model/counseling/WeeklyReportUnlockType.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/GetUserInfoUseCase.kt create mode 100644 app/src/main/java/com/egobook/app/domain/usecase/egoroom/UnlockWeeklyReportUseCase.kt 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 7de1817a..1815fb63 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 @@ -11,6 +11,7 @@ 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 @@ -60,6 +61,13 @@ interface CounselingApiService { @Body request: ReportStyleRequest ): Response + @POST("/ego-room/counsel/weekly/{startDate}/unlock") + suspend fun unlockWeeklyReport( + @Path("startDate") startDate: String, + @Path("unlockType") unlockType: WeeklyReportUnlockType + ): ApiResponse + + @GET("api/reports/weekly/style") suspend fun fetchWeeklyReportStyle(): Response 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 8cb9a6e0..d6b156fd 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 @@ -21,6 +21,7 @@ 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 javax.inject.Inject @@ -138,6 +139,20 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns 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/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/repository/CounselingRepository.kt b/app/src/main/java/com/egobook/app/domain/repository/CounselingRepository.kt index 9c38d590..62ef0388 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 @@ -9,6 +9,7 @@ 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 { @@ -22,4 +23,5 @@ interface CounselingRepository { 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/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/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/ui/counseling/view/EgoRoomWeeklyReportFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt index 0b4043de..61ca770e 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 @@ -36,7 +36,7 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r private val viewModel: WeeklyReportViewModel by viewModels() private val counselingWeeklyReportAdapter = CounselingWeeklyReportAdapter { item -> if(item.isLocked) { - val dialog = WeeklyReportUnlockDialog() + val dialog = WeeklyReportUnlockDialog(startDate = item.startDate) dialog.show(childFragmentManager, WeeklyReportUnlockDialog.TAG) } else { val action = EgoRoomFragmentDirections.actionMenuEgoRoomToCounselingWeeklyReportDetailFragment(startDate = item.startDate) 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 index ee38d6a3..0d408ca8 100644 --- 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 @@ -2,28 +2,82 @@ package com.egobook.app.ui.counseling.view import android.os.Bundle import android.view.View +import android.widget.Toast 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: DialogFragment(R.layout.dialog_weekly_report_unlock) { +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 onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = DialogWeeklyReportUnlockBinding.bind(view) initListeners() + initObservers() } private fun initListeners() = with(binding) { btnWeeklyReportUnlockUseInk.setOnClickListener { - // TODO: 잉크 사용 + 주간 리포트 열기 + 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/WeeklyReportViewModel.kt b/app/src/main/java/com/egobook/app/ui/counseling/viewmodel/WeeklyReportViewModel.kt index ad5e7b92..174eba2f 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 @@ -6,17 +6,21 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map import com.egobook.app.domain.model.ReportStyle +import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType +import com.egobook.app.domain.usecase.GetUserInfoUseCase import com.egobook.app.domain.usecase.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.home.user.User import com.egobook.app.util.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -34,7 +38,9 @@ class WeeklyReportViewModel @Inject constructor( private val getDailyAndWeeklyNotificationUseCase: GetDailyAndWeeklyNotificationUseCase, private val updateWeeklyReportStyleUseCase: UpdateWeeklyReportStyleUseCase, private val updateWeeklyReportNotificationUseCase: UpdateWeeklyReportNotificationUseCase, - private val getWeeklyReportByDateUseCase: GetWeeklyReportByDateUseCase + private val getWeeklyReportByDateUseCase: GetWeeklyReportByDateUseCase, + private val unlockWeeklyReportUseCase: UnlockWeeklyReportUseCase, + private val getUserInfoUseCase: GetUserInfoUseCase ): ViewModel() { private val _weeklyReportList = MutableStateFlow>(PagingData.empty()) @@ -115,4 +121,32 @@ class WeeklyReportViewModel @Inject constructor( } } } + + private val _unlockWeeklyReportResult = MutableSharedFlow>() + val unlockWeeklyReportResult = _unlockWeeklyReportResult.asSharedFlow() + + fun unlockWeeklyReport(startDate: String, unlockType: WeeklyReportUnlockType) { + viewModelScope.launch { + _unlockWeeklyReportResult.emit(UiState.Loading) + unlockWeeklyReportUseCase(startDate = startDate, unlockType = unlockType).onSuccess { + _unlockWeeklyReportResult.emit(UiState.Success(it)) + }.onFailure { error -> + _unlockWeeklyReportResult.emit(UiState.Failure(error.message)) + } + } + } + + private val _userInfo = MutableStateFlow>(UiState.Idle) + val userInfo = _userInfo.asStateFlow() + + fun getUserInfo() { + viewModelScope.launch { + _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 From 2637ca788e024767286bd21db598507972508a1c Mon Sep 17 00:00:00 2001 From: se05503 Date: Tue, 10 Feb 2026 20:07:54 +0900 Subject: [PATCH 44/55] =?UTF-8?q?refactor(weekly):=20=EC=A3=BC=EA=B0=84=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EB=B6=84=EC=9C=84=EA=B8=B0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0=EA=B2=B0=20-=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EC=9E=84=EC=8B=9C=20=EC=97=B0=EA=B2=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../egobook/app/data/api/CounselingApiService.kt | 4 ++-- .../data/repository/CounselingRepositoryImpl.kt | 15 +++++++-------- .../app/domain/repository/CounselingRepository.kt | 2 +- .../{ => egoroom}/GetWeeklyReportStyleUseCase.kt | 5 +++-- .../view/EgoRoomWeeklyReportFragment.kt | 4 ++-- .../counseling/viewmodel/WeeklyReportViewModel.kt | 8 ++++---- 6 files changed, 19 insertions(+), 19 deletions(-) rename app/src/main/java/com/egobook/app/domain/usecase/{ => egoroom}/GetWeeklyReportStyleUseCase.kt (57%) 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 1815fb63..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 @@ -68,8 +68,8 @@ interface CounselingApiService { ): ApiResponse - @GET("api/reports/weekly/style") - suspend fun fetchWeeklyReportStyle(): Response + @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/repository/CounselingRepositoryImpl.kt b/app/src/main/java/com/egobook/app/data/repository/CounselingRepositoryImpl.kt index d6b156fd..16cc4738 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 @@ -114,14 +114,13 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns Result.failure(e) } - 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 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) } 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 62ef0388..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 @@ -16,7 +16,7 @@ interface CounselingRepository { fun getDailyPraise(size: Int): Flow> suspend fun getDailyPraiseByDate(date: String): Result fun getWeeklyReports(size: Int): Flow> - suspend fun getWeeklyReportStyle(): Result + suspend fun getWeeklyReportStyle(): Result suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result suspend fun getStatistics(): Result suspend fun getDailyAndWeeklyNotification(): Result 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/ui/counseling/view/EgoRoomWeeklyReportFragment.kt b/app/src/main/java/com/egobook/app/ui/counseling/view/EgoRoomWeeklyReportFragment.kt index 61ca770e..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 @@ -134,8 +134,8 @@ class EgoRoomWeeklyReportFragment : Fragment(R.layout.fragment_ego_room_weekly_r 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) } } 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 174eba2f..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 @@ -8,7 +8,7 @@ import androidx.paging.map import com.egobook.app.domain.model.ReportStyle import com.egobook.app.domain.model.counseling.WeeklyReportUnlockType import com.egobook.app.domain.usecase.GetUserInfoUseCase -import com.egobook.app.domain.usecase.GetWeeklyReportStyleUseCase +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 @@ -95,13 +95,13 @@ class WeeklyReportViewModel @Inject constructor( } } - private val _weeklyReportStyle = MutableStateFlow>(UiState.Idle) + 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) } From 757b1eae90d36a179b40936d0e7fd6e8e29aa1a4 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 15:44:49 +0900 Subject: [PATCH 45/55] =?UTF-8?q?design(ego-room):=20=EC=97=90=EA=B3=A0?= =?UTF-8?q?=EB=A3=B8=20=EB=A9=94=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/fragment_ego_room.xml | 57 +++++++++++++------ app/src/main/res/values/styles.xml | 4 ++ 2 files changed, 44 insertions(+), 17 deletions(-) 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/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 From 884be00994b84bf3cb94c7044e4d1208bbdbbf91 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 15:53:49 +0900 Subject: [PATCH 46/55] =?UTF-8?q?design(ego-room):=20=EC=97=90=EA=B3=A0?= =?UTF-8?q?=EB=A3=B8=20=EC=9D=BC=EA=B0=84=20=EC=B9=AD=EC=B0=AC=EC=84=9C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20UI=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/fragment_ego_room_daily_praise.xml | 16 ++++++++++++++-- .../res/layout/item_counseling_daily_praise.xml | 10 +++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) 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..038977ec 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/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 From d850110d4b0400890456a429972c2d4cdc50006b Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 16:17:03 +0900 Subject: [PATCH 47/55] =?UTF-8?q?design(ego-room):=20=EC=97=90=EA=B3=A0?= =?UTF-8?q?=EB=A3=B8=20=EC=A3=BC=EA=B0=84=20=EC=B9=AD=EC=B0=AC=EC=84=9C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20UI=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20-=20=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4=20-=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../color/selector_weekly_report_style_bg.xml | 2 +- .../selector_weekly_report_style_text.xml | 4 +- .../layout/dialog_weekly_report_unlock.xml | 17 ++++- .../fragment_ego_room_weekly_report.xml | 38 +++++++--- ...fragment_ego_room_weekly_report_detail.xml | 72 +++++++++++++++---- .../layout/item_counseling_weekly_report.xml | 3 + 6 files changed, 109 insertions(+), 27 deletions(-) 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/layout/dialog_weekly_report_unlock.xml b/app/src/main/res/layout/dialog_weekly_report_unlock.xml index 75b1b780..ea157dc5 100644 --- a/app/src/main/res/layout/dialog_weekly_report_unlock.xml +++ b/app/src/main/res/layout/dialog_weekly_report_unlock.xml @@ -12,6 +12,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:textAlignment="center" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -22,6 +25,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="잉크 또는 광고 시청을 통해\n열람할 수 있어요" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" android:textAlignment="center" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/tv_weekly_report_unlock_title" @@ -54,7 +62,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:text="10"/> + android:text="10" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold"/> @@ -65,6 +76,8 @@ android:backgroundTint="@color/neutral" android:text="잉크 사용" android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="16dp" android:insetTop="0dp" android:insetBottom="0dp" @@ -80,6 +93,8 @@ android:backgroundTint="@color/neutral" android:text="광고 보기" android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginStart="12dp" android:insetTop="0dp" android:insetBottom="0dp" 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 b2e5dfda..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/item_counseling_weekly_report.xml b/app/src/main/res/layout/item_counseling_weekly_report.xml index fe5a475a..9b14ccbe 100644 --- a/app/src/main/res/layout/item_counseling_weekly_report.xml +++ b/app/src/main/res/layout/item_counseling_weekly_report.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"/> From 9364c359cd73700e541e64f7e952f1569ad291c5 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 17:02:59 +0900 Subject: [PATCH 48/55] =?UTF-8?q?design(ego-room):=20=EC=97=90=EA=B3=A0?= =?UTF-8?q?=EB=A3=B8=20=ED=86=B5=EA=B3=84=20=ED=99=94=EB=A9=B4=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/fragment_ego_room_statistics.xml | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) 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" /> From 17f7d0f104f4c3bd5fbd589a94fe6d4e8d5f9e0b Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 17:36:21 +0900 Subject: [PATCH 49/55] =?UTF-8?q?design(square):=20=EA=B4=91=EC=9E=A5=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/fragment_square.xml | 138 +++++++++++++++--- .../res/layout/item_square_friend_answer.xml | 16 +- .../main/res/layout/item_square_letter.xml | 3 + 3 files changed, 131 insertions(+), 26 deletions(-) diff --git a/app/src/main/res/layout/fragment_square.xml b/app/src/main/res/layout/fragment_square.xml index 446be64a..dfc5d54b 100644 --- a/app/src/main/res/layout/fragment_square.xml +++ b/app/src/main/res/layout/fragment_square.xml @@ -27,7 +27,10 @@ android:layout_height="wrap_content" android:layout_marginVertical="9dp" android:layout_weight="1" - android:text="광장" /> + 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" /> @@ -99,12 +107,18 @@ 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:text="1/1" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium"/> @@ -132,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"/> @@ -166,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"/> @@ -200,13 +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"/> @@ -264,7 +299,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"/> + 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"/> @@ -522,7 +589,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"/> @@ -561,7 +637,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"/> @@ -582,7 +660,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"/> @@ -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_letter.xml b/app/src/main/res/layout/item_square_letter.xml index 880966bb..d795b2e4 100644 --- a/app/src/main/res/layout/item_square_letter.xml +++ b/app/src/main/res/layout/item_square_letter.xml @@ -20,6 +20,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" 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" From edc2990ca8c93971d01fc2a59e04308b88aefaa8 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 18:08:56 +0900 Subject: [PATCH 50/55] =?UTF-8?q?design(square):=20=EA=B4=91=EC=9E=A5=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=80=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD=20-=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EA=B8=80=EC=9E=90=EC=88=98=20=EC=A0=9C=ED=95=9C=20360=EC=9E=90?= =?UTF-8?q?=20=E2=86=92=20350=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/square/view/LetterReplyFragment.kt | 2 +- .../app/ui/square/view/LetterWriteFragment.kt | 2 +- .../layout/dialog_arrived_pending_letter.xml | 13 ++++++ .../dialog_arrived_pending_letter_popup.xml | 12 ++++++ .../dialog_detect_abusive_content_failure.xml | 16 +++++++ .../dialog_detect_abusive_content_loading.xml | 9 ++++ .../dialog_detect_abusive_content_success.xml | 7 ++++ .../layout/dialog_give_up_reply_letter.xml | 12 ++++++ .../main/res/layout/dialog_letter_send.xml | 12 ++++++ .../res/layout/dialog_premium_letter_buy.xml | 16 ++++++- .../main/res/layout/fragment_letter_reply.xml | 35 ++++++++++++++-- .../main/res/layout/fragment_letter_write.xml | 26 ++++++++++-- .../res/layout/fragment_my_letter_detail.xml | 42 ++++++++++++++++--- .../main/res/layout/fragment_my_letters.xml | 5 ++- app/src/main/res/layout/fragment_square.xml | 2 +- .../layout/layout_letter_tooltip_popup.xml | 4 ++ 16 files changed, 198 insertions(+), 17 deletions(-) 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 5e9c030d..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 @@ -216,6 +216,6 @@ class LetterReplyFragment : Fragment(R.layout.fragment_letter_reply) { } companion object { - private const val MAX_LENGTH = 360 + 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/LetterWriteFragment.kt b/app/src/main/java/com/egobook/app/ui/square/view/LetterWriteFragment.kt index ee7603d4..b9af78e9 100644 --- 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 @@ -193,6 +193,6 @@ class LetterWriteFragment : Fragment(R.layout.fragment_letter_write) { } companion object { - private const val MAX_LENGTH = 360 + private const val MAX_LENGTH = 350 } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_arrived_pending_letter.xml b/app/src/main/res/layout/dialog_arrived_pending_letter.xml index 7c5517c0..0a0d5b07 100644 --- a/app/src/main/res/layout/dialog_arrived_pending_letter.xml +++ b/app/src/main/res/layout/dialog_arrived_pending_letter.xml @@ -39,6 +39,8 @@ android:layout_height="wrap_content" tools:text="2025.12.27" android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/iv_arrived_pending_letter_report" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/iv_arrived_pending_letter_report" /> @@ -60,6 +62,11 @@ tools:text="편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편 지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/iv_arrived_pending_letter_report" /> @@ -71,6 +78,8 @@ android:layout_marginTop="24dp" tools:text="From 이름" android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_arrived_pending_letter_content" /> @@ -87,6 +96,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" @@ -101,6 +112,8 @@ android:backgroundTint="@color/neutral" 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_arrived_pending_letter_popup.xml b/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml index 3d1058b6..11b0f17d 100644 --- a/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml +++ b/app/src/main/res/layout/dialog_arrived_pending_letter_popup.xml @@ -14,6 +14,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="낯선 고북이의\n편지가 도착했어요" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:textAlignment="center" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -43,6 +47,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="24시간 남음" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/civ_arrived_pending_letter" app:layout_constraintStart_toStartOf="@id/civ_arrived_pending_letter" @@ -54,6 +62,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_marginEnd="12dp" android:layout_marginTop="16dp" @@ -70,6 +80,8 @@ android:layout_height="wrap_content" android:text="확인하기" android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:backgroundTint="@color/neutral" android:insetTop="0dp" android:insetBottom="0dp" 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 index a44cc7f9..e88f17b7 100644 --- a/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml +++ b/app/src/main/res/layout/dialog_detect_abusive_content_failure.xml @@ -13,6 +13,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="부드럽게 다듬으면\n좋을 표현이 발견됐어요" + android:textColor="@color/neutral" + android:textSize="16sp" + android:lineHeight="21sp" + android:fontFamily="@font/arita_semibold" android:textAlignment="center" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -41,6 +45,10 @@ android:layout_height="wrap_content" android:text="비속어 혹은 모욕적인\n표현으로 의심되는 문장" android:textColor="@color/cos_red_light" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" android:textAlignment="center" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/cv_detect_abusive_content_failure" @@ -52,6 +60,11 @@ android:layout_width="200dp" android:layout_height="wrap_content" tools:text="사용자가 작성한 문장이 들어가는 자리 사용자가 작성한 문장이 들어가는 자리 사용자가 작성한 문장이 들어가는 자리" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" android:textAlignment="center" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/tv_detect_abusive_content_failure_words_title" @@ -63,6 +76,9 @@ android:layout_width="200dp" android:layout_height="wrap_content" android:text="수정하기" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:layout_marginTop="16dp" android:backgroundTint="@color/neutral" android:insetTop="0dp" 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 index bf50bfc6..4a9c03cc 100644 --- a/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml +++ b/app/src/main/res/layout/dialog_detect_abusive_content_loading.xml @@ -12,6 +12,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="고북이가\n날카로운 표현이 있는지\n확인하고 있어요" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:textAlignment="center" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -38,6 +42,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="기분 좋은 소통을 할 수 있도록\n도와드릴게요" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.01" android:textAlignment="center" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/cv_detect_abusive_loading" 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 index 98e59073..752eb7a0 100644 --- a/app/src/main/res/layout/dialog_detect_abusive_content_success.xml +++ b/app/src/main/res/layout/dialog_detect_abusive_content_success.xml @@ -13,6 +13,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="소중한 편지가\n무사히 전송되었어요" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:textAlignment="center" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -45,6 +49,9 @@ android:layout_width="200dp" android:layout_height="wrap_content" tools:text="잉크 1 획득!" + android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="16dp" android:backgroundTint="@color/brand" android:insetTop="0dp" diff --git a/app/src/main/res/layout/dialog_give_up_reply_letter.xml b/app/src/main/res/layout/dialog_give_up_reply_letter.xml index fc880801..a42b749d 100644 --- a/app/src/main/res/layout/dialog_give_up_reply_letter.xml +++ b/app/src/main/res/layout/dialog_give_up_reply_letter.xml @@ -12,6 +12,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" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -22,6 +25,11 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="편지를 포기하면 4시간 후에\n새로운 편지를 받을 수 있어요" + 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" @@ -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_letter_send.xml b/app/src/main/res/layout/dialog_letter_send.xml index 90710669..143b8306 100644 --- a/app/src/main/res/layout/dialog_letter_send.xml +++ b/app/src/main/res/layout/dialog_letter_send.xml @@ -13,6 +13,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="누군가에게\n편지를 보낼까요?" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" android:textAlignment="center" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -24,6 +28,10 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" tools:text="익명으로 전달돼요" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" app:layout_constraintEnd_toEndOf="@+id/tv_letter_send_title" app:layout_constraintStart_toStartOf="@+id/tv_letter_send_title" app:layout_constraintTop_toBottomOf="@+id/tv_letter_send_title" /> @@ -36,6 +44,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" @@ -51,6 +61,8 @@ android:backgroundTint="@color/neutral" 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_premium_letter_buy.xml b/app/src/main/res/layout/dialog_premium_letter_buy.xml index 053a5930..81e4f584 100644 --- a/app/src/main/res/layout/dialog_premium_letter_buy.xml +++ b/app/src/main/res/layout/dialog_premium_letter_buy.xml @@ -12,6 +12,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="24dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -22,6 +25,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="한 번 구매하면 계속 사용할 수 있어요" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" + android:letterSpacing="-0.01" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/tv_premium_letter_title" app:layout_constraintStart_toStartOf="@id/tv_premium_letter_title" @@ -53,7 +60,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:text="500"/> + android:text="500" + android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold"/> @@ -82,6 +92,8 @@ android:backgroundTint="@color/neutral_weak" android:text="돌아가기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginTop="16dp" android:layout_marginBottom="42dp" android:insetTop="0dp" @@ -99,6 +111,8 @@ android:backgroundTint="@color/brand" android:text="구매하기" android:textColor="@color/layer_white" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginStart="16dp" android:insetTop="0dp" android:insetBottom="0dp" diff --git a/app/src/main/res/layout/fragment_letter_reply.xml b/app/src/main/res/layout/fragment_letter_reply.xml index 595c9c5e..e30ba77c 100644 --- a/app/src/main/res/layout/fragment_letter_reply.xml +++ b/app/src/main/res/layout/fragment_letter_reply.xml @@ -33,7 +33,10 @@ + android:text="답장 하기" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -70,6 +76,11 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" tools:text="편지원문이 보이는자리 편지원문이 보이는자리편지원문이 보이는자리편지원문이 보이는자리편지원문이 보이는자리편지원문이 보이는자리편지원문이 보이는자리편지원문이 보이는자리" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_letter_reply_letter_received_title" /> @@ -94,6 +105,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" /> @@ -225,6 +239,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="To 낯선 고북이" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="@id/iv_letter_reply_tooltip" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/iv_letter_reply_tooltip" /> @@ -246,7 +263,12 @@ android:backgroundTint="@android:color/transparent" android:hint="편지를 작성해 보세요" android:gravity="start" - android:maxLength="360" + android:maxLength="350" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/iv_letter_reply_tooltip" @@ -257,8 +279,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginVertical="12dp" - android:text="0/360" + android:text="0/350" android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintTop_toBottomOf="@id/et_letter_reply_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/tv_letter_reply_sender" /> @@ -269,6 +293,9 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="From 또다른 고북이" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"/> @@ -282,6 +309,8 @@ android:layout_height="wrap_content" android:text="답장 보내기" android:textColor="@color/selector_square_button_text" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:backgroundTint="@color/selector_square_button_bg" android:layout_marginHorizontal="16dp" android:layout_marginTop="30dp" diff --git a/app/src/main/res/layout/fragment_letter_write.xml b/app/src/main/res/layout/fragment_letter_write.xml index d95a214a..a127b74b 100644 --- a/app/src/main/res/layout/fragment_letter_write.xml +++ b/app/src/main/res/layout/fragment_letter_write.xml @@ -29,7 +29,10 @@ + android:text="편지 쓰기" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -180,7 +186,12 @@ android:layout_marginTop="12dp" android:backgroundTint="@android:color/transparent" android:hint="편지를 작성해 보세요" - android:maxLength="360" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02" + android:maxLength="350" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/iv_letter_tooltip"/> @@ -190,8 +201,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:text="0/360" + android:text="0/350" android:textColor="@color/neutral_subtle" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/et_letter_write_content" /> @@ -201,6 +214,9 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="From 또다른 고북이" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_letter_write_content_length" /> @@ -214,6 +230,8 @@ android:layout_height="wrap_content" android:layout_marginTop="28dp" android:text="친구에게 보내기" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginStart="16dp" android:insetTop="0dp" android:insetBottom="0dp" @@ -230,6 +248,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:text="누군가에게 보내기" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" android:layout_marginEnd="16dp" android:layout_marginStart="12dp" android:insetTop="0dp" 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 12eb2320..9b93af49 100644 --- a/app/src/main/res/layout/fragment_my_letter_detail.xml +++ b/app/src/main/res/layout/fragment_my_letter_detail.xml @@ -35,7 +35,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - tools:text="2025.12.25" /> + tools:text="2025.12.25" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> @@ -63,6 +68,9 @@ 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" /> @@ -98,7 +106,12 @@ 사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용 -사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용" /> +사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용사용자가 작성하는 편지 내용" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> + android:text="받은 답장" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold"/> @@ -160,7 +179,12 @@ app:layout_constraintTop_toBottomOf="@id/iv_my_letter_detail_report" tools:text="편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편 지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 - 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용" /> + 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용편지 답장의 내용내용내용" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium" + android:lineHeight="21sp" + android:letterSpacing="-0.02"/> @@ -179,6 +207,8 @@ android:layout_marginTop="12dp" android:text="From 또 다른 고북이" android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_my_letter_detail_replied_content" /> diff --git a/app/src/main/res/layout/fragment_my_letters.xml b/app/src/main/res/layout/fragment_my_letters.xml index 3b993c79..f7237908 100644 --- a/app/src/main/res/layout/fragment_my_letters.xml +++ b/app/src/main/res/layout/fragment_my_letters.xml @@ -28,7 +28,10 @@ + android:text="내가 쓴 편지" + android:textColor="@color/neutral" + android:textSize="20sp" + android:fontFamily="@font/arita_semibold"/> + android:src="@drawable/ic_square_view_all" />3 \ No newline at end of file From ccf38dbe1fcef16e9eae74ce425b77984525e6ff Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 18:34:47 +0900 Subject: [PATCH 51/55] =?UTF-8?q?design(square):=20=EA=B4=91=EC=9E=A5=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD=20-=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=ED=99=94=EB=A9=B4=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/layout/dialog_square_report.xml | 33 +++++++++++++++++-- .../layout/fragment_my_replies_history.xml | 5 ++- .../layout/fragment_square_all_replies.xml | 9 ++++- .../main/res/layout/item_square_my_reply.xml | 12 ++++++- .../res/layout/item_square_question_reply.xml | 10 +++++- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/dialog_square_report.xml b/app/src/main/res/layout/dialog_square_report.xml index 2b8e8aec..498a2de6 100644 --- a/app/src/main/res/layout/dialog_square_report.xml +++ b/app/src/main/res/layout/dialog_square_report.xml @@ -12,6 +12,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" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -23,6 +26,8 @@ android:layout_marginTop="16dp" android:text="허위 신고일 경우 제제를 받을 수 있어요" android:textColor="@color/cos_red" + android:textSize="12sp" + android:fontFamily="@font/arita_medium" app:layout_constraintEnd_toEndOf="@id/tv_square_report_title" app:layout_constraintStart_toStartOf="@id/tv_square_report_title" app:layout_constraintTop_toBottomOf="@id/tv_square_report_title" /> @@ -37,6 +42,9 @@ android:paddingHorizontal="16dp" android:paddingVertical="12dp" android:text="비속어 또는 모욕적인 표현이 있어요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_square_report_description" /> @@ -59,6 +67,9 @@ android:paddingHorizontal="16dp" android:paddingVertical="12dp" android:text="광고 또는 스팸 글이에요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_abuse" /> @@ -81,6 +92,9 @@ android:paddingHorizontal="16dp" android:paddingVertical="12dp" android:text="부적절한 내용을 담고 있어요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/view_square_report_divider_spam" /> @@ -108,7 +122,10 @@ android:gravity="center" android:paddingHorizontal="16dp" android:paddingVertical="12dp" - android:text="기타 (직접 작성)" /> + android:text="기타 (직접 작성)" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:text="기타(직접 작성)" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_semibold"/> + android:hint="신고 사유를 작성해주세요" + android:textColor="@color/neutral" + android:textSize="14sp" + android:fontFamily="@font/arita_medium"/> @@ -156,6 +179,8 @@ android:insetBottom="0dp" android:text="돌아가기" android:textColor="@color/neutral" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:cornerRadius="4dp" app:layout_constraintEnd_toStartOf="@id/btn_square_report_submit" app:layout_constraintStart_toStartOf="parent" @@ -172,6 +197,8 @@ android:insetTop="0dp" android:insetBottom="0dp" android:text="신고하기" + android:textSize="12sp" + android:fontFamily="@font/arita_semibold" app:cornerRadius="4dp" app:layout_constraintBottom_toBottomOf="@id/btn_square_report_back" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/fragment_my_replies_history.xml b/app/src/main/res/layout/fragment_my_replies_history.xml index cb561da0..8be05d65 100644 --- a/app/src/main/res/layout/fragment_my_replies_history.xml +++ b/app/src/main/res/layout/fragment_my_replies_history.xml @@ -28,7 +28,10 @@ + 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: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" From 23d1b5f2c137d8ac85236865467fbb907e755c00 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 18:52:35 +0900 Subject: [PATCH 52/55] =?UTF-8?q?design(square):=20=EA=B4=91=EC=9E=A5=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/layout/dialog_delete_friend.xml | 12 +++++ .../res/layout/dialog_square_add_friend.xml | 46 ++++++++++++++++--- .../main/res/layout/fragment_friends_list.xml | 19 +++++++- .../layout/fragment_friends_pending_list.xml | 11 ++++- .../res/layout/fragment_square_friends.xml | 18 ++++++-- .../res/layout/item_square_friend_list.xml | 6 +++ ...em_square_friend_pending_received_list.xml | 10 ++++ .../item_square_friend_pending_sent_list.xml | 8 ++++ 8 files changed, 119 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/layout/dialog_delete_friend.xml b/app/src/main/res/layout/dialog_delete_friend.xml index 6e6a02b7..3a770d59 100644 --- a/app/src/main/res/layout/dialog_delete_friend.xml +++ b/app/src/main/res/layout/dialog_delete_friend.xml @@ -13,6 +13,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="고북이님을\n삭제할까요?" + android:textColor="@color/neutral" + android:textSize="16sp" + android:fontFamily="@font/arita_semibold" + android:lineHeight="21sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -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_square_add_friend.xml b/app/src/main/res/layout/dialog_square_add_friend.xml index b21e8395..d840a1ac 100644 --- a/app/src/main/res/layout/dialog_square_add_friend.xml +++ b/app/src/main/res/layout/dialog_square_add_friend.xml @@ -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/fragment_friends_list.xml b/app/src/main/res/layout/fragment_friends_list.xml index b330d18a..6cb5f5a4 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_square_friends.xml b/app/src/main/res/layout/fragment_square_friends.xml index 62efc086..ddbbf598 100644 --- a/app/src/main/res/layout/fragment_square_friends.xml +++ b/app/src/main/res/layout/fragment_square_friends.xml @@ -29,20 +29,26 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - android:text="친구" /> + 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_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" From 1e5f318c3b1721260942b66008507aca3c3d1c47 Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 19:35:34 +0900 Subject: [PATCH 53/55] =?UTF-8?q?refactor:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=EC=BD=94=EB=93=9C=20=EC=A0=90=EA=B2=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../repository/CounselingRepositoryImpl.kt | 115 +++++++++++++++--- .../view/WeeklyReportUnlockDialog.kt | 9 ++ .../app/ui/square/view/FriendsListFragment.kt | 6 + .../layout/fragment_ego_room_daily_praise.xml | 1 + .../main/res/layout/fragment_friends_list.xml | 2 +- app/src/main/res/layout/fragment_square.xml | 34 ------ .../layout/item_counseling_weekly_report.xml | 3 +- 8 files changed, 117 insertions(+), 55 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d243d61c..8a09d2aa 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 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 16cc4738..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 @@ -24,6 +24,7 @@ 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) : @@ -90,33 +91,110 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns } override fun getWeeklyReports(size: Int): Flow> { - return Pager( - config = PagingConfig( - pageSize = size, - initialLoadSize = size, - enablePlaceholders = false +// return Pager( +// config = PagingConfig( +// pageSize = size, +// initialLoadSize = size, +// enablePlaceholders = false +// ), +// pagingSourceFactory = { +// WeeklyReportsPagingSource(apiService = apiService) +// } +// ).flow + + val dummyWeeklyReports = listOf( + WeeklyReport( + id = 1L, + startDate = "2024.02.03", + endDate = "2024.02.09", + isRead = true, + isLocked = false ), - pagingSourceFactory = { - WeeklyReportsPagingSource(apiService = apiService) - } - ).flow + WeeklyReport( + id = 2L, + 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 + ) + ) + + // 2. Pager 대신 flowOf와 PagingData.from을 사용하여 즉시 반환 + return flowOf( + PagingData.from(dummyWeeklyReports) + ) } 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 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) { + if (response.status == 200) { Result.success(response.data) } else { Result.failure(Exception("Error: ${response.status}")) @@ -128,7 +206,8 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns override suspend fun updateWeeklyReportStyle(reportStyle: ReportStyle): Result = try { - val response = apiService.updateWeeklyReportStyle(request = ReportStyleRequest(toneStyle = reportStyle)) + val response = + apiService.updateWeeklyReportStyle(request = ReportStyleRequest(toneStyle = reportStyle)) if (response.isSuccessful) { Result.success(reportStyle) } else { @@ -143,7 +222,7 @@ class CounselingRepositoryImpl @Inject constructor(private val apiService: Couns unlockType: WeeklyReportUnlockType ): Result = try { val response = apiService.unlockWeeklyReport(startDate = startDate, unlockType = unlockType) - if(response.status == 200) { + if (response.status == 200) { Result.success(Unit) } else { Result.failure(Exception("Error: ${response.status}")) 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 index 0d408ca8..d89793d9 100644 --- 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 @@ -1,8 +1,11 @@ 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 @@ -21,6 +24,12 @@ class WeeklyReportUnlockDialog(private val startDate: String): DialogFragment(R. 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) 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/res/layout/fragment_ego_room_daily_praise.xml b/app/src/main/res/layout/fragment_ego_room_daily_praise.xml index 038977ec..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 @@ -78,6 +78,7 @@ android:fontFamily="@font/arita_medium" android:lineHeight="21sp" android:letterSpacing="-0.02" + android:layout_marginTop="12dp" android:gravity="center_horizontal"/> diff --git a/app/src/main/res/layout/fragment_friends_list.xml b/app/src/main/res/layout/fragment_friends_list.xml index 6cb5f5a4..033f1321 100644 --- a/app/src/main/res/layout/fragment_friends_list.xml +++ b/app/src/main/res/layout/fragment_friends_list.xml @@ -33,7 +33,7 @@ android:id="@+id/tv_friends_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="3/10명" + tools:text="3/10명" android:textColor="@color/neutral_subtle" android:textSize="14sp" android:fontFamily="@font/arita_semibold" diff --git a/app/src/main/res/layout/fragment_square.xml b/app/src/main/res/layout/fragment_square.xml index e828d7d1..fd1532b3 100644 --- a/app/src/main/res/layout/fragment_square.xml +++ b/app/src/main/res/layout/fragment_square.xml @@ -244,40 +244,6 @@ android:layout_marginTop="2dp"/> - - - - - - - - - - - - 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 9b14ccbe..413e26db 100644 --- a/app/src/main/res/layout/item_counseling_weekly_report.xml +++ b/app/src/main/res/layout/item_counseling_weekly_report.xml @@ -16,7 +16,8 @@ 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"/> Date: Wed, 11 Feb 2026 19:45:42 +0900 Subject: [PATCH 54/55] =?UTF-8?q?refactor:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=EC=BD=94=EB=93=9C=20=EC=A0=90=EA=B2=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../paging/FriendRepliesPagingSource.kt | 2 +- .../java/com/egobook/app/di/ServiceModule.kt | 60 ------------------- .../egobook/app/di/module/ServiceModule.kt | 27 +++++---- 3 files changed, 18 insertions(+), 71 deletions(-) delete mode 100644 app/src/main/java/com/egobook/app/di/ServiceModule.kt 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 51615724..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 @@ -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/di/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/ServiceModule.kt deleted file mode 100644 index b33ec34a..00000000 --- a/app/src/main/java/com/egobook/app/di/ServiceModule.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.egobook.app.di - -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.NotificationApiService -import com.egobook.app.data.api.QuestionApiService -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object ServiceModule { - @Provides - @Singleton - fun provideCounselingService(retrofit: Retrofit): CounselingApiService { - return retrofit.create(CounselingApiService::class.java) - } - - @Provides - @Singleton - fun provideNotificationService(retrofit: Retrofit): NotificationApiService { - return retrofit.create(NotificationApiService::class.java) - } - - @Provides - @Singleton - fun provideFriendsService(retrofit: Retrofit): FriendsApiService { - return retrofit.create(FriendsApiService::class.java) - } - - @Provides - @Singleton - fun provideQuestionService(retrofit: Retrofit): QuestionApiService { - return retrofit.create(QuestionApiService::class.java) - } - - @Provides - @Singleton - fun provideAuthService(retrofit: Retrofit): AuthApiService = - retrofit.create(AuthApiService::class.java) - - @Provides - @Singleton - fun provideAccountService(retrofit: Retrofit): AccountApiService = - retrofit.create(AccountApiService::class.java) - - - @Provides - @Singleton - fun provideDiaryService(retrofit: Retrofit): DiaryApiService = - retrofit.create(DiaryApiService::class.java) - -} \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt index 080a53f1..f33a3e2b 100644 --- a/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/ServiceModule.kt @@ -1,8 +1,10 @@ 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 @@ -50,20 +52,25 @@ object ServiceModule { return retrofit.create(LetterApiService::class.java) } - /** - * 토큰 갱신용 AuthApiService - */ @Provides @Singleton - fun provideAuthApiService( - @AuthRetrofit retrofit: Retrofit - ): AuthApiService { - return retrofit.create(AuthApiService::class.java) + fun provideAIService(@AIApi retrofit: Retrofit): AIApiService { + return retrofit.create(AIApiService::class.java) } @Provides @Singleton - fun provideAIService(@AIApi retrofit: Retrofit): AIApiService { - return retrofit.create(AIApiService::class.java) - } + fun provideAuthService(@AuthRetrofit retrofit: Retrofit): AuthApiService = + retrofit.create(AuthApiService::class.java) + + @Provides + @Singleton + fun provideAccountService(@BackendApi retrofit: Retrofit): AccountApiService = + retrofit.create(AccountApiService::class.java) + + + @Provides + @Singleton + fun provideDiaryService(@BackendApi retrofit: Retrofit): DiaryApiService = + retrofit.create(DiaryApiService::class.java) } \ No newline at end of file From 8c8cf0e57402aa3390d1bc7be153e7a4b81e1c9c Mon Sep 17 00:00:00 2001 From: se05503 Date: Wed, 11 Feb 2026 19:49:55 +0900 Subject: [PATCH 55/55] =?UTF-8?q?refactor:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=EC=BD=94=EB=93=9C=20=EC=A0=90=EA=B2=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/egobook/app/ui/home/repository/UserRepository.kt | 1 + 1 file changed, 1 insertion(+) 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 360adfa8..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