Files
sodalive-android/docs/20260625_메인_홈_팔로잉_탭/plan-task.md

41 KiB

메인 홈 팔로잉 탭 구현 계획/TASK

For agentic workers: REQUIRED SUB-SKILL: 구현 시 superpowers:subagent-driven-development 또는 superpowers:executing-plans를 사용해 task 단위로 진행한다. 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시 - [x]로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.

Goal: GET /api/v2/home/following 응답을 기반으로 메인 홈 팔로잉 탭에 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식을 표시한다.

Architecture: 기존 HomeMainFragment의 title bar, TextTabBarView, 추천/랭킹 탭 구조는 유지하고, 팔로잉 선택 시 전용 content surface를 노출한다. 신규 API/Repository/DTO/UI state/mapper/ViewModel/adapter는 kr.co.vividnext.sodalive.v2.main.home 하위에 두며, ChatRoomListItemResponse, CreatorActivityType, formatUtcRelativeTimeText, 기존 feed/live/profile widget을 우선 재사용한다. 로그인 유도 화면은 아직 디자인/문구가 정해지지 않았으므로 이번 구현 계획에서는 isLoginRequired 상태 분기와 팔로잉 섹션 숨김까지만 고정한다.

Tech Stack: Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.


전제와 성공 기준

  • PRD: docs/20260625_메인_홈_팔로잉_탭/prd.md
  • Figma: home_003 팔로잉 탭 24:5682
  • API endpoint는 GET /api/v2/home/following이다.
  • Authorization header는 optional이며, token이 blank이면 header를 보내지 않는다.
  • query parameter는 보내지 않는다.
  • isLoginRequired = true이면 팔로잉 섹션을 표시하지 않는다.
  • 로그인 유도 화면의 실제 UI, 문구, CTA, 로그인 완료 후 복귀 정책은 별도 확정 후 구현한다.
  • recentNews 시간은 visibleFromAtUtc를 디바이스 타임존 기준으로 상대 시간 표시한다.
  • PHOTO_CONTENT label은 우선 화보로 표시한다.
  • ranking news의 rank가 null이면 해당 news item은 표시하지 않는다.
  • monthlySchedules는 서버가 이번 달 범위로 정렬해서 내려주며 앱은 재정렬하지 않는다.
  • 섹션 title chevron은 터치 콜백까지만 연결하고 실제 이동 목적지는 만들지 않는다.
  • 구현 완료 후 최소 다음 명령을 실행한다.
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"
    • ./gradlew :app:mergeDebugResources
    • ./gradlew :app:compileDebugKotlin
    • ./gradlew :app:ktlintCheck
    • git diff --check

Figma 참조 필요 Phase

  • Phase 1: 제한 참조
    • 기존 홈 탭 구조, v2 위젯, DI/API 패턴 확인 중심으로 진행한다.
  • Phase 2: Figma 참조 불필요
    • API/DTO/Repository와 mapper 상태는 PRD 서버 계약과 기존 v2 data layer 패턴을 따른다.
  • Phase 3: Figma 참조 불필요
    • ViewModel 상태, optional auth header, isLoginRequired 분기는 단위 테스트 중심으로 검증한다.
  • Phase 4: 필수 참조
    • 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식 섹션 배치와 spacing은 Figma 24:5682를 기준으로 확인한다.
  • Phase 5: 필수 참조
    • 최종 수동 화면 검증은 PRD의 포함/제외 항목과 실제 화면을 대조한다.

