refactor(mypage): 기능 버튼 렌더링 책임을 분리한다
This commit is contained in:
@@ -4,12 +4,9 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
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.audio_content.detail.AudioContentDetailActivity
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
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.GridSpacingItemDecoration
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
|
import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
|
||||||
import kr.co.vividnext.sodalive.databinding.FragmentMyBinding
|
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.explorer.profile.UserProfileActivity
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
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.charge.CanChargeActivity
|
||||||
import kr.co.vividnext.sodalive.mypage.can.coupon.CanCouponActivity
|
import kr.co.vividnext.sodalive.mypage.can.coupon.CanCouponActivity
|
||||||
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity
|
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.point.PointStatusActivity
|
||||||
import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateActivity
|
import kr.co.vividnext.sodalive.mypage.profile.ProfileUpdateActivity
|
||||||
import kr.co.vividnext.sodalive.mypage.recent.RecentContentAdapter
|
import kr.co.vividnext.sodalive.mypage.recent.RecentContentAdapter
|
||||||
@@ -350,7 +347,12 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFunctionButtons(isAuth: Boolean? = null) {
|
private fun updateFunctionButtons(isAuth: Boolean? = null) {
|
||||||
|
functionButtonAdapter.submitList(buildFunctionButtonItems(isAuth))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildFunctionButtonItems(isAuth: Boolean?): List<FunctionButtonItem> {
|
||||||
val isKoreanUser = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
val isKoreanUser = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
||||||
|
|
||||||
val items = mutableListOf(
|
val items = mutableListOf(
|
||||||
FunctionButtonItem(
|
FunctionButtonItem(
|
||||||
iconRes = R.drawable.ic_my_storage,
|
iconRes = R.drawable.ic_my_storage,
|
||||||
@@ -468,7 +470,7 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
functionButtonAdapter.submitList(items)
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showAuthDialog() {
|
private fun showAuthDialog() {
|
||||||
@@ -497,47 +499,3 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class FunctionButtonItem(
|
|
||||||
@field:DrawableRes val iconRes: Int,
|
|
||||||
val title: String,
|
|
||||||
val onClick: () -> Unit
|
|
||||||
)
|
|
||||||
|
|
||||||
private class FunctionButtonAdapter : RecyclerView.Adapter<FunctionButtonAdapter.ViewHolder>() {
|
|
||||||
private val items = mutableListOf<FunctionButtonItem>()
|
|
||||||
|
|
||||||
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<FunctionButtonItem>) {
|
|
||||||
items.clear()
|
|
||||||
items.addAll(newItems)
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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<FunctionButtonAdapter.ViewHolder>() {
|
||||||
|
private val items = mutableListOf<FunctionButtonItem>()
|
||||||
|
|
||||||
|
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<FunctionButtonItem>) {
|
||||||
|
items.clear()
|
||||||
|
items.addAll(newItems)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
- 마이페이지 `btnCoupon` 터치 시 한국 사용자는 기존처럼 본인인증이 필요하고, 한국이 아닌 사용자는 본인인증 없이 쿠폰 등록 화면으로 이동하도록 수정한다.
|
- 마이페이지 `btnCoupon` 터치 시 한국 사용자는 기존처럼 본인인증이 필요하고, 한국이 아닌 사용자는 본인인증 없이 쿠폰 등록 화면으로 이동하도록 수정한다.
|
||||||
- 마이페이지 `btnCoupon`은 한국이 아닌 사용자에 한해 민감한 콘텐츠 보기 설정이 켜져 있을 때만 화면에 보이도록 수정한다.
|
- 마이페이지 `btnCoupon`은 한국이 아닌 사용자에 한해 민감한 콘텐츠 보기 설정이 켜져 있을 때만 화면에 보이도록 수정한다.
|
||||||
- 마이페이지 기능 버튼 영역을 `RecyclerView` 기반 그리드로 전환해 숨겨진 버튼이 있어도 중간 빈 슬롯 없이 왼쪽부터 자연스럽게 재배치되도록 수정한다.
|
- 마이페이지 기능 버튼 영역을 `RecyclerView` 기반 그리드로 전환해 숨겨진 버튼이 있어도 중간 빈 슬롯 없이 왼쪽부터 자연스럽게 재배치되도록 수정한다.
|
||||||
|
- `MyPageFragment`에 혼합된 기능 버튼 모델/어댑터 책임을 분리해 화면 제어와 렌더링 관심사를 정리한다.
|
||||||
|
|
||||||
## 체크리스트
|
## 체크리스트
|
||||||
- [x] AC1: `countryCode == "KR"` 이고 `isAuth == false`인 경우 기존처럼 인증 필요 토스트와 `showAuthDialog()`가 실행된다.
|
- [x] AC1: `countryCode == "KR"` 이고 `isAuth == false`인 경우 기존처럼 인증 필요 토스트와 `showAuthDialog()`가 실행된다.
|
||||||
@@ -24,6 +25,10 @@
|
|||||||
- QA: `fragment_my.xml`이 `RecyclerView`를 사용하고, `MyPageFragment`가 쿠폰/본인인증 버튼을 조건에 따라 아이템 리스트에 포함/제외하는지 코드 확인
|
- QA: `fragment_my.xml`이 `RecyclerView`를 사용하고, `MyPageFragment`가 쿠폰/본인인증 버튼을 조건에 따라 아이템 리스트에 포함/제외하는지 코드 확인
|
||||||
- [x] AC9: 쿠폰 버튼이 숨겨지는 비한국 사용자(`isAdultContentVisible == false`)에서도 기능 버튼 간 가로/세로 간격이 기존 4열 그리드와 동일하게 유지된다.
|
- [x] AC9: 쿠폰 버튼이 숨겨지는 비한국 사용자(`isAdultContentVisible == false`)에서도 기능 버튼 간 가로/세로 간격이 기존 4열 그리드와 동일하게 유지된다.
|
||||||
- QA: `GridLayoutManager(4)`와 `GridSpacingItemDecoration(..., 16dp, false)` 적용으로 기존 16dp 간격 패턴을 유지하는지 코드 확인
|
- QA: `GridLayoutManager(4)`와 `GridSpacingItemDecoration(..., 16dp, false)` 적용으로 기존 16dp 간격 패턴을 유지하는지 코드 확인
|
||||||
|
- [x] AC10: `FunctionButtonItem`과 `FunctionButtonAdapter`가 `MyPageFragment` 밖 별도 파일로 분리되어 Fragment가 화면 상태/버튼 목록 조립에 집중한다.
|
||||||
|
- QA: 모델/어댑터 클래스가 별도 파일로 이동하고 `MyPageFragment` 하단 정의가 제거됐는지 코드 확인
|
||||||
|
- [x] AC11: 기능 버튼 조립 로직은 `buildFunctionButtonItems()`로 분리되며, 기존 버튼 순서/조건/클릭 동작은 유지된다.
|
||||||
|
- QA: `MyPageFragment`가 `updateFunctionButtons()`에서 조립 함수 결과만 어댑터에 전달하는지, 버튼 분기 로직이 동일한지 코드 확인
|
||||||
|
|
||||||
## 검증 기록
|
## 검증 기록
|
||||||
- 2026-04-02
|
- 2026-04-02
|
||||||
@@ -71,3 +76,23 @@
|
|||||||
- 기존 버튼 제목, 아이콘, 클릭 액션, 한국/비한국 및 인증/민감 콘텐츠 조건은 그대로 유지됐다.
|
- 기존 버튼 제목, 아이콘, 클릭 액션, 한국/비한국 및 인증/민감 콘텐츠 조건은 그대로 유지됐다.
|
||||||
- `.kt`/`.xml` 대상 `lsp_diagnostics`는 현재 환경에 Kotlin/XML LSP가 없어 실행 불가(`No LSP server configured for extension: .kt/.xml`)였다.
|
- `.kt`/`.xml` 대상 `lsp_diagnostics`는 현재 환경에 Kotlin/XML LSP가 없어 실행 불가(`No LSP server configured for extension: .kt/.xml`)였다.
|
||||||
- `:app:testDebugUnitTest`, `:app:assembleDebug` 실행은 `BUILD SUCCESSFUL`로 완료됐다.
|
- `: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`로 완료됐다.
|
||||||
|
|||||||
Reference in New Issue
Block a user