From 336d411627d4ae90816b318a94cad25f8c5bb330 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 2 Apr 2026 12:22:08 +0900 Subject: [PATCH] =?UTF-8?q?refactor(mypage):=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20=EB=B6=84=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/mypage/MyPageFragment.kt | 58 +++---------------- .../function_button/FunctionButtonAdapter.kt | 47 +++++++++++++++ .../function_button/FunctionButtonItem.kt | 9 +++ ...20260402_쿠폰등록해외사용자본인인증예외.md | 25 ++++++++ 4 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt index c83a0133..0d274ebd 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt @@ -4,12 +4,9 @@ import android.annotation.SuppressLint import android.content.Intent import android.graphics.Rect import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.webkit.URLUtil import android.widget.Toast -import androidx.annotation.DrawableRes import androidx.core.net.toUri import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.GridLayoutManager @@ -23,13 +20,11 @@ import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.common.Constants -import kr.co.vividnext.sodalive.common.FunctionButtonHelper import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.databinding.FragmentMyBinding -import kr.co.vividnext.sodalive.databinding.ItemFunctionButtonBinding import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.moneyFormat @@ -42,6 +37,8 @@ import kr.co.vividnext.sodalive.mypage.block.BlockMemberActivity import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity import kr.co.vividnext.sodalive.mypage.can.coupon.CanCouponActivity import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity +import kr.co.vividnext.sodalive.mypage.function_button.FunctionButtonAdapter +import kr.co.vividnext.sodalive.mypage.function_button.FunctionButtonItem import kr.co.vividnext.sodalive.mypage.point.PointStatusActivity import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateActivity import kr.co.vividnext.sodalive.mypage.recent.RecentContentAdapter @@ -350,7 +347,12 @@ class MyPageFragment : BaseFragment(FragmentMyBinding::inflat } private fun updateFunctionButtons(isAuth: Boolean? = null) { + functionButtonAdapter.submitList(buildFunctionButtonItems(isAuth)) + } + + private fun buildFunctionButtonItems(isAuth: Boolean?): List { val isKoreanUser = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR" + val items = mutableListOf( FunctionButtonItem( iconRes = R.drawable.ic_my_storage, @@ -468,7 +470,7 @@ class MyPageFragment : BaseFragment(FragmentMyBinding::inflat } } - functionButtonAdapter.submitList(items) + return items } private fun showAuthDialog() { @@ -497,47 +499,3 @@ class MyPageFragment : BaseFragment(FragmentMyBinding::inflat } } } - -private data class FunctionButtonItem( - @field:DrawableRes val iconRes: Int, - val title: String, - val onClick: () -> Unit -) - -private class FunctionButtonAdapter : RecyclerView.Adapter() { - private val items = mutableListOf() - - inner class ViewHolder( - private val binding: ItemFunctionButtonBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: FunctionButtonItem) { - FunctionButtonHelper.setupFunctionButton( - buttonView = binding.root, - iconRes = item.iconRes, - title = item.title, - clickListener = View.OnClickListener { item.onClick() } - ) - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( - ItemFunctionButtonBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bind(items[position]) - } - - override fun getItemCount() = items.size - - @SuppressLint("NotifyDataSetChanged") - fun submitList(newItems: List) { - items.clear() - items.addAll(newItems) - notifyDataSetChanged() - } -} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt new file mode 100644 index 00000000..f53064f6 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt @@ -0,0 +1,47 @@ +package kr.co.vividnext.sodalive.mypage.function_button + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.common.FunctionButtonHelper +import kr.co.vividnext.sodalive.databinding.ItemFunctionButtonBinding + +class FunctionButtonAdapter : RecyclerView.Adapter() { + private val items = mutableListOf() + + inner class ViewHolder( + private val binding: ItemFunctionButtonBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: FunctionButtonItem) { + FunctionButtonHelper.setupFunctionButton( + buttonView = binding.root, + iconRes = item.iconRes, + title = item.title, + clickListener = View.OnClickListener { item.onClick() } + ) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemFunctionButtonBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount() = items.size + + @SuppressLint("NotifyDataSetChanged") + fun submitList(newItems: List) { + items.clear() + items.addAll(newItems) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt new file mode 100644 index 00000000..6f6f4b66 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.mypage.function_button + +import androidx.annotation.DrawableRes + +data class FunctionButtonItem( + @param:DrawableRes val iconRes: Int, + val title: String, + val onClick: () -> Unit +) diff --git a/docs/20260402_쿠폰등록해외사용자본인인증예외.md b/docs/20260402_쿠폰등록해외사용자본인인증예외.md index fa1ced48..57c73400 100644 --- a/docs/20260402_쿠폰등록해외사용자본인인증예외.md +++ b/docs/20260402_쿠폰등록해외사용자본인인증예외.md @@ -4,6 +4,7 @@ - 마이페이지 `btnCoupon` 터치 시 한국 사용자는 기존처럼 본인인증이 필요하고, 한국이 아닌 사용자는 본인인증 없이 쿠폰 등록 화면으로 이동하도록 수정한다. - 마이페이지 `btnCoupon`은 한국이 아닌 사용자에 한해 민감한 콘텐츠 보기 설정이 켜져 있을 때만 화면에 보이도록 수정한다. - 마이페이지 기능 버튼 영역을 `RecyclerView` 기반 그리드로 전환해 숨겨진 버튼이 있어도 중간 빈 슬롯 없이 왼쪽부터 자연스럽게 재배치되도록 수정한다. +- `MyPageFragment`에 혼합된 기능 버튼 모델/어댑터 책임을 분리해 화면 제어와 렌더링 관심사를 정리한다. ## 체크리스트 - [x] AC1: `countryCode == "KR"` 이고 `isAuth == false`인 경우 기존처럼 인증 필요 토스트와 `showAuthDialog()`가 실행된다. @@ -24,6 +25,10 @@ - QA: `fragment_my.xml`이 `RecyclerView`를 사용하고, `MyPageFragment`가 쿠폰/본인인증 버튼을 조건에 따라 아이템 리스트에 포함/제외하는지 코드 확인 - [x] AC9: 쿠폰 버튼이 숨겨지는 비한국 사용자(`isAdultContentVisible == false`)에서도 기능 버튼 간 가로/세로 간격이 기존 4열 그리드와 동일하게 유지된다. - QA: `GridLayoutManager(4)`와 `GridSpacingItemDecoration(..., 16dp, false)` 적용으로 기존 16dp 간격 패턴을 유지하는지 코드 확인 +- [x] AC10: `FunctionButtonItem`과 `FunctionButtonAdapter`가 `MyPageFragment` 밖 별도 파일로 분리되어 Fragment가 화면 상태/버튼 목록 조립에 집중한다. + - QA: 모델/어댑터 클래스가 별도 파일로 이동하고 `MyPageFragment` 하단 정의가 제거됐는지 코드 확인 +- [x] AC11: 기능 버튼 조립 로직은 `buildFunctionButtonItems()`로 분리되며, 기존 버튼 순서/조건/클릭 동작은 유지된다. + - QA: `MyPageFragment`가 `updateFunctionButtons()`에서 조립 함수 결과만 어댑터에 전달하는지, 버튼 분기 로직이 동일한지 코드 확인 ## 검증 기록 - 2026-04-02 @@ -71,3 +76,23 @@ - 기존 버튼 제목, 아이콘, 클릭 액션, 한국/비한국 및 인증/민감 콘텐츠 조건은 그대로 유지됐다. - `.kt`/`.xml` 대상 `lsp_diagnostics`는 현재 환경에 Kotlin/XML LSP가 없어 실행 불가(`No LSP server configured for extension: .kt/.xml`)였다. - `:app:testDebugUnitTest`, `:app:assembleDebug` 실행은 `BUILD SUCCESSFUL`로 완료됐다. +- 2026-04-02 + - 무엇/왜/어떻게: `RecyclerView` 전환 이후 `MyPageFragment` 안에 기능 버튼 모델, 어댑터, 버튼 목록 조립이 함께 들어와 관심사가 섞였다. 기존 `mypage` 패키지의 어댑터 분리 패턴에 맞춰 `FunctionButtonItem`과 `FunctionButtonAdapter`를 별도 파일로 분리하고, Fragment에는 `buildFunctionButtonItems()`를 통한 버튼 목록 조립과 화면 제어만 남기도록 정리했다. + - 실행 명령/도구: + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)` + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/recent/RecentContentAdapter.kt)` + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/profile/tag/MemberTagAdapter.kt)` + - `apply_patch(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)` + - `apply_patch(app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt)` + - `apply_patch(app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt)` + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt)` + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonAdapter.kt)` + - `read(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)` + - `lsp_diagnostics(app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt)` + - `./gradlew :app:testDebugUnitTest :app:assembleDebug` + - 결과: + - `FunctionButtonItem`은 `app/src/main/java/kr/co/vividnext/sodalive/mypage/function_button/FunctionButtonItem.kt`로, `FunctionButtonAdapter`는 `.../FunctionButtonAdapter.kt`로 분리됐다. + - `MyPageFragment` 하단의 내부 모델/어댑터 정의는 제거됐고, `updateFunctionButtons()`는 `buildFunctionButtonItems()` 결과를 어댑터에 전달하는 역할만 수행한다. + - 버튼 순서, 쿠폰 노출 조건, 한국/비한국 인증 분기, 각 버튼 클릭 동작은 유지됐다. + - `.kt` 대상 `lsp_diagnostics`는 현재 환경에 Kotlin LSP가 없어 실행 불가(`No LSP server configured for extension: .kt`)였다. + - `:app:testDebugUnitTest`, `:app:assembleDebug` 실행은 `BUILD SUCCESSFUL`로 완료됐다.