파일 구조

  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingApi.kt
    • GET /api/v2/home/following Retrofit endpoint를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt
    • HomeFollowingTabResponse, FollowingCreatorResponse, FollowingLiveResponse, FollowingScheduleResponse, FollowingNewsResponse, FollowingNewsType DTO를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingRepository.kt
    • API 호출을 repository method로 감싼다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiState.kt
    • Loading, LoginRequired, Content, Empty, Error 상태를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiModels.kt
    • 팔로잉 크리에이터, live, chat, schedule, news section/item UI model을 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt
    • DTO를 UI model/state로 변환하고, rank == null news 숨김, PHOTO_CONTENT label, visibleFromAtUtc 시간 기준을 적용한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingAuthHeader.kt
    • blank token이면 null, 값이 있으면 Bearer {token}을 반환한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModel.kt
    • 팔로잉 탭 API 호출, loading/error/login-required/content 상태를 관리한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • HomeFollowingApi, HomeFollowingRepository, HomeFollowingViewModel을 Koin에 등록한다.
  • Modify: app/src/main/res/layout/fragment_v2_main_home.xml
    • 팔로잉 탭 content surface와 섹션 RecyclerView들을 추가한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • HOME_TAB_FOLLOWING 분기, ViewModel observer, adapter binding, section visibility, chevron click callback을 연결한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingCreatorAdapter.kt
    • followingCreators horizontal profile list를 바인딩한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingLiveAdapter.kt
    • onAirLives horizontal live card list를 바인딩한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingChatAdapter.kt
    • recentChats horizontal chat card list를 바인딩한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingScheduleAdapter.kt
    • monthlySchedules vertical schedule list를 바인딩한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingNewsAdapter.kt
    • recentNews vertical news list를 바인딩한다.
  • Create: app/src/main/res/layout/item_home_following_creator.xml
    • 팔로잉 크리에이터 profile item이다.
  • Create: app/src/main/res/layout/item_home_following_live.xml
    • On Air live item이다.
  • Create: app/src/main/res/layout/item_home_following_chat.xml
    • 최근 대화 compact card item이다.
  • Create: app/src/main/res/layout/item_home_following_schedule.xml
    • 이달의 스케줄 item이다.
  • Create: app/src/main/res/layout/item_home_following_news_rank.xml
    • ranking news item이다.
  • Create: app/src/main/res/layout/item_home_following_news_content.xml
    • audio/photo content news item이다.
  • Modify: app/src/main/res/values/strings.xml
    • 팔로잉 섹션 title, On Air, 화보, empty/error label을 추가한다.
  • Modify: app/src/main/res/values-en/strings.xml
    • 팔로잉 섹션 title, On Air, photo label, empty/error label을 추가한다.
  • Modify: app/src/main/res/values-ja/strings.xml
    • 팔로잉 섹션 title, On Air, photo label, empty/error label을 추가한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingAuthHeaderTest.kt
    • optional auth header 생성 규칙을 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingMapperTest.kt
    • DTO to UI mapping, login-required, rank null filtering, news label/time 기준을 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModelTest.kt
    • API success/error/login-required/loading 상태 전환을 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt
    • layout id, adapter/ViewModel 연결, HOME_TAB_FOLLOWING 분기, chevron click callback 연결을 source-level로 검증한다.

Phase 1: 기존 구조 확인과 작업 경계 고정

  • Task 1.1: 홈 탭 삽입 지점 확인

    • 확인:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/res/layout/fragment_v2_main_home.xml
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeRecommendationViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeCreatorRankingViewModel.kt
    • 작업:
      • 기존 추천, 랭킹, 팔로잉 Text Tab bar는 유지한다.
      • showHomeTab(HOME_TAB_FOLLOWING) 분기에서 팔로잉 surface를 표시할 위치를 확인한다.
      • 추천/랭킹 API와 ViewModel은 리팩터링하지 않는다.
    • 검증:
      • Run: rg -n "HOME_TAB_FOLLOWING|showHomeTab|nsvHomeRecommendationContent|rvHomeCreatorRankings|textTabBarHome" app/src/main/java/kr/co/vividnext/sodalive/v2/main/home app/src/main/res/layout/fragment_v2_main_home.xml
      • Expected: 팔로잉 탭 분기와 기존 추천/랭킹 surface visibility 제어 지점이 확인된다.
      • Result: PASS. HomeMainFragment.kt에서 showHomeTab, HOME_TAB_FOLLOWING, 추천/랭킹 visibility 제어 지점을 확인했다.
  • Task 1.2: 재사용 위젯과 신규 adapter 경계 확정

    • 확인:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCreatorProfileImageLoader.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/widget/livethumbnail/LiveThumbnailDetailView.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/model/ChatRoomMappers.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/common/CreatorActivityType.kt
    • 작업:
      • 프로필 이미지 로딩은 HomeCreatorProfileImageLoader 또는 같은 placeholder 정책 재사용으로 고정한다.
      • 채팅 데이터 변환은 ChatRoomListItemResponse.toUiItem() 재사용으로 고정한다.
      • 스케줄 type label은 CreatorActivityType.labelResId 재사용으로 고정한다.
      • Figma와 크기가 맞지 않는 카드들은 팔로잉 전용 adapter/layout 신규 생성으로 고정한다.
    • 검증:
      • Run: rg -n "HomeCreatorProfileImageLoader|class LiveThumbnailDetailView|sealed class FeedItem|fun ChatRoomListItemResponse.toUiItem|enum class CreatorActivityType" app/src/main/java/kr/co/vividnext/sodalive/v2
      • Expected: 재사용 후보 클래스와 함수가 확인된다.
      • Result: PASS. LiveThumbnailDetailView, FeedItem, ChatRoomListItemResponse.toUiItem(), CreatorActivityType 재사용 후보를 확인했다.
  • Task 1.3: 제외 범위 확인

    • 확인:
      • docs/20260625_메인_홈_팔로잉_탭/prd.md
    • 제외:
      • 로그인 유도 화면 실제 디자인/문구/CTA 구현
      • 더보기 chevron 목적지 이동
      • 팔로잉/언팔로잉 액션
      • 스케줄 월 필터와 앱 내 재정렬
      • 레거시 홈 화면 직접 수정
    • 검증:
      • Run: rg -n "Non-Goals|로그인 유도|더보기|monthlySchedules|rank|PHOTO_CONTENT|Open Questions" docs/20260625_메인_홈_팔로잉_탭/prd.md
      • Expected: 제외 범위와 확정 정책이 확인된다.
      • Result: PASS. 로그인 유도 UI 미확정, 더보기 목적지 제외, monthlySchedules 정렬 정책, rank == null 제외, PHOTO_CONTENT label 정책을 확인했다.

