fix(home): 팔로잉 크리에이터 전체 버튼을 고정한다
This commit is contained in:
@@ -115,8 +115,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private val popularCommunityAdapter = HomePopularCommunityAdapter { openPopularCommunityPost(it) }
|
private val popularCommunityAdapter = HomePopularCommunityAdapter { openPopularCommunityPost(it) }
|
||||||
private val creatorRankingAdapter = CreatorRankingAdapter { openCreatorRankingProfile(it) }
|
private val creatorRankingAdapter = CreatorRankingAdapter { openCreatorRankingProfile(it) }
|
||||||
private val followingCreatorAdapter = HomeFollowingCreatorAdapter(
|
private val followingCreatorAdapter = HomeFollowingCreatorAdapter(
|
||||||
onClickItem = { openCreatorProfile(it.creatorId) },
|
onClickItem = { openCreatorProfile(it.creatorId) }
|
||||||
onClickAll = { openFollowingCreatorAll() }
|
|
||||||
)
|
)
|
||||||
private val followingLiveAdapter = HomeFollowingLiveAdapter { onFollowingLiveClick(it) }
|
private val followingLiveAdapter = HomeFollowingLiveAdapter { onFollowingLiveClick(it) }
|
||||||
private val followingChatAdapter = HomeFollowingChatAdapter { openFollowingChat(it) }
|
private val followingChatAdapter = HomeFollowingChatAdapter { openFollowingChat(it) }
|
||||||
@@ -264,6 +263,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpFollowingAdapters() {
|
private fun setUpFollowingAdapters() {
|
||||||
|
binding.tvHomeFollowingCreatorAll.setOnClickListener { openFollowingCreatorAll() }
|
||||||
binding.rvHomeFollowingCreators.apply {
|
binding.rvHomeFollowingCreators.apply {
|
||||||
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
||||||
adapter = followingCreatorAdapter
|
adapter = followingCreatorAdapter
|
||||||
|
|||||||
+9
-35
@@ -10,9 +10,8 @@ import kr.co.vividnext.sodalive.R
|
|||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingCreatorUiItem
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingCreatorUiItem
|
||||||
|
|
||||||
class HomeFollowingCreatorAdapter(
|
class HomeFollowingCreatorAdapter(
|
||||||
private val onClickItem: (HomeFollowingCreatorUiItem) -> Unit = {},
|
private val onClickItem: (HomeFollowingCreatorUiItem) -> Unit = {}
|
||||||
private val onClickAll: () -> Unit = {}
|
) : RecyclerView.Adapter<HomeFollowingCreatorAdapter.CreatorViewHolder>() {
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
private var items: List<HomeFollowingCreatorUiItem> = emptyList()
|
private var items: List<HomeFollowingCreatorUiItem> = emptyList()
|
||||||
|
|
||||||
fun submitItems(items: List<HomeFollowingCreatorUiItem>) {
|
fun submitItems(items: List<HomeFollowingCreatorUiItem>) {
|
||||||
@@ -20,29 +19,18 @@ class HomeFollowingCreatorAdapter(
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CreatorViewHolder {
|
||||||
val layoutResId = when (viewType) {
|
val view = LayoutInflater.from(parent.context)
|
||||||
VIEW_TYPE_ALL -> R.layout.item_home_following_creator_all
|
.inflate(R.layout.item_home_following_creator, parent, false)
|
||||||
else -> R.layout.item_home_following_creator
|
|
||||||
}
|
|
||||||
val view = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
|
|
||||||
view.layoutParams = recyclerItemLayoutParams(parent)
|
view.layoutParams = recyclerItemLayoutParams(parent)
|
||||||
return when (viewType) {
|
return CreatorViewHolder(view, onClickItem)
|
||||||
VIEW_TYPE_ALL -> AllViewHolder(view, onClickAll)
|
|
||||||
else -> CreatorViewHolder(view, onClickItem)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CreatorViewHolder, position: Int) {
|
||||||
when (holder) {
|
holder.bind(items[position])
|
||||||
is AllViewHolder -> holder.bind()
|
|
||||||
is CreatorViewHolder -> holder.bind(items[position])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size + if (items.isNotEmpty()) 1 else 0
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int = if (position < items.size) VIEW_TYPE_CREATOR else VIEW_TYPE_ALL
|
|
||||||
|
|
||||||
class CreatorViewHolder(
|
class CreatorViewHolder(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
@@ -57,18 +45,4 @@ class HomeFollowingCreatorAdapter(
|
|||||||
itemView.setOnClickListener { onClickItem(item) }
|
itemView.setOnClickListener { onClickItem(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AllViewHolder(
|
|
||||||
itemView: View,
|
|
||||||
private val onClickAll: () -> Unit
|
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
|
||||||
fun bind() {
|
|
||||||
itemView.setOnClickListener { onClickAll() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
private const val VIEW_TYPE_CREATOR = 0
|
|
||||||
private const val VIEW_TYPE_ALL = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,17 +276,29 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/spacing_12"
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
android:orientation="vertical">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rv_home_following_creators"
|
android:id="@+id/rv_home_following_creators"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingHorizontal="@dimen/spacing_14"
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/item_home_following_creator" />
|
tools:listitem="@layout/item_home_following_creator" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_home_following_creator_all"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="@dimen/home_live_more_width"
|
||||||
|
android:layout_height="@dimen/home_live_row_height"
|
||||||
|
android:layout_marginEnd="@dimen/spacing_20"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/screen_home_theme_all"
|
||||||
|
android:textColor="@color/soda_400" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_home_following_creator_all"
|
|
||||||
style="@style/Typography.Body5"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="75dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:text="@string/screen_home_theme_all"
|
|
||||||
android:textColor="@color/soda_400" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/Typography.Body5"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/spacing_6"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:text="@string/screen_home_theme_all"
|
|
||||||
android:visibility="invisible" />
|
|
||||||
</LinearLayout>
|
|
||||||
+13
-14
@@ -316,29 +316,28 @@ class HomeFollowingFragmentSourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `팔로잉 크리에이터 목록은 전체 item을 마지막에 추가하고 이동을 연결한다`() {
|
fun `팔로잉 크리에이터 전체 버튼은 목록 밖에 고정하고 이동을 연결한다`() {
|
||||||
|
val layout = homeMainLayoutSource()
|
||||||
|
val section = layout.substringAfter("@+id/ll_home_following_creators_section")
|
||||||
|
.substringBefore("@+id/ll_home_following_on_air_section")
|
||||||
val adapter = projectFile(
|
val adapter = projectFile(
|
||||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingCreatorAdapter.kt"
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingCreatorAdapter.kt"
|
||||||
).readText()
|
).readText()
|
||||||
val fragment = homeMainFragmentSource()
|
val fragment = homeMainFragmentSource()
|
||||||
val allLayoutFile = projectFileOrNull("app/src/main/res/layout/item_home_following_creator_all.xml")
|
|
||||||
|
|
||||||
assertTrue(allLayoutFile != null)
|
assertTrue(section.contains("@+id/rv_home_following_creators"))
|
||||||
val allLayout = allLayoutFile?.readText().orEmpty()
|
assertTrue(section.contains("@+id/tv_home_following_creator_all"))
|
||||||
|
assertTrue(adapter.contains("override fun getItemCount(): Int = items.size"))
|
||||||
assertTrue(adapter.contains("VIEW_TYPE_CREATOR"))
|
assertFalse(adapter.contains("VIEW_TYPE_ALL"))
|
||||||
assertTrue(adapter.contains("VIEW_TYPE_ALL"))
|
assertFalse(adapter.contains("AllViewHolder"))
|
||||||
assertTrue(adapter.contains("getItemViewType(position: Int)"))
|
assertFalse(adapter.contains("item_home_following_creator_all"))
|
||||||
assertTrue(adapter.contains("if (position < items.size) VIEW_TYPE_CREATOR else VIEW_TYPE_ALL"))
|
assertTrue(projectFileOrNull("app/src/main/res/layout/item_home_following_creator_all.xml") == null)
|
||||||
assertTrue(adapter.contains("override fun getItemCount(): Int = items.size + if (items.isNotEmpty()) 1 else 0"))
|
|
||||||
assertTrue(adapter.contains("R.layout.item_home_following_creator_all"))
|
|
||||||
assertTrue(adapter.contains("AllViewHolder"))
|
|
||||||
assertTrue(fragment.contains("import kr.co.vividnext.sodalive.following.FollowingCreatorActivity"))
|
assertTrue(fragment.contains("import kr.co.vividnext.sodalive.following.FollowingCreatorActivity"))
|
||||||
assertTrue(fragment.contains("HomeFollowingCreatorAdapter("))
|
assertTrue(fragment.contains("HomeFollowingCreatorAdapter("))
|
||||||
assertTrue(fragment.contains("onClickAll = { openFollowingCreatorAll() }"))
|
assertTrue(fragment.contains("binding.tvHomeFollowingCreatorAll.setOnClickListener"))
|
||||||
|
assertTrue(fragment.contains("openFollowingCreatorAll()"))
|
||||||
assertTrue(fragment.contains("private fun openFollowingCreatorAll()"))
|
assertTrue(fragment.contains("private fun openFollowingCreatorAll()"))
|
||||||
assertTrue(fragment.contains("startActivity(Intent(requireContext(), FollowingCreatorActivity::class.java))"))
|
assertTrue(fragment.contains("startActivity(Intent(requireContext(), FollowingCreatorActivity::class.java))"))
|
||||||
assertTrue(allLayout.contains("@string/screen_home_theme_all"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1441,7 +1441,112 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Phase 14: 팔로잉 크리에이터 전체 버튼 우측 고정
|
||||||
|
|
||||||
|
**Phase 결과:** 팔로잉 탭 최상단 팔로잉 크리에이터 섹션에서 프로필 목록만 수평 스크롤되고, `전체` 버튼은 추천 탭 라이브 섹션과 동일하게 우측 끝에 고정된다.
|
||||||
|
|
||||||
|
**선행조건:** Phase 8의 기존 팔로잉 크리에이터 `전체` 버튼 클릭 라우팅과 `FollowingCreatorActivity` 진입 동작이 유지되어야 한다.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P14-T1`~`P14-GATE` 완료, 팔로잉 크리에이터 `전체` 버튼이 RecyclerView item이 아니라 별도 TextView로 분리됐음을 source/local unit test와 resource merge로 검증하고, 우측 고정 위치는 수동 확인으로 판정한다.
|
||||||
|
|
||||||
|
#### Task 14.1 팔로잉 전체 버튼 고정 구조 RED 테스트 추가
|
||||||
|
|
||||||
|
**Goal 실행 `P14-T1`:** 현재 RecyclerView 마지막 item 방식의 `전체` 버튼 구조를 실패 test로 고정하고, 추천 탭과 같은 우측 고정 구조가 없음을 재현한다.
|
||||||
|
|
||||||
|
- **시작 조건:** PRD의 `2026-08-18 팔로잉 크리에이터 전체 버튼 고정 Requirements` 확정.
|
||||||
|
- **완료 증거:** 구현 전 focused source/local test가 `tv_home_following_creator_all` 고정 TextView 부재 또는 adapter의 `VIEW_TYPE_ALL` 잔존으로 실패한다.
|
||||||
|
- **범위 밖:** 팔로잉 크리에이터 API/mapper 변경, 다른 팔로잉 섹션 UI 변경, 추천 탭 코드 변경.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt`
|
||||||
|
|
||||||
|
- [x] **RED:** `fragment_v2_main_home.xml`에서 `rv_home_following_creators`와 sibling `TextView` `@+id/tv_home_following_creator_all`이 같은 팔로잉 크리에이터 section 아래에 존재하는지 검증한다. 레이아웃 폭, margin, gravity, visibility 같은 UI 표현 속성은 source test로 직접 검증하지 않는다.
|
||||||
|
- [x] **RED:** `HomeFollowingCreatorAdapter`가 `VIEW_TYPE_ALL`, `AllViewHolder`, `item_home_following_creator_all`을 더 이상 사용하지 않고 `getItemCount()`가 `items.size`를 반환하는지 검증한다.
|
||||||
|
- [x] **RED 확인:** `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`를 실행해 현재 구현의 우측 고정 TextView 부재와 adapter `전체` item 잔존으로 인한 assertion 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** Task 14.2와 Task 14.3의 최소 구현으로 RED를 통과시킨다.
|
||||||
|
- [x] **GREEN 확인:** 같은 focused test를 다시 실행해 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 구조와 라우팅 계약 검증만 남기고, 우측 고정 위치는 Task 14.4 수동 확인 항목으로 유지한다.
|
||||||
|
|
||||||
|
검증 기록:
|
||||||
|
|
||||||
|
- 2026-08-18 RED: focused test 17개 중 `팔로잉 크리에이터 전체 버튼은 목록 밖에 고정하고 이동을 연결한다` 1개가 `HomeFollowingFragmentSourceTest.kt:329` assertion으로 실패했다. 현재 layout에 sibling `tv_home_following_creator_all`이 없어 요청 구조가 구현되지 않은 것이 실패 원인이다.
|
||||||
|
- 2026-08-18 GREEN: `--rerun-tasks`로 focused test를 재실행해 17개 모두 PASS했다. 테스트는 RecyclerView와 고정 TextView의 sibling 구조, creator-only adapter, obsolete 전체 item layout 제거, 기존 `FollowingCreatorActivity` 이동 연결을 검증한다.
|
||||||
|
|
||||||
|
#### Task 14.2 팔로잉 크리에이터 섹션 레이아웃을 추천 탭 구조로 변경
|
||||||
|
|
||||||
|
**Goal 실행 `P14-T2`:** 팔로잉 크리에이터 RecyclerView 밖 우측 끝에 고정 `전체` TextView를 추가한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P14-T1` RED 확인.
|
||||||
|
- **완료 증거:** 구조 source test, resource merge 통과와 수동 확인 가능 상태 기록.
|
||||||
|
- **범위 밖:** 팔로잉 크리에이터 item 디자인 변경, 다른 섹션 spacing 변경, 새 drawable/string 추가.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `app/src/main/res/layout/fragment_v2_main_home.xml`
|
||||||
|
|
||||||
|
- [x] **RED:** Task 14.1의 실패 test를 기준으로 한다.
|
||||||
|
- [x] **RED 확인:** Task 14.1 실행 결과를 확인한다.
|
||||||
|
- [x] **GREEN:** `ll_home_following_creators_section`의 orientation을 `horizontal`로 바꾸고, `rv_home_following_creators`는 `layout_width="0dp"`, `layout_weight="1"`, `paddingStart="@dimen/spacing_20"`, `clipToPadding="false"`로 둔다.
|
||||||
|
- [x] **GREEN:** `rv_home_following_creators` 뒤에 `TextView` `@+id/tv_home_following_creator_all`을 추가하고, 추천 탭의 `tv_home_live_all`처럼 `@string/screen_home_theme_all`, `@color/soda_400`, `@color/black`, center gravity, 우측 margin을 사용한다.
|
||||||
|
- [x] **GREEN 확인:** `./gradlew --no-daemon --rerun-tasks :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"` 실행 과정의 resource merge와 focused test 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 레이아웃 변경이 만든 중복만 점검하고, 추천 탭 live 섹션과 팔로잉 섹션 구조 차이가 의도된 범위인지 기록한다.
|
||||||
|
|
||||||
|
검증 기록:
|
||||||
|
|
||||||
|
- 2026-08-18: 추천 탭 `ll_home_live_section`과 동일한 horizontal sibling 구조를 적용했다. RecyclerView는 남은 폭을 사용하고 `tv_home_following_creator_all`은 우측 고정 영역에 배치했다. 기존 팔로잉 크리에이터 item과 다른 섹션은 변경하지 않았으며, resource merge와 focused test가 PASS했다.
|
||||||
|
|
||||||
|
#### Task 14.3 adapter의 전체 item 제거와 클릭 연결 유지
|
||||||
|
|
||||||
|
**Goal 실행 `P14-T3`:** `HomeFollowingCreatorAdapter`는 크리에이터 item만 표시하고, `전체` 클릭은 Fragment의 고정 TextView에서 처리한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P14-T2` 완료.
|
||||||
|
- **완료 증거:** adapter source/local test, Kotlin compile 통과.
|
||||||
|
- **범위 밖:** `openFollowingCreatorAll()` 목적지 변경, `HomeFollowingCreatorUiItem` 모델 변경.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingCreatorAdapter.kt`
|
||||||
|
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
|
||||||
|
- Delete: `app/src/main/res/layout/item_home_following_creator_all.xml`
|
||||||
|
|
||||||
|
- [x] **RED:** Task 14.1의 adapter 실패 test를 기준으로 한다.
|
||||||
|
- [x] **RED 확인:** Task 14.1 실행 결과를 확인한다.
|
||||||
|
- [x] **GREEN:** `HomeFollowingCreatorAdapter`에서 `onClickAll`, `VIEW_TYPE_ALL`, `AllViewHolder`, `item_home_following_creator_all`, `items.size + 1` item count 로직을 제거한다.
|
||||||
|
- [x] **GREEN:** `HomeMainFragment`에서 `HomeFollowingCreatorAdapter` 생성자는 `onClickItem`만 전달하고, `binding.tvHomeFollowingCreatorAll.setOnClickListener { openFollowingCreatorAll() }`로 기존 전체 목록 이동을 연결한다.
|
||||||
|
- [x] **GREEN:** 팔로잉 크리에이터 섹션 visibility가 `followingCreators` empty 여부로 제어될 때 고정 `전체` TextView도 같은 section 안에서 함께 숨겨지도록 기존 section visibility 로직을 유지한다.
|
||||||
|
- [x] **GREEN 확인:** `--rerun-tasks` focused test 실행 과정의 Kotlin compile과 test 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 삭제한 `item_home_following_creator_all.xml` 참조가 남지 않았는지 `rg -n "item_home_following_creator_all|VIEW_TYPE_ALL|AllViewHolder|tv_home_following_creator_all" app/src/main`로 확인한다.
|
||||||
|
|
||||||
|
검증 기록:
|
||||||
|
|
||||||
|
- 2026-08-18: adapter를 creator-only 구조로 단순화하고 Fragment의 고정 TextView에 기존 `openFollowingCreatorAll()`을 연결했다. obsolete item layout을 삭제했으며, `rg` 결과 production에는 신규 고정 TextView id 참조만 남아 있다.
|
||||||
|
|
||||||
|
#### Task 14.4 팔로잉 전체 버튼 고정 회귀 검증
|
||||||
|
|
||||||
|
**Goal 실행 `P14-GATE`:** 팔로잉 크리에이터 전체 버튼 고정 구조와 기존 팔로잉 탭 동작에 회귀가 없는지 판정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P14-T1`, `P14-T2`, `P14-T3` 완료.
|
||||||
|
- **완료 증거:** 아래 자동 검증 결과와 수동 확인 가능 여부를 기록한다.
|
||||||
|
- **범위 밖:** 연결 기기 없는 환경에서 수동 검증을 성공으로 추정하는 행위, 사용자가 요청하지 않은 androidTest 작성.
|
||||||
|
|
||||||
|
- [x] `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"` PASS.
|
||||||
|
- [x] `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"` PASS.
|
||||||
|
- [x] `./gradlew --no-daemon :app:mergeDebugResources` PASS.
|
||||||
|
- [x] `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
|
||||||
|
- [x] `./gradlew --no-daemon :app:ktlintCheck` PASS. 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
|
||||||
|
- [x] `git diff --check` PASS.
|
||||||
|
- [x] 사용자 요청에 따라 visual QA와 기기 조작은 실행하지 않았다. 사용자가 `app/build/outputs/apk/debug/app-debug.apk`로 팔로잉 크리에이터 목록 스크롤 중 `전체` 우측 고정과 전체 팔로잉 목록 이동을 수동 확인했다.
|
||||||
|
|
||||||
|
검증 기록:
|
||||||
|
|
||||||
|
- 2026-08-18: focused RED 1건을 확인한 뒤 최소 구현으로 GREEN 전환했다. 팔로잉 회귀 테스트, resource merge, Kotlin compile, ktlint, `git diff --check`, `:app:assembleDebug`가 모두 PASS했다. 첫 통합 검증에서 이번 변경의 adapter 마지막 빈 줄 1건이 ktlint 실패했으나 해당 줄만 제거한 뒤 전체 게이트를 재실행해 PASS했다. Oracle 읽기 전용 리뷰는 Critical/Important finding 없이 승인했다. visual QA와 기기 조작은 사용자 요청으로 생략했으며, 사용자가 debug APK로 `전체` 우측 고정과 전체 팔로잉 목록 이동을 수동 확인했다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Verification Log
|
## Verification Log
|
||||||
|
- 2026-08-18 Phase 14 구현 완료: 팔로잉 크리에이터 `전체`를 RecyclerView 마지막 item에서 추천 탭 라이브 섹션과 같은 우측 고정 TextView로 변경했다. adapter는 creator item만 표시하고, 기존 로그인 가드와 `FollowingCreatorActivity` 이동을 고정 TextView 클릭에 연결했다. TDD RED/GREEN, 팔로잉 회귀, resource merge, Kotlin compile, ktlint, `git diff --check`, debug APK assemble가 PASS했다. visual QA와 기기 조작은 사용자 요청으로 실행하지 않았고, 사용자가 `app/build/outputs/apk/debug/app-debug.apk`로 팔로잉 크리에이터 목록 스크롤 중 `전체` 고정과 클릭 이동을 수동 확인했다. Phase 14 리뷰에서 Critical/Important finding은 없었다.
|
||||||
|
- 2026-08-18 문서 전용 계획 추가: 팔로잉 크리에이터 `전체` 버튼을 RecyclerView 마지막 item에서 추천 탭 라이브 섹션과 같은 우측 고정 TextView 구조로 변경하는 요구사항을 PRD에 추가하고, 구현은 진행하지 않은 상태로 Phase 14 계획/TASK만 작성했다.
|
||||||
- 2026-07-31 이번 Phase 1~13 리뷰 재검증: 현재 working tree의 PRD·계획·production/test 코드와 Phase별 리뷰 보고서 13개를 대조했다. 새 확정 발견 사항은 없고 기존 확정 항목은 모두 각 회귀 수정 Task에서 수정 완료 상태다. Phase 9 보고서 범위를 Task 9.7까지 확장해 production/test `FeedAdapter|FeedImageViews` 호출자 0건과 현재 Feed view/model 직접 재사용을 기록했다. `./gradlew --no-daemon :app:testDebugUnitTest ... :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS(`BUILD SUCCESSFUL`, 16 suites·120 tests, failures/errors/skipped 0, 46 actionable tasks), `./gradlew --no-daemon tasks --all`, staged/unstaged `git diff --check` PASS. `adb devices`에서 기기 `2cec640c34017ece` 연결은 확인했지만 테스트 계정·API 데이터 기반 팔로잉 화면 조작과 Figma 대조는 수행하지 않아 `REV-P5-001` / Task 5.3 보류를 유지한다. 이번 리뷰에서는 production/test/resource 코드를 수정하지 않았다.
|
- 2026-07-31 이번 Phase 1~13 리뷰 재검증: 현재 working tree의 PRD·계획·production/test 코드와 Phase별 리뷰 보고서 13개를 대조했다. 새 확정 발견 사항은 없고 기존 확정 항목은 모두 각 회귀 수정 Task에서 수정 완료 상태다. Phase 9 보고서 범위를 Task 9.7까지 확장해 production/test `FeedAdapter|FeedImageViews` 호출자 0건과 현재 Feed view/model 직접 재사용을 기록했다. `./gradlew --no-daemon :app:testDebugUnitTest ... :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS(`BUILD SUCCESSFUL`, 16 suites·120 tests, failures/errors/skipped 0, 46 actionable tasks), `./gradlew --no-daemon tasks --all`, staged/unstaged `git diff --check` PASS. `adb devices`에서 기기 `2cec640c34017ece` 연결은 확인했지만 테스트 계정·API 데이터 기반 팔로잉 화면 조작과 Figma 대조는 수행하지 않아 `REV-P5-001` / Task 5.3 보류를 유지한다. 이번 리뷰에서는 production/test/resource 코드를 수정하지 않았다.
|
||||||
- 2026-07-31 Phase 1~13 최종 리뷰 검증: 현재 working tree의 PRD·계획·production/test 코드와 Phase별 리뷰 보고서 13개를 다시 대조했다. 신규 확정 항목은 `REV-P4-004`(공용 source test의 영문 문장형 테스트명 11개)와 `REV-P9-002`(API가 제공하지 않는 creator ID 자리에 content/post ID를 합성)이며, 코드 수정 전에 Phase 4 Task 4.9 / `P4-R4`, Phase 9 Task 9.6 / `P9-R2`로 전환했다. 이번 리뷰에서는 production/test/resource 코드를 수정하지 않았다. `./gradlew --no-daemon :app:testDebugUnitTest ... :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS(`BUILD SUCCESSFUL`, 20 suites·306 tests, failures/errors/skipped 0, 46 actionable tasks), `./gradlew --no-daemon tasks --all`, staged/unstaged `git diff --check`도 PASS했다. `adb devices`는 정상 실행됐으나 연결 기기가 없어 `REV-P5-001` / Task 5.3 실기기 수동 확인 보류를 유지한다.
|
- 2026-07-31 Phase 1~13 최종 리뷰 검증: 현재 working tree의 PRD·계획·production/test 코드와 Phase별 리뷰 보고서 13개를 다시 대조했다. 신규 확정 항목은 `REV-P4-004`(공용 source test의 영문 문장형 테스트명 11개)와 `REV-P9-002`(API가 제공하지 않는 creator ID 자리에 content/post ID를 합성)이며, 코드 수정 전에 Phase 4 Task 4.9 / `P4-R4`, Phase 9 Task 9.6 / `P9-R2`로 전환했다. 이번 리뷰에서는 production/test/resource 코드를 수정하지 않았다. `./gradlew --no-daemon :app:testDebugUnitTest ... :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS(`BUILD SUCCESSFUL`, 20 suites·306 tests, failures/errors/skipped 0, 46 actionable tasks), `./gradlew --no-daemon tasks --all`, staged/unstaged `git diff --check`도 PASS했다. `adb devices`는 정상 실행됐으나 연결 기기가 없어 `REV-P5-001` / Task 5.3 실기기 수동 확인 보류를 유지한다.
|
||||||
- 2026-07-31 리뷰 보완 Task 2.8, 6.8 완료 검증: `REV-P2-002`에 따라 미사용 `HomeFollowingNewsUiItem.Content.labelResId`와 전용 `FollowingNewsType.toLabelResId()`를 제거하고, 실제 표시 경로인 `FeedContentCategory.Photo` label 회귀를 `HomeFollowingNewsAdapterTest`에 유지했다. `REV-P6-004`에 따라 creator·최근 대화 source test의 UI 표현 속성 문자열 assertion만 제거하고 구조·필수 field·Direct badge·상대 시간 formatter·click 계약 검증은 유지했다. focused test 3개, 팔로잉 전체 회귀, `./gradlew --no-daemon :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다. `adb devices`에는 연결 기기가 없어 Task 5.3 실기기 수동 확인 보류를 유지한다.
|
- 2026-07-31 리뷰 보완 Task 2.8, 6.8 완료 검증: `REV-P2-002`에 따라 미사용 `HomeFollowingNewsUiItem.Content.labelResId`와 전용 `FollowingNewsType.toLabelResId()`를 제거하고, 실제 표시 경로인 `FeedContentCategory.Photo` label 회귀를 `HomeFollowingNewsAdapterTest`에 유지했다. `REV-P6-004`에 따라 creator·최근 대화 source test의 UI 표현 속성 문자열 assertion만 제거하고 구조·필수 field·Direct badge·상대 시간 formatter·click 계약 검증은 유지했다. focused test 3개, 팔로잉 전체 회귀, `./gradlew --no-daemon :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다. `adb devices`에는 연결 기기가 없어 Task 5.3 실기기 수동 확인 보류를 유지한다.
|
||||||
|
|||||||
@@ -215,8 +215,9 @@ Figma `24:5682` 기준 상단 title bar와 tab bar는 기존 홈 화면 구조
|
|||||||
#### Requirements
|
#### Requirements
|
||||||
- profile image와 creator nickname을 표시한다.
|
- profile image와 creator nickname을 표시한다.
|
||||||
- item 터치 시 해당 크리에이터 채널로 이동한다.
|
- item 터치 시 해당 크리에이터 채널로 이동한다.
|
||||||
- `followingCreators`가 1명 이상이면 가로 RecyclerView의 마지막 item으로 `전체` 버튼을 항상 표시한다.
|
- `followingCreators`가 1명 이상이면 추천 탭 라이브 섹션의 `전체`와 동일하게 크리에이터 가로 RecyclerView 우측 끝에 고정 `TextView`로 `전체` 버튼을 항상 표시한다.
|
||||||
- `전체` 버튼 item은 `width=wrap_content`, creator item 전체 높이와 동일한 높이, `paddingHorizontal=16dp`, `textColor=@color/soda_400`로 표시한다.
|
- `전체` 버튼은 RecyclerView의 마지막 item으로 포함하지 않는다. 팔로잉 크리에이터 목록만 좌측 가로 스크롤 영역에 남고, `전체` 버튼은 스크롤되지 않아야 한다.
|
||||||
|
- `전체` 버튼은 추천 탭 라이브 섹션의 `tv_home_live_all` 구조처럼 section root를 horizontal container로 두고, `RecyclerView`는 `layout_width="0dp"`, `layout_weight="1"`, `전체` TextView는 우측 고정 폭과 end margin을 가진다.
|
||||||
- `전체` 버튼 터치 시 기존 전체 팔로잉 크리에이터 목록 화면으로 이동한다.
|
- `전체` 버튼 터치 시 기존 전체 팔로잉 크리에이터 목록 화면으로 이동한다.
|
||||||
- 리스트가 비어 있으면 섹션을 숨긴다.
|
- 리스트가 비어 있으면 섹션을 숨긴다.
|
||||||
- 이미지 URL이 비어 있거나 로드 실패하면 기존 홈 creator profile placeholder 정책을 따른다.
|
- 이미지 URL이 비어 있거나 로드 실패하면 기존 홈 creator profile placeholder 정책을 따른다.
|
||||||
@@ -306,6 +307,15 @@ Figma `24:5682` 기준 상단 title bar와 tab bar는 기존 홈 화면 구조
|
|||||||
- 기존 `COMMUNITY_POST` 상세 이동은 유지하고, `CONTENT_RANKING`과 `PHOTO_CONTENT`의 터치 동작은 이번 범위에서 추가하지 않는다.
|
- 기존 `COMMUNITY_POST` 상세 이동은 유지하고, `CONTENT_RANKING`과 `PHOTO_CONTENT`의 터치 동작은 이번 범위에서 추가하지 않는다.
|
||||||
- API/DTO/mapper, 최근 소식 표시 UI와 다른 팔로잉 섹션의 동작은 변경하지 않는다.
|
- API/DTO/mapper, 최근 소식 표시 UI와 다른 팔로잉 섹션의 동작은 변경하지 않는다.
|
||||||
|
|
||||||
|
#### 2026-08-18 팔로잉 크리에이터 전체 버튼 고정 Requirements
|
||||||
|
- 팔로잉 탭 최상단 팔로잉 크리에이터 섹션의 `전체` 버튼은 추천 탭 라이브 섹션과 동일하게 RecyclerView 밖 우측 끝에 고정한다.
|
||||||
|
- 기존 RecyclerView 마지막 item 방식의 `전체` 노출은 더 이상 사용하지 않는다.
|
||||||
|
- 팔로잉 크리에이터 프로필 item만 수평 스크롤되며, `전체` 버튼은 사용자가 리스트를 좌우로 스크롤해도 화면 우측에 남아 있어야 한다.
|
||||||
|
- `followingCreators`가 비어 있으면 기존처럼 팔로잉 크리에이터 섹션 전체를 숨기며, 고정 `전체` 버튼도 보이지 않아야 한다.
|
||||||
|
- `전체` 버튼 클릭 동작은 기존 `FollowingCreatorActivity` 진입 동작을 유지한다.
|
||||||
|
- 추천 탭과 동일한 구조 기준은 `fragment_v2_main_home.xml`의 `ll_home_live_section`, `rv_home_lives`, `tv_home_live_all` 배치 방식이다.
|
||||||
|
- 이번 요구사항은 팔로잉 크리에이터 섹션의 배치 방식만 변경하며 API/DTO/mapper, 다른 팔로잉 섹션, 최근 소식 라우팅, empty/login/error 정책은 변경하지 않는다.
|
||||||
|
|
||||||
#### 2026-06-30 후속 수정 Requirements
|
#### 2026-06-30 후속 수정 Requirements
|
||||||
- `CREATOR_RANKING` 최근 소식 본문은 순위 숫자만 단독 표시하지 않고, Figma `24:5717` 기준으로 크리에이터 이름과 순위를 포함한 문장형 메시지를 표시한다.
|
- `CREATOR_RANKING` 최근 소식 본문은 순위 숫자만 단독 표시하지 않고, Figma `24:5717` 기준으로 크리에이터 이름과 순위를 포함한 문장형 메시지를 표시한다.
|
||||||
- ranking 문장 안의 순위 텍스트는 기존 `FeedRankTextStyler` highlight range로 강조한다.
|
- ranking 문장 안의 순위 텍스트는 기존 `FeedRankTextStyler` highlight range로 강조한다.
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Phase 14 팔로잉 크리에이터 전체 버튼 우측 고정 리뷰
|
||||||
|
|
||||||
|
## 1. 리뷰 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 리뷰 대상 | Phase 14 / Task 14.1~14.4 |
|
||||||
|
| 기준 commit 또는 working tree | `388304dbaae06eeb7204fe8d0c697d58ce721f00` 기준 working tree |
|
||||||
|
| 리뷰 일자 | 2026-08-18 |
|
||||||
|
| 리뷰어 | Oracle, Sisyphus |
|
||||||
|
| 기준 문서 | `docs/20260625_메인_홈_팔로잉_탭/prd.md`, `docs/20260625_메인_홈_팔로잉_탭/plan-task.md` Phase 14 |
|
||||||
|
| 리뷰 상태 | 판정 완료 |
|
||||||
|
|
||||||
|
## 2. 리뷰 목적과 범위
|
||||||
|
|
||||||
|
### 목적
|
||||||
|
|
||||||
|
- 팔로잉 크리에이터 `전체` 버튼이 RecyclerView item에서 우측 고정 sibling TextView로 변경됐는지 확인한다.
|
||||||
|
- 빈 목록 visibility와 기존 로그인 가드·전체 목록 이동에 회귀가 없는지 확인한다.
|
||||||
|
- Phase 14 체크박스와 실제 코드·테스트·검증 기록이 일치하는지 확인한다.
|
||||||
|
|
||||||
|
### 포함 범위
|
||||||
|
|
||||||
|
- 코드: `HomeMainFragment.kt`, `HomeFollowingCreatorAdapter.kt`, `fragment_v2_main_home.xml`, 삭제된 `item_home_following_creator_all.xml`.
|
||||||
|
- 테스트: `HomeFollowingFragmentSourceTest.kt`와 `v2.main.home.*Following*` local unit/source test.
|
||||||
|
- 문서: PRD의 2026-08-18 요구사항과 `plan-task.md` Phase 14.
|
||||||
|
- 수동 검증: 사용자 요청에 따라 visual QA와 기기 조작을 제외하고 debug APK 확인 절차만 인계한다.
|
||||||
|
|
||||||
|
### 제외 범위
|
||||||
|
|
||||||
|
- 다른 홈 섹션 UI, API/DTO/mapper, androidTest, 스크린샷·시각 비교.
|
||||||
|
|
||||||
|
## 3. 검토한 근거
|
||||||
|
|
||||||
|
- `fragment_v2_main_home.xml:274`: RecyclerView와 `tv_home_following_creator_all` sibling 구조.
|
||||||
|
- `HomeFollowingCreatorAdapter.kt:12`: creator-only adapter와 `items.size` item count.
|
||||||
|
- `HomeMainFragment.kt:265`: 고정 TextView 클릭 연결.
|
||||||
|
- `HomeMainFragment.kt:547`: 빈 creator 목록의 부모 section visibility 처리.
|
||||||
|
- `HomeMainFragment.kt:724`: 기존 로그인 가드와 `FollowingCreatorActivity` 이동.
|
||||||
|
- `HomeFollowingFragmentSourceTest.kt:318`: 구조·adapter·route 회귀 테스트.
|
||||||
|
|
||||||
|
## 4. 실행한 검증
|
||||||
|
|
||||||
|
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|
||||||
|
|---|---|---|
|
||||||
|
| focused test RED | 성공 | 17개 중 신규 시나리오 1개가 고정 TextView 부재로 실패 |
|
||||||
|
| focused test GREEN `--rerun-tasks` | 성공 | `BUILD SUCCESSFUL`, 39개 task 실제 실행 |
|
||||||
|
| 팔로잉 회귀 + resource + compile + ktlint | 성공 | 첫 실행의 신규 빈 줄 위반을 제거한 뒤 `BUILD SUCCESSFUL` |
|
||||||
|
| `git diff --check` | 성공 | 출력 없음 |
|
||||||
|
| `./gradlew --no-daemon :app:assembleDebug` | 성공 | `app/build/outputs/apk/debug/app-debug.apk` 생성 |
|
||||||
|
| visual QA·기기 조작 | 제외 | 사용자 명시 요청에 따라 미실행 |
|
||||||
|
| 사용자 수동 확인 | 성공 | debug APK에서 팔로잉 크리에이터 목록 스크롤 중 `전체` 우측 고정과 전체 팔로잉 목록 이동 확인 |
|
||||||
|
|
||||||
|
## 5. 발견 사항 요약
|
||||||
|
|
||||||
|
확정 발견 사항 없음. Oracle 리뷰에서 Critical/Important finding 없이 승인했다.
|
||||||
|
|
||||||
|
## 6. plan·goal 전환
|
||||||
|
|
||||||
|
전환 항목 없음.
|
||||||
|
|
||||||
|
## 7. 리뷰 종료 판정
|
||||||
|
|
||||||
|
| 판정 항목 | 결과 | 근거 |
|
||||||
|
|---|---|---|
|
||||||
|
| 리뷰 범위 전체 확인 | 충족 | Phase 14 코드·테스트·문서 대조 완료 |
|
||||||
|
| 후보 항목 판정 완료 | 충족 | blocking finding 없음 |
|
||||||
|
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
|
||||||
|
| 보류 항목 기록 | 해당 없음 | visual QA는 사용자 요청에 따른 제외, 사용자 수동 확인 완료 |
|
||||||
|
| 검증 명령과 결과 기록 | 충족 | Phase 14 검증 기록과 Verification Log 갱신 |
|
||||||
|
|
||||||
|
**최종 결론:** 확정 발견 사항 없음
|
||||||
|
|
||||||
|
**남은 항목:** 없음
|
||||||
Reference in New Issue
Block a user