From 9f70bcd3eb320bc8aae919b3058786e420004107 Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 14 Jul 2026 12:22:58 +0900 Subject: [PATCH] =?UTF-8?q?docs(v2):=20=EA=B3=B5=ED=86=B5=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EA=B0=80=EB=93=9C=20=EA=B3=84=ED=9A=8D=EC=9D=84=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 365 ++++++++++++++++++ .../prd.md | 246 ++++++++++++ 2 files changed, 611 insertions(+) create mode 100644 docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md create mode 100644 docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md diff --git a/docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md b/docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md new file mode 100644 index 00000000..1dc761b5 --- /dev/null +++ b/docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md @@ -0,0 +1,365 @@ +# V2 공통 접근 가드와 도메인 액션 Plan/Task + +## Assumptions +- 작업 범위는 `kr.co.vividnext.sodalive.v2` 하위 신규/기존 코드와 이를 조립하는 `AppDI.kt`로 제한한다. +- 화면별 최초 집계 API와 Response는 feature가 계속 소유할 수 있다. +- 공통화 대상은 최소 두 호출부에서 반복되거나 모든 화면에서 동일해야 하는 정책과 사용자 행동이다. +- 기존 UX, API, Intent extra, Activity result, 화면 새로고침 동작을 보존한다. +- 직접적인 하위 Activity 결과는 `ActivityResult -> 명시적 도메인 결과 -> feature 단일 handler`를 기본 전달 방식으로 사용한다. +- 현재 화면 안에서 Action/Repository가 직접 반환하는 결과에는 불필요하게 `ActivityResult`를 도입하지 않고 같은 handler를 호출한다. +- `SharedFlow`/observer는 Activity 밖의 독립 소비자가 다수 존재하거나 백그라운드 변경 전파 요구가 확인될 때만 사용한다. +- 레거시 기능은 수정하지 않고 `v2` adapter/wrapper에서 호출한다. +- 구현은 Access부터 도메인별 Phase로 진행하며 각 Phase를 독립적으로 검증한다. +- 모든 도메인에 동일한 형식의 클래스를 강제하지 않고 실제 중복과 정책이 확인된 최소 단위만 추출한다. + +## Success Criteria +- `v2` 로그인/성인 접근 판단의 단일 소유자가 존재한다. +- Activity와 Fragment가 동일한 Access 공개 진입점을 사용한다. +- 합의된 도메인별 반복 동작이 단일 Action 공개 진입점을 사용한다. +- Action 입력은 화면 전용 UI model/DTO가 아닌 ID, 값 또는 명시적 command다. +- 화면별 최초 query와 UI composition은 유지된다. +- Community mutation은 명시적 변경 결과를 반환하고 Creator Channel의 단일 composition handler가 Home/Community projection 갱신을 결정한다. +- Activity result를 사용하는 도메인 흐름은 raw result를 화면마다 해석하지 않고 명시적 결과와 단일 handler를 사용한다. +- feature 간 직접 Activity/Coordinator 의존은 공통 Action 또는 navigation 계약으로 대체된다. +- 각 Phase의 테스트, 컴파일, ktlint, whitespace 검증 결과가 문서에 누적된다. + +### Phase 0: 기준선과 Action 카탈로그 확정 +- [x] **Task 0.1: `v2` 구조와 중복 접근 구현 기준선 조사** + - 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/` + - 확인 내용: + - 운영 Kotlin 258개, 약 25,294줄 + - 테스트 Kotlin 127개, 약 23,809줄 + - `SharedPreferenceManager` 직접 참조 29개 파일, 92곳 + - API 11개, Repository 11개, ViewModel 22개, Activity/Fragment 20개 + - 검증 기준: 전체 재배치가 아닌 반복 동작별 점진 전환이 필요한 근거를 기록한다. + - 검증 기록: + - 2026-07-14: `find`, `wc`, `rg`로 `v2` 운영/테스트 코드 규모와 직접 의존을 확인했다. `widget` 64개는 UI 재사용 컴포넌트이므로 초기 도메인 Action 전환 대상에서 제외한다. + +- [x] **Task 0.2: 초기 Action 카탈로그 작성** + - 파일: + - `docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md` + - `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md` + - 작업: Access, Content, Live, Creator, Community, Chat의 초기 공통 동작과 호출 화면을 기록한다. + - 검증 기준: 화면별 집계 query와 공통 action을 구분하고, FanTalk/Donation/Schedule은 실제 재사용 수요 확인 전 추출 대상에서 제외한다. + - 검증 기록: + - 2026-07-14: 기존 호출부 조사 결과 Access, 오디오/시리즈 진입, Live 상세/입장, Creator Channel 진입, Community 상세, Chat/DM 진입을 초기 후보로 확정했다. + +- [ ] **Task 0.3: 구현 직전 호출부 및 동작 차이 상세 목록 확정** + - 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/` + - `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md` + - 작업: 각 Action별 호출 함수, 접근 조건, Intent extra, Activity result, callback/refresh 차이를 기록한다. + - 검증 기준: + - 공통 계약으로 보존할 동작과 화면에 남길 후처리가 구분된다. + - Activity result 흐름은 adapter에서 만들 명시적 결과와 feature 단일 handler 후보가 기록된다. + - observer가 필요한 흐름은 Activity 밖의 소비자 수, lifecycle, replay 요구사항이 함께 기록된다. + +### Phase 1: V2 공통 Access 정책과 실행기 +- [ ] **Task 1.1: Access 판단 단위 테스트 작성** + - 생성 예정 파일: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/access/AccessPolicyTest.kt` + - 검증 대상: + - 로그인 필요 요구사항의 로그인/미로그인 결과 + - 성인 콘텐츠 요구사항의 미로그인 결과 + - 국내 미인증 사용자의 본인인증 필요 결과 + - 국외 사용자의 본인인증 생략 + - 성인 콘텐츠 표시 설정 비활성 결과 + - 모든 조건 충족 시 허용 결과 + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.AccessPolicyTest" + ``` + - 기대 결과: 구현 전 RED, 구현 후 GREEN. + +- [ ] **Task 1.2: Access 입력·출력 계약과 순수 정책 구현** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessRequirement.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessDecision.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessState.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessPolicy.kt` + - 작업: 로그인, 국가/본인인증, 성인 콘텐츠 표시 설정을 명시적 입력과 결과로 판단한다. + - 검증 기준: Android, Activity, Fragment, Dialog, Retrofit 의존 없이 Task 1.1 테스트가 통과한다. + +- [ ] **Task 1.3: Activity/Fragment 공용 Access 실행기 구현** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt` + - 수정 가능 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - 작업: + - `AccessDecision`을 로그인, 본인인증, 콘텐츠 설정 UX로 연결한다. + - 기존 `LoginActivity`, `V2ModalDialog`, `Auth.auth`, `MyPageViewModel.authVerify`, `ContentSettingsActivity`를 재사용한다. + - Activity와 Fragment에서 같은 공개 함수를 호출할 수 있게 한다. + - 검증 기준: + - `Allowed`에서만 원래 action을 한 번 실행한다. + - 거부 결과에서 원래 action을 실행하지 않는다. + - 기존 Intent flag/extra와 인증 완료 후 흐름을 유지한다. + +- [ ] **Task 1.4: 기존 Access 구현을 공통 실행기로 전환** + - 수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt` + - 작업: + - 기존 호출부를 `V2AccessGuard`로 전환한다. + - 화면 진입 guard와 클릭 action guard의 기존 종료/후처리 차이를 보존한다. + - 모든 호출부 전환 후 중복 `ensureLoginAndAdultAuth` 구현을 제거한다. + - 검증 기준: + - `CreatorChannelActivity`, `HomeOnAirLiveActivity`에 별도 로그인/성인 판단 함수가 남지 않는다. + - 대상 클릭 handler에 직접 token 조건문이 남지 않는다. + +- [ ] **Task 1.5: 기존 Access source test를 새 계약에 맞게 전환** + - 수정 예정 파일: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentLoginGuardSourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` + - 작업: 이전 함수명 고정 assertion을 새 공통 Access 사용 및 기존 UX 보존 검증으로 최소 수정한다. + - 검증 기준: 정책은 `AccessPolicyTest`, 호출 연결은 필요한 source test가 담당한다. + +- [ ] **Task 1.6: Access Phase 검증 및 문서 반영** + - 파일: + - `docs/agent-guides/build-test-style.md` + - `docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md` + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + - 기대 결과: 신규 Access 테스트와 영향받은 기존 테스트, 컴파일, ktlint, whitespace 검증 통과. + +### Phase 2: Content 공통 Action +- [ ] **Task 2.1: 오디오/시리즈 진입 호출부와 계약 확정** + - 확인 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: ID, 성인 여부, Activity result, route source, 기존 extra 차이를 목록화하고 Action 계약을 확정한다. + - 검증 기준: + - 화면 전용 UI model을 받지 않는 최소 command/result 계약이 정해진다. + - Activity result 또는 호출 화면 후처리가 있는 경로는 Content result adapter와 단일 handler 필요 여부가 결정된다. + +- [ ] **Task 2.2: Content Action 순수 계약 테스트 및 구현** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentAction.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionCommand.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionTest.kt` + - 검증 대상: invalid ID, audio/series route, 성인 Access 요구사항 전달, 허용/차단 결과. + - 검증 기준: feature UI model과 DTO 의존 없이 입력·출력이 테스트된다. + +- [ ] **Task 2.3: Content navigation adapter 구현 및 전체 호출부 전환** + - 생성/수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionHandler.kt` + - Task 2.1에서 확정한 Home/Content/Overview/Creator Channel 호출 파일 + - 작업: 기존 extra와 Access UX를 보존하면서 오디오/시리즈 상세 진입을 한 곳으로 통합한다. + - 검증 기준: 대상 UI에서 상세 Activity Intent 직접 생성이 제거되고 Content Action을 사용한다. + +- [ ] **Task 2.4: Content Phase 회귀 검증** + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.content.action.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + - 수동 확인: Home, Content, Content Overview, Creator Channel에서 audio/series 상세 진입과 성인 차단 UX가 기존과 동일하다. + +### Phase 3: Live 공통 Action +- [ ] **Task 3.1: 기존 Live 흐름 차이와 레거시 의존 목록 확정** + - 확인 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt` + - 작업: 상세, 입장, 예약, 결제, 비밀번호, 오디오 중단, Activity result, refresh callback, navigation 차이를 기록한다. + - 검증 기준: + - Live 공통 정책과 화면별 후처리가 구분된다. + - 라이브 생성 결과를 포함한 raw Activity result의 Live 결과 변환과 feature 단일 handler가 확정된다. + +- [ ] **Task 3.2: Live 입장 결정 계약과 단위 테스트 작성** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryDecision.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryPolicy.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryPolicyTest.kt` + - 검증 대상: 관리자, 무료/결제 완료, 비밀번호, 유료, 상세 표시 분기. + - 검증 기준: Activity/Dialog 없이 기존 분기 순서를 순수 입력·출력으로 검증한다. + +- [ ] **Task 3.3: Live Action/Coordinator 단일 구현과 호환 adapter 구성** + - 생성/이동/수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActions.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt` + - 작업: + - 기존 Coordinator 동작을 Live 소유 진입점으로 옮기고 필요한 동안 기존 이름은 얇은 adapter로 유지한다. + - 하위 Activity가 반환하는 Live 결과는 명시적 result로 변환하고 호출 feature의 단일 handler로 전달한다. + - 검증 기준: 새 구현이 Creator Channel feature를 import하지 않고 레거시 Live 기능만 adapter로 호출한다. + +- [ ] **Task 3.4: Home/Creator Channel/On-air 호출부 전환과 중복 제거** + - 수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt` + - 검증 기준: 세 화면이 동일 Live Action을 사용하고 On-air의 중복 입장 조건이 제거된다. + +- [ ] **Task 3.5: Live Phase 회귀 검증** + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + - 수동 확인: 무료, 유료, 비밀번호, 관리자, 상세 표시, 예약/취소 흐름과 화면별 refresh 동작이 기존과 동일하다. + +### Phase 4: Creator와 Community 공통 Action +- [ ] **Task 4.1: Creator Channel 진입 호출부와 계약 확정** + - 확인 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/` + - 검증 기준: `creatorId`, 로그인 요구 여부, route source, 기존 extra 차이가 정리된다. + +- [ ] **Task 4.2: Creator Action 구현과 호출부 전환** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/action/CreatorActions.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/action/CreatorActionsTest.kt` + - 검증 기준: 유효 ID와 Access를 한 곳에서 처리하고 feature UI model을 입력받지 않는다. + +- [ ] **Task 4.3: Community 게시글 진입 계약 구현과 호출부 전환** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActions.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActionsTest.kt` + - 수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 검증 기준: 로그인, ID 검사, Activity result 차이를 보존하고 공통 게시글 진입점을 사용한다. + +- [ ] **Task 4.4: Community 변경 결과와 레거시 ActivityResult adapter 구현** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChange.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActivityResultMapper.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt` + - 작업: + - 작성, 수정, 삭제, 고정 변경을 `Created`, `Updated`, `Deleted`, `PinChanged`로 표현한다. + - 레거시 작성/수정 Activity의 `RESULT_OK`를 명시적인 Community 변경 결과로 변환한다. + - 결과 계약에는 Home/Community 탭 refresh callback 또는 Fragment 참조를 포함하지 않는다. + - 검증 기준: result code, 유효/누락 post ID, 변경 종류가 화면 의존 없이 테스트된다. + +- [ ] **Task 4.5: Creator Channel Community 변경 composition handler 통합** + - 수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: + - `handleCommunityChange(change)` 단일 함수에서 Home/Community projection 갱신을 조합한다. + - Community 작성, 수정, 삭제, 고정 변경 성공 경로가 위 함수를 사용하도록 전환한다. + - 기존 `ActivityResultLauncher`, Activity result 수신 시점, Fragment initial load 동작은 유지한다. + - 검증 기준: + - Community Action/결과가 Creator Channel Home 또는 Community Fragment를 알지 않는다. + - Activity 내부의 개별 성공 경로에 `refreshHome()`과 `refreshCreatorChannelCommunity()` 조합이 반복되지 않는다. + - 현재 Activity 내부 전파를 위해 전역 EventBus/observer singleton을 추가하지 않는다. + +- [ ] **Task 4.6: Creator/Community Phase 회귀 검증** + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.community.action.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + +### Phase 5: Chat 공통 Action +- [ ] **Task 5.1: Chat/DM 진입 유형과 결과 계약 확정** + - 확인 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: 기존 room 진입, creator 기반 DM 생성, AI/DM 타입, owner/non-owner, Activity result 차이를 구분한다. + - 검증 기준: + - 서로 다른 동작을 하나의 nullable/boolean 다목적 함수로 합치지 않는다. + - 호출 화면 후처리가 필요한 Activity result는 명시적 Chat 결과와 feature 단일 handler로 연결한다. + +- [ ] **Task 5.2: Chat Action 계약과 단위 테스트 작성** + - 생성 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActions.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionCommand.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionsTest.kt` + - 검증 기준: room/creator 식별자와 진입 유형이 명시적이고 Home/Creator UI model에 의존하지 않는다. + +- [ ] **Task 5.3: Home/Chat/Creator Channel 호출부 전환** + - 수정 예정 파일: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 검증 기준: 기존 login guard, room 생성 API, 성공 후 이동, owner/non-owner 분기가 유지된다. + +- [ ] **Task 5.4: Chat Phase 회귀 검증** + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.chat.action.*" + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + +### Phase 6: 의존 방향과 이전 구현 정리 +- [ ] **Task 6.1: feature 간 직접 navigation/Coordinator 의존 재검사** + - 확인 범위: `app/src/main/java/kr/co/vividnext/sodalive/v2/**/*.kt` + - 실행 명령: + ```bash + rg -n '^import kr\.co\.vividnext\.sodalive\.v2\.(main|creator|live)\..*(Activity|Fragment|Coordinator)' app/src/main/java/kr/co/vividnext/sodalive/v2 --glob '*.kt' + ``` + - 검증 기준: composition에 필요한 부모-자식 Fragment 연결을 제외하고 도메인 Action으로 대체할 직접 의존이 남지 않는다. + +- [ ] **Task 6.2: 이전 helper와 중복 함수 제거** + - 삭제/수정 대상: 각 Phase에서 호환을 위해 남긴 adapter, 이전 `ensure...`, 중복 `open...`, 직접 route helper. + - 검증 기준: 모든 호출부가 새 공개 진입점을 사용하는 것이 확인된 구현만 제거한다. + +- [ ] **Task 6.3: 미추출 도메인 재평가** + - 확인 대상: FanTalk, Donation, Schedule, Ranking, Discovery. + - 작업: 실제 반복 호출과 동일 정책이 생겼는지 확인하고 추출 또는 현행 유지 근거를 기록한다. + - 검증 기준: 형식적 Action 추가 없이 단일 사용 기능은 기존 feature에 유지한다. + +- [ ] **Task 6.4: 결과 전달 방식과 observer 필요성 재검사** + - 확인 대상: Content, Live, Community, Chat 및 후속 mutation 흐름. + - 작업: + - 하위 Activity 결과가 `ActivityResult -> 명시적 결과 -> feature 단일 handler`로 연결되는지 확인한다. + - 동일한 raw result 해석과 refresh 조합이 여러 호출부에 남아 있는지 확인한다. + - `SharedFlow`/observer 도입 지점이 있다면 Activity 밖의 독립 소비자, lifecycle, replay, 중복 처리 근거를 기록한다. + - 검증 기준: 직접 결과 전달로 충분한 흐름에 전역 EventBus 또는 singleton observer가 추가되지 않는다. + +- [ ] **Task 6.5: 통합 회귀 검증** + - 실행 명령: + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*" + ./gradlew :app:compileDebugKotlin + ./gradlew :app:ktlintCheck + git diff --check + ``` + - 수동 확인: + - 로그인/본인인증/성인 설정 차단과 허용 흐름 + - Home, Content, Creator Channel, On-air의 오디오/시리즈/라이브 진입 + - Creator Channel 및 Community 상세 진입 + - Chat/DM 생성과 기존 room 진입 + - 기존 Activity result와 새로고침 동작 + - 기대 결과: `v2` 전체 관련 테스트와 컴파일, ktlint, whitespace 검증 통과. + +## Verification Log +- 2026-07-14: 사용자 요청에 따라 이번 단계에서는 PRD와 구현 계획 문서만 작성했다. 운영 코드, 테스트 코드, DI는 아직 변경하지 않았고 Gradle 검증은 실행하지 않았다. +- 2026-07-14: 문서 범위는 Access부터 도메인별로 순차 전환하는 하나의 연속 작업으로 정의했다. 화면별 최초 집계 API는 유지하고 반복 action만 단일 소유 진입점으로 통합한다. +- 2026-07-14: 문서 작성 후 `./gradlew tasks --all`을 실행해 계획에 사용하는 `:app:testDebugUnitTest`, `:app:compileDebugKotlin`, `:app:ktlintCheck` 작업이 존재함을 확인했으며 명령은 `BUILD SUCCESSFUL`로 종료되었다. `git diff --check`도 whitespace 오류 없이 통과했다. +- 2026-07-14: Community 업로드 후 Home/Community 탭 갱신 설계를 구체화했다. 초기 구현은 전역 observer 대신 `CommunityChange` 결과와 Creator Channel의 단일 `handleCommunityChange` composition handler를 사용하며, 레거시 `ActivityResult`는 `v2` adapter에서 변경 결과로 변환한다. +- 2026-07-14: 사용자 확인에 따라 Activity result가 필요한 모든 도메인 Action에 공통 결과 전달 원칙을 확장했다. 기본 방식은 `ActivityResult -> 명시적 도메인 결과 -> feature 단일 handler`이며, Action/Repository 직접 결과도 동일 handler로 합류한다. `SharedFlow`/observer는 Activity 밖의 독립 소비자와 lifecycle/replay 요구가 확인될 때만 도입한다. diff --git a/docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md b/docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md new file mode 100644 index 00000000..c23abe59 --- /dev/null +++ b/docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md @@ -0,0 +1,246 @@ +# PRD: V2 공통 접근 가드와 도메인 액션 + +## 1. Overview +`v2` 화면에 분산된 로그인·성인 콘텐츠 접근 검사와 반복 사용자 동작을 단일 소유 함수로 통합하고, 각 화면은 화면별 조회 결과를 표시한 뒤 공통 기능을 조합해 사용하도록 정리한다. + +--- + +## 2. Problem +현재 `v2`는 동일한 접근 조건과 화면 이동 동작을 여러 화면에서 각각 구현하고 있다. + +- 로그인, 국내 사용자 본인인증, 성인 콘텐츠 표시 설정 검사가 `MainV2LoginGuard`, `CreatorChannelActivity`, `HomeOnAirLiveActivity`에 중복되어 있다. +- 일부 화면은 `SharedPreferenceManager.token`을 직접 확인하고, 일부 화면은 별도 `ensure...` 함수를 사용해 동일 정책의 변경 지점이 분산되어 있다. +- 오디오 콘텐츠, 시리즈, 크리에이터 채널, 커뮤니티 게시글, 채팅방 등의 진입에서 ID 검사, 접근 검사, `Intent` 생성이 화면마다 반복된다. +- Home이 `CreatorChannelLiveCoordinator`를 직접 사용하는 등 공통 기능이 실제 소유 도메인이 아닌 최초 구현 화면 패키지에 위치한다. +- 동일 기능의 새 진입 화면이 추가되면 기존 구현을 재사용하기보다 조건과 이동 코드를 다시 작성할 가능성이 있다. +- 호출부가 접근 가드 적용을 기억해야 하므로 진입 경로에 따라 보호 정책이 누락될 수 있다. + +--- + +## 3. Goals +- 로그인 및 성인 콘텐츠 접근 판단을 `v2` 공통 Access 구현 한 곳에서 소유한다. +- Activity와 Fragment가 동일한 Access 진입점을 사용한다. +- 기존 로그인 화면, 본인인증, 성인 콘텐츠 설정 이동 동작과 back stack/extra 계약을 유지한다. +- 둘 이상의 `v2` 화면에서 반복되는 사용자 동작은 해당 도메인의 단일 Action 진입점으로 통합한다. +- 도메인 Action은 화면 전용 UI model 대신 안정적인 ID, 값 객체 또는 명시적인 command를 입력받는다. +- 도메인 Action의 결과가 호출 화면에 따라 달리 표현되어야 하면 명시적인 result 계약으로 반환한다. +- Home, Content, Creator Channel, On-air 등 feature는 화면별 최초 집계 API와 UI 구성을 유지하면서 공통 Action을 조합한다. +- 새 화면은 기존 도메인 Action을 호출해 동일 정책과 동작을 재구현하지 않고 사용할 수 있어야 한다. +- 각 Phase는 기존 동작을 보존하고 독립적으로 테스트·컴파일 가능한 상태로 완료한다. +- 하위 Activity가 반환하는 도메인 변경은 `ActivityResult`를 명시적 결과로 변환한 뒤 feature의 단일 handler에서 후처리한다. + +--- + +## 4. Non-Goals +- `v2` 전체 파일을 한 번에 이동하거나 일괄 패키지 재구성하지 않는다. +- Gradle 멀티 모듈 전환은 포함하지 않는다. +- 서버 API, 공개 DTO 스키마, 데이터베이스 계약은 변경하지 않는다. +- Home 추천, Creator Channel 홈과 같은 화면별 집계 API를 여러 개의 도메인 API 호출로 강제 분해하지 않는다. +- 한 번만 사용되고 별도 정책이 없는 화면 전용 동작에 형식적인 Action, UseCase, Repository 인터페이스를 추가하지 않는다. +- UI 레이아웃, 문구, 디자인, 화면 전환 UX를 변경하지 않는다. +- 본인인증 SDK, 로그인 API, 콘텐츠 구매, 라이브 결제 등 레거시 기능 자체를 수정하지 않는다. +- 레거시 파일을 공통화를 위해 직접 수정하지 않는다. 필요한 기능은 `v2` wrapper/adapter에서 호출한다. +- FanTalk, Donation, Schedule 등 현재 재사용 수요가 확인되지 않은 기능을 추측으로 공통화하지 않는다. +- Creator Channel 내부의 Community 변경 전파를 위해 전역 EventBus 또는 application singleton observer를 도입하지 않는다. +- 직접적인 요청자와 결과 처리자가 명확한 흐름을 일괄적으로 `SharedFlow` 또는 observer 기반으로 변경하지 않는다. + +--- + +## 5. Target Users +- Home, Content, Creator Channel, On-air, Chat 등 `v2` 화면을 이용하는 사용자 +- 동일 기능을 새로운 `v2` 화면에 연결해야 하는 Android 개발자 +- 로그인·성인 접근·콘텐츠 진입 정책을 변경하거나 검증하는 유지보수 담당자 + +--- + +## 6. User Stories +- 비로그인 사용자는 어느 `v2` 화면에서 보호 기능을 선택하더라도 동일한 로그인 안내를 받고 싶다. +- 성인 콘텐츠 접근 조건을 충족하지 않은 사용자는 진입 화면과 무관하게 동일한 인증 또는 설정 안내를 받고 싶다. +- 로그인 및 접근 조건을 충족한 사용자는 기존과 동일하게 콘텐츠, 라이브, 커뮤니티, 채팅 기능으로 이동하고 싶다. +- 개발자는 라이브 입장, 오디오 콘텐츠 열기, 커뮤니티 게시글 열기와 같은 동작을 한 번 구현한 뒤 여러 화면에서 호출하고 싶다. +- 개발자는 공통 정책 변경 시 모든 Activity와 Fragment를 검색해 개별 조건문을 수정하지 않고 싶다. +- 개발자는 화면별 집계 API와 UI 구성을 유지하면서 도메인별 공통 동작만 조합하고 싶다. + +--- + +## 7. Core Features + +### Feature A: V2 공통 Access 계약 +로그인과 성인 콘텐츠 접근 조건을 화면과 분리된 공통 계약으로 정의한다. + +#### Requirements +- Access 요구사항은 최소 `Login`, `AdultContent`를 명시적으로 표현한다. +- `AdultContent`는 로그인 확인 후 국가/본인인증, 성인 콘텐츠 표시 설정 순서로 평가한다. +- 로그인 여부는 기존과 동일하게 `SharedPreferenceManager.token.isBlank()` 기준을 사용한다. +- `countryCode.ifBlank { "KR" } == "KR"`이면 국내 사용자로 판단한다. +- 국내 사용자이고 `SharedPreferenceManager.isAuth == false`이면 본인인증 필요 결과를 반환한다. +- `SharedPreferenceManager.isAdultContentVisible == false`이면 성인 콘텐츠 설정 필요 결과를 반환한다. +- Access 판단 결과는 최소 `Allowed`, `LoginRequired`, `AdultVerificationRequired`, `AdultContentSettingRequired`를 구분한다. +- 정책 판단 로직은 Activity, Fragment, Dialog, `Intent`에 의존하지 않는 단위 테스트 가능한 형태로 작성한다. +- 세션 값 접근은 주입 가능한 snapshot/provider 경계를 두어 테스트에서 전역 상태에 직접 의존하지 않게 한다. + +#### Edge Cases +- blank 국가 코드는 기존 정책과 동일하게 `KR`로 처리한다. +- 국외 사용자는 국내 본인인증 조건을 건너뛰고 성인 콘텐츠 표시 설정을 확인한다. +- 로그인만 필요한 동작은 성인 인증과 콘텐츠 표시 설정을 검사하지 않는다. +- invalid ID 또는 지원하지 않는 route는 기존처럼 Access 실행 전에 종료할 수 있다. + +### Feature B: V2 공통 Access 실행기 +Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연결하는 재사용 진입점을 제공한다. + +#### Requirements +- Activity와 Fragment가 동일한 `V2AccessGuard` 공개 진입점을 사용할 수 있어야 한다. +- 허용된 경우에만 전달된 동작을 정확히 한 번 실행한다. +- 미로그인인 경우 기존 `LoginActivity` 진입 계약과 호출 화면의 extra/back stack 동작을 유지한다. +- 본인인증이 필요한 경우 기존 `V2ModalDialog`, `Auth.auth`, `MyPageViewModel.authVerify` 흐름을 재사용한다. +- 성인 콘텐츠 표시 설정이 필요한 경우 `ContentSettingsActivity`에 `Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE = true`를 전달한다. +- `MainV2LoginGuard`, `CreatorChannelActivity.ensureLoginAndAdultAuth`, `HomeOnAirLiveActivity.ensureLoginAndAdultAuth`의 중복 판단 구현을 제거한다. +- `v2` UI의 직접 로그인 검사 중 화면 진입 자체를 막는 검사와 개별 행동을 막는 검사를 구분해 기존 종료 여부를 유지한다. + +#### Edge Cases +- 인증 callback이 Activity 종료 이후 전달되는 경우 안전하게 UI 후처리를 생략한다. +- 로그인 화면 이동 시 호출 Activity가 보존하던 기존 `intent.extras` 전달 계약을 유지한다. +- 화면 진입 guard와 클릭 행동 guard가 같은 Access 정책을 사용하더라도 화면 종료 여부는 호출 feature의 기존 UX를 유지한다. + +### Feature C: 도메인 Action 카탈로그와 단일 소유권 +반복 동작의 호출부, 현재 차이, 소유 도메인, 입력·출력 계약을 구현 전에 확정한다. + +#### Requirements +- 각 Action 후보는 최소 두 개의 실제 호출부가 있거나 여러 화면에서 반드시 동일해야 하는 정책을 포함해야 한다. +- Action별로 소유 도메인, 호출 화면, 입력, 결과, UI side effect, 레거시 의존성을 기록한다. +- 화면 전용 집계 query와 도메인 공통 action을 구분한다. +- Action의 공개 입력에 `HomeRecommendation...UiModel`, `CreatorChannel...Response` 같은 feature 전용 타입을 사용하지 않는다. +- 동일한 이름이더라도 정책이 다른 동작은 Access와 도메인 정책으로 나누고 하나의 범용 함수에 합치지 않는다. + +#### Initial Action Catalog +| 소유자 | 단일 동작 후보 | 주요 호출 화면 | 공통 계약 방향 | +|---|---|---|---| +| Access | 로그인/성인 콘텐츠 접근 확인 | Home, Content, Creator Channel, On-air, Chat | `AccessRequirement` -> `AccessDecision` | +| Content | 오디오 콘텐츠 상세 진입 | Home, Content, Content Overview, Creator Channel | `contentId`, 접근 정보 -> 진입/차단 결과 | +| Content | 시리즈 상세 진입 | Content, Creator Channel | `seriesId`, 접근 정보 -> 진입/차단 결과 | +| Live | 라이브 상세/입장 | Home, Creator Channel, On-air | `liveId` -> 상세/입장/비밀번호/결제/차단 결과 | +| Creator | 크리에이터 채널 진입 | Home, Content, Main, AI 캐릭터 route | `creatorId` -> 진입/무시 결과 | +| Community | 커뮤니티 게시글 진입 | Home, Creator Channel | `postId` -> 진입/차단 결과 | +| Community | 게시글 작성/수정/삭제/고정 변경 결과 | Creator Channel Home/Community projection | mutation -> `CommunityChange` | +| Chat | DM/채팅방 진입 | Home, Chat, Creator Channel | room/creator 식별자 -> 생성/진입/차단 결과 | + +#### Result Propagation Policy +- 하위 Activity의 완료 결과로 도메인 변경을 전달하는 기존 흐름은 `ActivityResultLauncher` 생명주기 계약을 유지한다. +- raw `RESULT_OK`, nullable extra, 화면별 boolean은 `v2` adapter에서 명시적인 도메인 Action 결과 또는 change 타입으로 변환한다. +- feature는 변환된 결과를 `handleContentChange`, `handleLiveChange`, `handleCommunityChange`, `handleChatChange`와 같은 의미 있는 단일 handler에 전달한다. +- 단일 handler는 현재 feature가 조합하는 projection 갱신, Activity result 재전달, navigation, 화면별 후처리를 결정한다. +- 도메인 Action은 `refreshHome`, `refreshTab`, Fragment 참조처럼 호출 화면의 구성을 나타내는 callback을 입력으로 받지 않는다. +- Repository 또는 Action이 현재 화면 안에서 직접 반환하는 비동기 결과는 불필요하게 `ActivityResult`로 변환하지 않고 같은 단일 handler에 전달한다. +- 발행자와 처리자가 직접 연결되고 갱신 대상이 한 feature 내부에 있으면 명시적인 결과 전달을 기본값으로 사용한다. +- Activity 밖의 서로 독립적인 소비자가 여러 개 생기거나 백그라운드 변경을 함께 관찰해야 할 때만 Activity 범위 `SharedFlow`, application event 또는 Repository Flow를 검토한다. +- 전역 EventBus/singleton observer는 이벤트 범위, creator/content 식별자, replay, 중복 처리, lifecycle 요구사항이 먼저 확정되지 않으면 도입하지 않는다. + +### Feature D: Content 공통 Action +오디오 콘텐츠와 시리즈 상세 진입의 공통 사전 조건과 navigation 계약을 Content가 한 번만 구현한다. + +#### Requirements +- 오디오 콘텐츠 ID와 시리즈 ID 유효성 검사를 한 곳에서 소유한다. +- 성인 여부를 호출부에서 알 수 있는 경우 공통 Access를 적용한다. +- 성인 여부를 알 수 없는 기존 경로는 임의 판정하지 않고 기존 정책 또는 상세 화면 정책을 유지한다. +- 기존 `Constants.EXTRA_AUDIO_CONTENT_ID`, `Constants.EXTRA_SERIES_ID` 계약을 유지한다. +- Home, Content, Content Overview, Creator Channel의 해당 진입점은 Content Action을 사용한다. +- Action 적용 후 대상 UI에서 `AudioContentDetailActivity`, `SeriesDetailActivity` Intent를 직접 구성하지 않는다. + +#### Edge Cases +- ID가 0 이하이면 기존처럼 이동하지 않는다. +- 접근 조건이 충족되지 않으면 상세 Activity를 시작하지 않는다. +- 화면별 분석 source 또는 후처리가 존재하면 command metadata 또는 호출 결과 처리로 보존하고 도메인 규칙과 혼합하지 않는다. + +### Feature E: Live 공통 Action +현재 Creator Channel과 Home에서 공유하는 Coordinator 및 On-air 중복 흐름을 Live 소유의 단일 진입점으로 정리한다. + +#### Requirements +- `CreatorChannelLiveCoordinator`의 실제 소유권을 Live로 이동하거나 Live 공개 진입점 뒤의 호환 adapter로 전환한다. +- 라이브 상세 조회, 입장 가능 여부, 무료/결제, 비밀번호, 상세 표시 분기를 한 번만 구현한다. +- 기존 오디오 서비스 중단, 라이브 입장 API, 예약/결제/비밀번호 Dialog, `LiveRoomActivity` 이동 동작을 보존한다. +- Home, Creator Channel, On-air가 동일한 Live 진입점을 사용한다. +- Home 새로고침과 같은 호출 화면 전용 후처리는 Live 규칙과 분리된 callback/result 처리로 유지한다. +- 라이브 생성처럼 하위 Activity가 결과를 반환하는 흐름은 raw `ActivityResult`를 명시적 Live 결과로 변환하고 Creator Channel의 단일 Live result handler에서 입장 또는 안내 후처리를 결정한다. +- 레거시 `LiveViewModel`, Dialog, Activity는 수정하지 않고 adapter로 사용한다. + +#### Edge Cases +- 방 관리자 본인 입장, 무료/결제 완료, 비밀번호 방, 유료 방 조건의 기존 분기 순서를 유지한다. +- 라이브 상세에 channel 정보가 없는 경우 기존처럼 상세 화면 흐름을 사용한다. +- 입장 성공 이후 호출 화면 새로고침 여부가 달라도 입장 정책은 중복 구현하지 않는다. + +### Feature F: Creator, Community, Chat 공통 Action +반복 navigation과 접근 조건을 각 소유 도메인의 단일 진입점으로 통합한다. + +#### Requirements +- Creator Action은 유효한 `creatorId`를 기준으로 Creator Channel 진입을 제공한다. +- Community Action은 로그인과 유효한 `postId` 확인 후 게시글 상세 진입을 제공한다. +- Community mutation 성공은 `CommunityChange.Created`, `Updated`, `Deleted`, `PinChanged`처럼 발생한 사실을 나타내는 명시적 결과로 표현한다. +- 레거시 Community 작성/수정 Activity의 `ActivityResult.RESULT_OK`는 `v2` adapter에서 `CommunityChange`로 변환한다. +- Community Action과 mutation 결과는 `refreshHome`, `refreshCommunityTab`처럼 호출 화면 구조를 나타내는 callback을 입력으로 받지 않는다. +- Creator Channel은 `handleCommunityChange` 단일 composition 진입점에서 Community 변경 종류에 따른 Home/Community projection 갱신을 결정한다. +- Community 작성, 수정, 삭제, 고정 변경의 성공 경로는 직접 개별 refresh를 호출하지 않고 `handleCommunityChange`를 사용한다. +- Chat Action은 room 기반 진입과 creator 기반 DM 생성/진입의 차이를 명시적인 command로 구분한다. +- 기존 owner/non-owner, AI/DM 채팅 타입 분기와 Activity result 계약을 유지한다. +- Chat/DM 하위 Activity 결과에 따라 호출 화면 후처리가 필요한 경우 raw result를 명시적 Chat 결과로 변환하고 호출 feature의 단일 handler에서 처리한다. +- Home의 UI model과 Creator Channel의 Response를 Action 공개 입력으로 사용하지 않는다. +- 실제 중복이 확인되지 않은 follow, 알림, FanTalk, Donation mutation은 이번 Action에 추측으로 포함하지 않는다. + +#### Edge Cases +- creator, post, room ID가 유효하지 않으면 기존처럼 이동하지 않는다. +- Activity result가 필요한 호출부는 일반 `startActivity`로 단순화하지 않는다. +- 채팅방 생성 API가 필요한 경로와 기존 room ID로 바로 진입하는 경로를 혼합하지 않는다. +- 아직 생성되지 않은 Creator Channel Fragment는 자체 initial load로 최신 데이터를 조회하며, 현재 Activity 내부 두 projection 갱신을 위해 이벤트 replay/state 동기화 계층을 추가하지 않는다. + +### Feature G: 의존 방향과 호환성 정리 +공통 Action 도입 후 feature 간 직접 의존과 이전 중복 진입점을 정리한다. + +#### Requirements +- feature는 공통 Access 또는 소유 도메인 Action을 호출할 수 있다. +- 도메인 Action은 Home, Content Main, Creator Channel 같은 호출 feature를 import하지 않는다. +- data 구현은 Retrofit DTO와 레거시 API를 알고, 도메인 정책은 Retrofit 및 Android UI를 알지 않도록 유지한다. +- Android UI가 필요한 navigation/Dialog wrapper는 정책과 분리된 application/presentation action으로 둔다. +- 직접적인 Activity 결과 흐름은 domain/application event로 우회하지 않고 `ActivityResult -> 명시적 결과 -> feature handler` 의존 방향을 유지한다. +- `AppDI.kt`는 새 계약과 구현을 조립하되 레거시 등록을 불필요하게 변경하지 않는다. +- 모든 호출부 전환이 끝난 이전 helper와 중복 함수만 제거한다. +- package 이동은 Action 전환 후 필요성이 확인된 파일에 한해 별도 Task로 수행한다. + +--- + +## 8. UX / UI Expectations +- 로그인, 본인인증, 성인 콘텐츠 설정 안내의 표시 순서와 문구를 유지한다. +- 허용된 사용자의 콘텐츠, 라이브, 커뮤니티, 채팅 진입 결과는 기존과 같아야 한다. +- 기존 Dialog 크기, Activity flag, Intent extra, Activity result 처리와 화면 새로고침 동작을 유지한다. +- 리팩토링 자체로 신규 화면, 버튼, Toast, loading UI를 추가하지 않는다. + +--- + +## 9. Technical Constraints +- 변경 범위는 `app/src/main/java/kr/co/vividnext/sodalive/v2`, 대응 `app/src/test/.../v2`, `AppDI.kt`, 본 작업 문서로 제한한다. +- 레거시 파일은 직접 수정하지 않고 기존 기능을 호출하는 `v2` wrapper/adapter를 작성한다. +- API -> Repository -> ViewModel -> Activity/Fragment의 기존 흐름을 임의로 깨지 않는다. +- 신규 테스트는 Access 판단, Action 입력·출력, route/정책 같은 순수 로직을 우선 검증한다. +- 소스 문자열 테스트만으로 정책을 검증하지 않고, 가능한 범위에서 실제 입력·출력 단위 테스트를 추가한다. +- Community 변경처럼 호출 화면이 갱신 대상을 결정하는 경우 도메인 결과와 feature invalidation handler를 분리하고, observer는 Activity 밖의 다수 소비자에게 전파할 요구가 생길 때만 도입한다. +- 각 도메인 Phase에서 Activity result, callback, refresh 후처리를 조사하고 단일 handler가 필요한 흐름만 최소 범위로 통합한다. +- 기존 source test가 이전 함수명을 고정한 경우 새 공개 계약을 검증하도록 최소 수정한다. +- 각 Phase는 관련 단위 테스트, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`를 통과해야 한다. +- 테스트 클래스가 추가되면 `docs/agent-guides/build-test-style.md`에 단일 실행 예시를 추가한다. +- 작업 범위가 변경되면 구현 전에 `plan-task.md`를 먼저 갱신한다. + +--- + +## 10. Metrics +- 로그인과 성인 접근 정책을 독립적으로 판단하는 구현이 `v2` 내 한 곳에만 존재한다. +- `CreatorChannelActivity`와 `HomeOnAirLiveActivity`에 화면 전용 `ensureLoginAndAdultAuth` 구현이 남지 않는다. +- 대상 UI 호출부에서 `SharedPreferenceManager.token.isBlank()`로 개별 행동 접근을 판단하지 않는다. +- 오디오, 시리즈, 라이브, 크리에이터, 커뮤니티, 채팅의 합의된 호출부가 각각 단일 Action 진입점을 사용한다. +- Creator Channel의 Community 작성, 수정, 삭제, 고정 변경 성공 경로가 명시적 `CommunityChange`를 거쳐 하나의 composition handler에서 projection을 갱신한다. +- 공통 Action 공개 계약이 feature 전용 UI model 또는 DTO에 의존하지 않는다. +- 기존 Intent extra, Activity result, 로그인/인증/설정 UX를 검증하는 회귀 테스트가 통과한다. +- 각 Phase 완료 시 중복 제거 전후 호출부 목록과 검증 결과가 `plan-task.md`에 누적 기록된다. + +--- + +## 11. Open Questions +- 없음. 전체 작업은 한 번에 패키지를 재구성하지 않고 Access부터 도메인별로 순차 진행한다. +- 각 도메인 Phase에서 조사 결과 실제 중복이 없거나 화면별 정책이 다른 동작은 공통 Action으로 만들지 않고 계획 문서에 근거를 기록한다.