Phase 2: API, DTO, Repository, mapper 추가

  • Task 2.1: optional auth header 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingAuthHeaderTest.kt
    • 테스트 케이스:
      • blank token은 null을 반환한다.
      • non-blank token은 Bearer {token}을 반환한다.
      • 앞뒤 공백이 있는 token은 trim 후 Bearer {token}을 반환한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingAuthHeaderTest"
      • Expected: helper 구현 전 RED 실패.
      • Result: RED 확인. homeFollowingAuthHeader 미구현으로 compileDebugUnitTestKotlin unresolved reference 실패가 발생했다.
  • Task 2.2: optional auth header helper 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingAuthHeader.kt
    • 작업:
      • fun homeFollowingAuthHeader(token: String): String?를 추가한다.
      • token.trim().takeIf { it.isNotEmpty() }?.let { "Bearer $it" } 규칙을 적용한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingAuthHeaderTest"
      • Expected: PASS.
      • Result: PASS. blank/whitespace token은 null, non-blank token은 trim 후 Bearer {token}으로 검증됐다.
  • Task 2.3: API/DTO/Repository 계약 추가

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingApi.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingRepository.kt
    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • Retrofit endpoint는 @GET("/api/v2/home/following")로 정의한다.
      • @Header("Authorization") authHeader: String?를 사용한다.
      • query parameter는 정의하지 않는다.
      • DTO는 PRD의 Android Response Contract 필드를 모두 포함하고 @Keep, @SerializedName을 사용한다.
      • recentChats는 기존 ChatRoomListItemResponse를 사용한다.
      • FollowingScheduleResponse.type은 기존 CreatorActivityType을 사용한다.
      • Koin networkModule, repositoryModule에 신규 API/Repository를 등록한다.
    • 검증:
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: 신규 data layer와 DI 등록이 컴파일된다.
      • Result: PASS. HomeFollowingApi, DTO, Repository, API/Repository DI 등록이 compileDebugKotlin에서 컴파일됐다.
  • Task 2.4: string resource 추가

    • 수정:
      • app/src/main/res/values/strings.xml
      • app/src/main/res/values-en/strings.xml
      • app/src/main/res/values-ja/strings.xml
    • 작업:
      • 섹션 title 문자열을 추가한다.
        • screen_home_following_creators_title
        • screen_home_following_on_air_title
        • screen_home_following_recent_chats_title
        • screen_home_following_monthly_schedules_title
        • screen_home_following_recent_news_title
      • news/category 문자열을 추가한다.
        • screen_home_following_on_air
        • screen_home_following_photo_content
      • empty/error 문자열을 추가한다.
        • screen_home_following_empty
        • screen_home_following_error
    • 검증:
      • Run: ./gradlew :app:mergeDebugResources
      • Expected: 3개 locale string resource가 중복 없이 merge된다.
      • Result: PASS. values, values-en, values-ja string resource merge가 통과했다.
  • Task 2.5: mapper RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingMapperTest.kt
    • 테스트 케이스:
      • isLoginRequired = true 응답은 HomeFollowingUiState.LoginRequired로 매핑된다.
      • isLoginRequired = false이고 모든 섹션이 비면 HomeFollowingUiState.Empty로 매핑된다.
      • followingCreators, onAirLives, recentChats, monthlySchedules, recentNews가 section UI model로 매핑된다.
      • recentChatsChatRoomListItemResponse.toUiItem() 결과가 null인 항목을 제외한다.
      • monthlySchedules는 서버 응답 순서를 유지한다.
      • PHOTO_CONTENTscreen_home_following_photo_content label로 매핑된다.
      • CREATOR_RANKING, CONTENT_RANKINGrank == null 항목은 제외된다.
      • news 상대 시간은 visibleFromAtUtc 값을 formatter에 전달한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest"
      • Expected: UI model/mapper 구현 전 RED 실패.
      • Result: RED 확인. DTO/UI model/mapper/string resource 미구현으로 compileDebugUnitTestKotlin unresolved reference 실패가 발생했다.
  • Task 2.6: UI state/model과 mapper 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiState.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt
    • 작업:
      • HomeFollowingUiStateLoading, LoginRequired, Content, Empty, Error를 정의한다.
      • Content에는 followingCreators, onAirLives, recentChats, monthlySchedules, recentNews section을 둔다.
      • Content.isEmpty helper를 추가해 모든 section item이 비었는지 판정한다.
      • mapper는 UtcRelativeTimeTextFormatter를 받아 visibleFromAtUtc 상대 시간을 생성한다.
      • ranking news의 rank == null은 map 단계에서 제외한다.
      • PHOTO_CONTENT는 photo label string resource id를 매핑한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest"
      • Expected: PASS.
      • Result: PASS. login-required, empty, content mapping, invalid chat 제외, schedule 순서 유지, PHOTO_CONTENT label, null rank filtering, visibleFromAtUtc formatter 전달이 검증됐다.

Phase 3: ViewModel 상태와 API 호출 연결

  • Task 3.1: ViewModel RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModelTest.kt
    • 테스트 케이스:
      • loadFollowing()은 loading 후 content 상태를 발행한다.
      • blank token이면 repository에 null auth header를 전달한다.
      • token이 있으면 repository에 Bearer {token} auth header를 전달한다.
      • isLoginRequired = true 응답은 login-required 상태를 발행한다.
      • API success이지만 data가 null이면 error 상태와 toast를 발행한다.
      • API failure throwable이면 error 상태와 toast를 발행한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingViewModelTest"
      • Expected: ViewModel 구현 전 RED 실패.
      • Result: RED 확인. HomeFollowingViewModel 미구현으로 compileDebugUnitTestKotlin unresolved reference 실패가 발생했다.
  • Task 3.2: HomeFollowingViewModel 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModel.kt
    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • HomeFollowingViewModel(repository, relativeTimeTextFormatter)를 추가한다.
      • SharedPreferenceManager.tokenhomeFollowingAuthHeader()로 변환해 repository에 전달한다.
      • RxJava3 subscribeOn(Schedulers.io()), observeOn(AndroidSchedulers.mainThread()) 패턴을 따른다.
      • success data는 mapper로 UI state 변환한다.
      • error는 HomeFollowingUiState.Error와 기존 unknown error toast 패턴을 따른다.
      • Koin viewModelModuleHomeFollowingViewModel(get(), get())를 등록한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingViewModelTest"
      • Expected: PASS.
      • Result: PASS. loading/content, optional auth header, login-required, null data error/toast, throwable error/toast 상태 전환이 검증됐다.
  • Task 3.3: data/model/ViewModel 통합 컴파일 확인

    • 확인:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingApi.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 검증:
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: 팔로잉 data/model/ViewModel/DI 코드가 컴파일된다.
      • Result: PASS. 팔로잉 data/model/ViewModel/DI 코드가 compileDebugKotlin에서 컴파일됐다.
    • 검증 기록:
      • 2026-06-25 코드 리뷰: Phase 1~3 범위의 API/DTO/Repository/mapper/ViewModel/DI/string/test 변경을 검토했으며, 현재 코드 기준으로 blocking finding은 발견하지 못했다.
      • 2026-06-25 검증: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS.

Phase 4: 팔로잉 탭 UI surface와 adapter 연결

  • Task 4.1: Fragment source RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt
    • 테스트 케이스:
      • fragment_v2_main_home.xmlnsv_home_following_content가 있다.
      • layout에 rv_home_following_creators, rv_home_following_on_air_lives, rv_home_following_recent_chats, rv_home_following_monthly_schedules, rv_home_following_recent_news가 있다.
      • HomeMainFragmentHomeFollowingViewModel을 주입한다.
      • HOME_TAB_FOLLOWING 분기에서 팔로잉 surface를 visible 처리한다.
      • section title chevron click listener가 연결되어 있고 실제 route 호출은 없다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"
      • Expected: layout/fragment 수정 전 RED 실패.
      • Result: RED 확인. layout ID와 HomeMainFragment 팔로잉 wiring 미구현 상태에서 6개 source test가 모두 실패했다.
  • Task 4.2: 팔로잉 content layout 추가

    • 수정:
      • app/src/main/res/layout/fragment_v2_main_home.xml
    • 생성:
      • app/src/main/res/layout/item_home_following_creator.xml
      • app/src/main/res/layout/item_home_following_live.xml
      • app/src/main/res/layout/item_home_following_chat.xml
      • app/src/main/res/layout/item_home_following_schedule.xml
      • app/src/main/res/layout/item_home_following_news_rank.xml
      • app/src/main/res/layout/item_home_following_news_content.xml
    • 작업:
      • nsv_home_following_contenttext_tab_bar_home 아래에 추가하고 기본 visibility="gone"으로 둔다.
      • 섹션 순서는 followingCreators, On Air, recentChats, monthlySchedules, recentNews로 둔다.
      • 각 섹션 title은 view_section_title을 include한다.
      • 로그인 유도 화면 전용 layout은 이번 phase에서 추가하지 않는다.
    • 검증:
      • Run: ./gradlew :app:mergeDebugResources
      • Expected: 신규 layout/resource가 merge된다.
      • Result: PASS. nsv_home_following_content, 5개 섹션 RecyclerView, 6개 item layout이 resource merge를 통과했다.
  • Task 4.3: 팔로잉 adapter 구현

    • 생성:
      • 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/HomeFollowingLiveAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingChatAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingScheduleAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingNewsAdapter.kt
    • 작업:
      • 각 adapter는 submitItems()와 item click callback을 제공한다.
      • profile image는 기존 loadHomeCreatorProfileImage() 또는 동일 placeholder 정책을 사용한다.
      • recent chat은 ChatRoomListUiItem을 바인딩한다.
      • schedule은 CreatorActivityType.labelResId, isOnAir, scheduledAtUtc 표시 모델을 사용한다.
      • news adapter는 rank item과 content item view type을 분리한다.
    • 검증:
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: 신규 adapter가 컴파일된다.
      • Result: PASS. 팔로잉 creator/live/chat/schedule/news adapter 5개가 compileDebugKotlin에서 컴파일됐다.
  • Task 4.4: HomeMainFragment에 팔로잉 탭 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • 작업:
      • private val homeFollowingViewModel: HomeFollowingViewModel by viewModel()을 추가한다.
      • 팔로잉 adapter 5개를 초기화한다.
      • showHomeTab(HOME_TAB_FOLLOWING)에서 추천/랭킹 surface를 숨기고 팔로잉 surface를 표시한다.
      • 팔로잉 탭 최초 선택 시 homeFollowingViewModel.loadFollowing()을 1회 호출한다.
      • LoginRequired 상태에서는 팔로잉 섹션 content를 숨긴다.
      • Content 상태에서는 각 section item이 비어 있으면 해당 섹션을 숨긴다.
      • section title chevron은 onFollowingSectionMoreClick(section) callback까지만 연결하고 route는 호출하지 않는다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"
      • Expected: PASS.
      • Result: PASS. 팔로잉 ViewModel 주입, adapter 연결, 탭 surface 전환, 최초 1회 load, login-required/empty/error 섹션 숨김, content 섹션 binding이 source test로 검증됐다.
  • Task 4.5: UI routing skeleton 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • 작업:
      • creator item click은 기존 openCreatorProfile(creatorId)를 재사용한다.
      • recent chat item click은 기존 v2 chat/DM 진입 flow를 확인해 재사용 가능한 메서드로 연결한다.
      • live, schedule, news item click은 type/targetId별 route 함수로 분리한다.
      • 목적지가 확정되지 않은 더보기 chevron은 route 없이 callback만 받는다.
    • 검증:
      • Run: rg -n "openFollowing|onFollowing|HomeFollowingSection|openCreatorProfile|HOME_TAB_FOLLOWING" app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • Expected: 팔로잉 item click과 more click 콜백 함수가 확인된다.
      • Result: PASS. creator click은 openCreatorProfile, recent chat click은 기존 AI/DM chat 진입 flow로 연결했고 live/schedule/news/more click은 route 없는 callback skeleton으로 유지했다.
    • 검증 기록:
      • 2026-06-25 Phase 4 코드 리뷰: Figma 24:5682와 PRD 기준으로 On Air 시작 시간, 이달의 스케줄 프로필/타입 label/On Air 상태, 최근 소식 label/title 바인딩 누락을 확인했다. 누락 항목은 HomeFollowingFragmentSourceTest RED로 고정한 뒤 adapter/layout 최소 수정으로 보완했다.
      • 2026-06-25 Phase 4 재코드 리뷰: 현재 워킹트리 기준 HomeMainFragment, 팔로잉 adapter 5개, 팔로잉 layout, HomeFollowingFragmentSourceTest를 Figma 24:5682/PRD와 대조했으며 blocking finding은 발견하지 못했다.

Phase 5: 통합 검증과 문서 기록

  • Task 5.1: 팔로잉 관련 단위 테스트 실행

    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"
      • Expected: 팔로잉 관련 local unit/source test가 모두 PASS.
    • 검증 기록:
      • 2026-06-25 Phase 4 리뷰 보완 후 실행 결과 PASS. 팔로잉 관련 local unit/source test가 모두 통과했다.
  • Task 5.2: 리소스/컴파일/린트 검증

    • 검증:
      • Run: ./gradlew :app:mergeDebugResources
      • Expected: 신규 layout/string resource merge PASS.
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: Kotlin compile PASS.
      • Run: ./gradlew :app:ktlintCheck
      • Expected: ktlint PASS.
      • Run: git diff --check
      • Expected: whitespace error 없음.
    • 검증 기록:
      • 2026-06-25 Phase 4 리뷰 보완 후 ./gradlew :app:mergeDebugResources PASS. 최초 sandbox lock 권한 오류 후 승인 실행으로 통과했다.
      • 2026-06-25 Phase 4 리뷰 보완 후 ./gradlew :app:compileDebugKotlin PASS.
      • 2026-06-25 Phase 4 리뷰 보완 후 ./gradlew :app:ktlintCheck PASS. 기존 .editorconfig disabled_rules deprecation warning만 출력됐다.
      • 2026-06-25 Phase 4 리뷰 보완 후 git diff --check PASS.
  • Task 5.3: Figma 기준 수동 확인

    • 확인:
      • Figma 24:5682
      • app/src/main/res/layout/fragment_v2_main_home.xml
    • 수동 확인 항목:
      • 팔로잉 탭 선택 시 추천/랭킹 content가 겹쳐 보이지 않는다.
      • 섹션 순서가 팔로잉 크리에이터On Air최근 대화이달의 스케줄최근 소식이다.
      • title bar와 tab bar는 고정되고 팔로잉 content만 세로 스크롤된다.
      • empty section은 숨겨진다.
      • isLoginRequired 상태에서 팔로잉 섹션 content는 표시되지 않는다.
      • 더보기 chevron 터치 시 앱이 크래시하지 않고 화면 이동은 발생하지 않는다.
    • 검증 기록:
      • 2026-06-25 Figma 24:5682 디자인 컨텍스트와 스크린샷 기준 정적 대조를 수행했다. 실제 기기/에뮬레이터에서의 수동 화면 확인은 아직 실행하지 않았다.
      • 2026-06-25 Phase 5 진행: Figma 24:5682 스크린샷과 fragment_v2_main_home.xml, HomeMainFragment.kt를 대조했다. 팔로잉 탭 전용 nsv_home_following_content가 title bar/tab bar 아래 별도 scroll surface로 배치되어 있고, 팔로잉 선택 시 추천/랭킹 surface를 숨기는 분기, 섹션 순서, empty/login-required 섹션 숨김, 더보기 chevron no-op callback 연결을 정적으로 확인했다.
      • 2026-06-25 Phase 5 진행: 실제 기기 검증을 위해 adb devices에서 2cec640c34017ece 연결을 확인한 뒤 ./gradlew :app:installDebug를 실행했으나, 설치 중 디바이스 연결이 해제되어 device '2cec640c34017ece' not found로 실패했다. 재확인 시 adb devices에 연결된 디바이스가 없어 실제 화면 수동 확인은 blocked 상태로 남긴다.

Phase 6: Figma 디자인 재대조 후속 수정

  • Task 6.1: 디자인 불일치 RED 테스트 추가

    • 수정:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt
    • 작업:
      • 팔로잉 크리에이터 섹션에 header include가 없는지 검증한다.
      • 최근 대화 RecyclerView가 horizontal인지 검증한다.
      • 최근 대화 item이 Figma box 카드 폭/프로필/Direct badge/상대시간 바인딩을 갖는지 검증한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"
      • Expected: 구현 전 RED 실패.
      • Result: RED 확인. 팔로잉 크리에이터 header 제거, 최근 대화 horizontal box list, 최근 대화 Figma box field 검증 3개가 현재 구현과 맞지 않아 실패했다.
  • Task 6.2: 팔로잉 크리에이터와 최근 대화 UI 수정

    • 수정:
      • app/src/main/res/layout/fragment_v2_main_home.xml
      • app/src/main/res/layout/item_home_following_creator.xml
      • app/src/main/res/layout/item_home_following_chat.xml
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingChatAdapter.kt
    • 작업:
      • 팔로잉 크리에이터 section header를 제거한다.
      • 팔로잉 크리에이터 profile item을 Figma의 simple profile 크기에 맞춘다.
      • 최근 대화 RecyclerView를 horizontal로 변경한다.
      • 최근 대화 item을 Figma의 box card 형태로 조정하고 Direct badge와 상대시간 표시를 유지한다.
      • 최근 대화 시간은 기존 formatChatRoomLastMessageTime()을 유지해 server ISO 시간을 디바이스 timezone 기준 상대시간/날짜로 표시한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"
      • Expected: PASS.
      • Result: PASS. 팔로잉 크리에이터 header 제거, 75dp simple profile item, 최근 대화 horizontal box list, Direct badge/상대시간 바인딩이 source test로 검증됐다.
  • Task 6.3: 후속 변경 통합 검증

    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"
      • Expected: 팔로잉 관련 local unit/source test가 모두 PASS.
      • Run: ./gradlew :app:mergeDebugResources
      • Expected: layout/resource merge PASS.
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: Kotlin compile PASS.
      • Run: ./gradlew :app:ktlintCheck
      • Expected: ktlint PASS.
      • Run: git diff --check
      • Expected: whitespace error 없음.
    • 검증 기록:
      • 2026-06-26 후속 변경 검증: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*" 최초 병렬 실행 중 HomeFollowingViewModelTest.blank token이면 repository에 null auth header를 전달한다 1건이 실패했으나, 동일 테스트 단독 재실행과 전체 팔로잉 테스트 단독 재실행은 모두 PASS했다. 실패는 SharedPreferenceManager 전역 상태를 쓰는 테스트의 병렬 Gradle 실행 간섭으로 판단했다.
      • 2026-06-26 후속 변경 검증: ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐다.
      • 2026-06-26 실제 표면 검증: adb devices에서 2cec640c34017ece 연결을 확인했고, ./gradlew :app:installDebugkr.co.vividnext.sodalive.debug 설치 PASS. adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1로 런처 실행 PASS. 팔로잉 탭 내부 API 데이터 기반 화면 대조는 자동 조작/테스트 계정 상태가 없어 정적 source test와 설치/실행 검증으로 대체했다.
      • 2026-06-26 리뷰 지적 수정: 최근 대화 adapter가 XML 284dp 폭을 런타임 MATCH_PARENT로 덮는 문제를 확인했다. HomeFollowingFragmentSourceTest.following recent chat item matches figma box fields에 RED 검증을 추가했고, HomeFollowingChatAdapterrecyclerItemLayoutParams(parent) 사용으로 변경해 XML 폭을 유지했다. 해당 테스트 재실행 PASS.
      • 2026-06-26 테스트 안정화: SharedPreferenceManager.resetForTest()가 DataStore 저장값을 지우지 않아 HomeFollowingViewModelTest 묶음 실행 시 token이 이전 값으로 복원될 수 있음을 확인했다. 테스트 setUp()의 시작 token을 빈 값으로 명시해 테스트 격리를 보강했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*" 재실행 PASS.
      • 2026-06-26 후속 요청 반영: 최근 대화 item의 대화 내용 표시를 한 줄로 제한하고 ellipsize="end"를 유지했다. HomeFollowingFragmentSourceTest.following recent chat item matches figma box fieldsmaxLines="1"/ellipsis 검증을 추가해 RED 확인 후 GREEN 전환했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest.following recent chat item matches figma box fields", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, git diff --check 모두 PASS.
  • Task 6.4: 최근 소식 Feed 위젯 재사용과 chevron 후속 수정

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingNewsAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt
    • 작업:
      • 최근 소식 section title의 chevron을 표시하지 않는다.
      • COMMUNITY_POST 최근 소식은 기존 v2.widget.feed.FeedCommunityViewview_feed_community.xml을 재사용한다.
      • ranking 최근 소식은 기존 v2.widget.feed.FeedRankViewview_feed_rank.xml을 재사용한다.
      • 오디오/화보 content 최근 소식은 기존 v2.widget.feed.FeedContentViewview_feed_content.xml을 재사용한다.
      • 최근 소식 API에 없는 댓글 수, 좋아요 수, 잠금/오너 액션 값은 임의 생성하지 않고 현재 모델 범위에서 0 또는 빈 값으로 둔다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"
      • Expected: PASS.
    • 검증 기록:
      • 2026-06-26 최근 소식 후속 수정: 먼저 HomeFollowingFragmentSourceTest에 최근 소식 chevron 제거와 Feed 위젯 재사용 검증을 추가해 RED를 확인했다. 이후 HomeMainFragment에서 최근 소식 showMore = true와 chevron listener를 제거했고, HomeFollowingNewsAdapterCOMMUNITY_POSTFeedCommunityView, ranking은 FeedRankView, audio/photo content는 FeedContentView를 inflate/bind하도록 변경했다.
      • 2026-06-26 최근 소식 후속 검증: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐다.

Verification Log

  • 2026-06-25 Phase 1-3 구현 검증: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS.
  • 2026-06-25 Phase 1-3 코드 리뷰 및 재검증: blocking finding 없음. ./gradlew :app:mergeDebugResources는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, 나머지 검증 명령도 PASS.
  • 2026-06-25 Phase 4 코드 리뷰 및 검증: Figma 24:5682 기준 UI 필드 바인딩 누락을 보완했고, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS.
  • 2026-06-25 Phase 4 재코드 리뷰 및 검증: blocking finding 없음. Figma 24:5682 디자인 컨텍스트/스크린샷과 현재 Phase 4 변경을 정적 대조했고, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS. mergeDebugResources는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했다.
  • 2026-06-25 Phase 5 진행 검증: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS. ./gradlew :app:installDebug는 실행 중 Android 디바이스 연결 해제로 실패했고, 재확인 시 연결된 디바이스가 없어 실제 화면 수동 확인은 blocked 상태다.
  • 2026-06-26 Phase 6 후속 디자인 수정 검증: Figma 24:5682 기준 팔로잉 크리에이터 header 제거, 최근 대화 horizontal box list, Direct badge/상대시간 표시를 RED 테스트로 고정한 뒤 GREEN 확인했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check, ./gradlew :app:installDebug, adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1 모두 PASS.
  • 2026-06-26 Phase 6 리뷰 지적 후 재검증: 최근 대화 item 폭 override를 제거하고 source test를 보강했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest.following recent chat item matches figma box fields", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check, ./gradlew :app:installDebug 모두 PASS. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐다.
  • 2026-06-26 최근 대화 한 줄 제한 후속 검증: item_home_following_chat.xmltv_home_following_chat_messagemaxLines="1"로 변경하고 기존 ellipsize="end"를 유지했다. RED/GREEN source test, ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, git diff --check 모두 PASS.
  • 2026-06-26 최근 소식 Feed 위젯 재사용 후속 검증: 최근 소식 chevron 제거와 FeedCommunityView/FeedRankView/FeedContentView 재사용을 source test로 고정했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check 모두 PASS. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐다.