31 KiB
31 KiB
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을 호출해 동일 정책과 동작을 재구현하지 않고 사용할 수 있어야 한다.
- 푸시와 딥링크로 유입된 도메인 이동도 일반 화면과 같은 Action 및 Live 진입 정책을 사용한다.
- 푸시와 딥링크로 Live 상태를 조회해 목적지를 판단하는 동안 사용자에게 진행 상태와 실패 결과를 표시한다.
- 각 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 푸시/딥링크 진입점으로 계속 사용하는
DeepLinkActivity는 사용자 승인에 따라 기존 payload 계약을 Action에 연결하는 최소 변경을 허용한다. - 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는 로그인 확인 후 국가/본인인증, 성인 콘텐츠 표시 설정 순서로 평가한다.- 로그인 여부는 기존
MainV2Activity.isLoggedIn()과 동일하게SharedPreferenceManager.token.isNotBlank() && SharedPreferenceManager.token.length > 10기준을 사용한다. 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의 중복 판단 구현을 제거한다.v2UI의 직접 로그인 검사 중 화면 진입 자체를 막는 검사와 개별 행동을 막는 검사를 구분해 기존 종료 여부를 유지한다.
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와 도메인 정책으로 나누고 하나의 범용 함수에 합치지 않는다.
Final Ownership Boundary
- Access는 로그인 여부, 본인인증, 성인 콘텐츠 설정 등 접근 허용 판단과 기존 로그인/인증/설정 UX 실행을 소유한다.
- 도메인 Action은 안정적인 ID/command 유효성 검사, 동작에 필요한
AccessRequirement선택, Access 실행 요청과Ignored/Blocked/도메인 결과 반환을 소유한다. - Action Handler는 Access 실행기 주입과 레거시
Intent/Activity/Dialog/navigation adapter를 소유하고, 호출 feature의 UI model이나 화면별 refresh를 알지 않는다. - feature는 UI model을 command로 변환하고, 화면별 최초 query/API, 단일 화면만 소유하는 생성·mutation API,
ActivityResult수신, refresh/callback/projection 조합을 소유한다. - 보호된 도메인 Action을 호출하는 화면은 같은 Access guard를 바깥에서 중복 실행하지 않는다. 공통 Access 직접 호출은 화면 진입 자체 또는 화면 전용 동작의 접근 제한에 사용한다.
- 도메인과 관련된 모든 코드를 이관하지 않는다. 둘 이상의 호출부에서 반복되거나 모든 진입점에서 같아야 하는 유효성·접근 정책·결과 계약만 도메인 Action으로 이관한다.
- 도메인 Action의 범위는 페이지 이동만이 아니다. 반복되는 사전 조건과 결과 계약은 Action이, 실제 Android 화면 이동은 Handler가 소유한다.
- parent-child Fragment composition, same-feature child flow, 도메인 Action이 없는 system-only route, Home on-air 목록 화면 진입, 단일 owner 기능은 합의된 반복 정책이 없으면 feature에 유지한다.
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은v2adapter에서 명시적인 도메인 Action 결과 또는 change 타입으로 변환한다. - feature는 변환된 결과를
handleContentChange,handleLiveChange,handleCommunityChange,handleChatChange와 같은 의미 있는 단일 handler에 전달한다. - 단일 handler는 현재 feature가 조합하는 projection 갱신, Activity result 재전달, navigation, 화면별 후처리를 결정한다.
- 순수 도메인 정책과 결과 타입은
refreshHome,refreshTab, Fragment 참조처럼 호출 화면의 구성을 나타내는 callback을 입력으로 받지 않는다. Android Dialog/Activity를 직접 조립하는 presentation adapter는 레거시 UI side effect를 연결하기 위해 호출 화면 후처리 callback을 주입받을 수 있으나, 해당 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,SeriesDetailActivityIntent를 직접 구성하지 않는다.
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 처리로 유지한다.
LiveActionCoordinator는 레거시 Dialog/Activity를 조립하는 presentation adapter이므로refreshHomecallback을 주입받을 수 있지만,LiveEntryPolicy,LiveEntryDecision,LiveCreationResult에는 호출 화면 callback을 포함하지 않는다. - 라이브 생성처럼 하위 Activity가 결과를 반환하는 흐름은 raw
ActivityResult를 명시적 Live 결과로 변환하고 Creator Channel의 단일 Live result handler에서 입장 또는 안내 후처리를 결정한다. - 라이브 상세의 channel 정보가 없어 unavailable로 분기되는 경우 성인 접근 확인과 오디오 중단보다 먼저 처리한다.
- unavailable callback이 있으면 호출 화면의 후처리를 실행하고, callback이 없으면 호출자가 전달한 성인 여부와 상세 응답의 성인 여부를 OR로 합성한 동일 조건으로 접근을 확인한 뒤 상세 화면을 표시한다.
- 라이브 생성
RESULT_OK는 즉시 입장할 수 없는 결과도 Home을 새로고침하며, channel 정보가 있지만 room ID가 유효하지 않으면RefreshOnly로 변환한다. 실패 또는 취소만Ignored로 변환한다. - 레거시
LiveViewModel, Dialog, Activity는 수정하지 않고 adapter로 사용한다.
Edge Cases
- 방 관리자 본인 입장, 무료/결제 완료, 비밀번호 방, 유료 방 조건의 기존 분기 순서를 유지한다.
- 라이브 상세에 channel 정보가 없는 경우 기존처럼 상세 화면 흐름을 사용한다.
- 호출자가 전달한 성인 여부와 재조회한 상세 응답의 성인 여부가 다르면 하나라도
true인 경우 성인 접근을 요구한다. - 입장 성공 이후 호출 화면 새로고침 여부가 달라도 입장 정책은 중복 구현하지 않는다.
Feature F: Creator, Community, Chat 공통 Action
반복 navigation과 접근 조건을 각 소유 도메인의 단일 진입점으로 통합한다.
Requirements
- Creator Action은 유효한
creatorId와AccessRequirement.Login을 기준으로 Creator Channel 진입 또는 명시적 차단/무시 결과를 제공한다. - Community Action은 로그인과 유효한
postId확인 후 게시글 상세 진입을 제공한다. - Community mutation 성공은
CommunityChange.Created,Updated,Deleted,PinChanged처럼 발생한 사실을 나타내는 명시적 결과로 표현한다. - 레거시 Community 작성/수정 Activity의
ActivityResult.RESULT_OK는v2adapter에서CommunityChange로 변환한다. - Community Action과 mutation 결과는
refreshHome,refreshCommunityTab처럼 호출 화면 구조를 나타내는 callback을 입력으로 받지 않는다. - Creator Channel은
handleCommunityChange단일 composition 진입점에서 Community 변경 종류에 따른 Home/Community projection 갱신을 결정한다. - Community 작성, 수정, 삭제, 고정 변경의 성공 경로는 직접 개별 refresh를 호출하지 않고
handleCommunityChange를 사용한다. - Chat Action은 room 기반 진입과 creator 기반 DM/owner 목록 진입의 차이를 명시적인 command로 구분하고, 유효 ID 확인 후
AccessRequirement.Login을 적용한다. - Creator Channel AI Chat의
AccessRequirement.AdultContent사전 조건과createChatRoom(characterId)API는 feature가 유지한다. 생성 성공 후 받은 room ID의 로그인 정책과 화면 이동은 Chat Action에 위임한다. - Creator, Community, Chat의 보호된 Action 호출부는 같은 로그인 guard를 화면에서 중복 실행하지 않는다.
- 기존 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는 실제 외부 의존성, 공유 생명주기 또는 구현 교체가 필요한 계약만 조립한다. 상태 없는 Action/Handler는 호출 경계에서 직접 조합할 수 있으며 DI 등록 자체를 완료 조건으로 삼지 않는다.- 모든 호출부 전환이 끝난 이전 helper와 중복 함수만 제거한다.
- package 이동은 Action 전환 후 필요성이 확인된 파일에 한해 별도 Task로 수행한다.
- feature 간 직접 Activity/Coordinator 의존은 합의된 반복 navigation/정책 대상만 Action/Handler로 대체한다. parent-child composition, same-feature child flow, system/deeplink/notification route, 목록 화면 진입과 단일 owner 기능은 예외 근거를 기록하고 유지할 수 있다.
Feature H: 푸시/딥링크의 도메인 Action 연결
푸시와 딥링크 payload 해석은 기존 진입점에 유지하고, 해석된 도메인 command의 실행은 이미 분리된 Action과 Live 진입 정책에 위임한다.
Requirements
- FCM 알림과 앱 내 알림 목록은 기존처럼
DeepLinkActivity로 진입하며, 별도의 범용PushRouteAction이나 EventBus를 추가하지 않는다. DeepLinkActivity가 foreground에서 직접 처리하는 오디오, 시리즈, 크리에이터, 커뮤니티 게시글, DM 이동은 각각 Content, Creator, Community, Chat Action을 사용한다.DeepLinkActivity에서 Main으로 전달한 payload와 앱 cold start payload는MainV2Activity가 같은 도메인 Action으로 처리한다.- 채팅 deep-link 값과 유효한 room ID가 함께 있으면 DM Action을 먼저 실행하고, 그 외 유효한 room ID는 channel/content ID보다 먼저 Live 진입으로 처리한다.
- Live 진입은
LiveActionCoordinator.enterLiveRoom(roomId)를 사용한다. 조회 결과 현재 진행 중인 라이브이면 기존 무료/유료/비밀번호 정책에 따라 입장하고, 예약 또는 즉시 입장할 channel 정보가 없으면 레거시 Live Detail 화면을 표시한다. - Community payload에 유효한
postId가 있으면 creator ID보다 우선하여CommunityActionCommand.PostDetail(postId)를 실행한다. - Community payload의
postId는 서버 실제 사용상 항상 전달되는 값으로 보되 선택적 계약은 유지한다. 유효한postId가 없고 creator ID가 있으면CreatorActionCommand.Profile(creatorId)로 Creator Channel을 표시한다. - Community 딥링크의
deep_link_sub5와${URISCHEME}://community/{id}path ID는 기존 계약의creatorId다.postId는 query/canonical extra로 별도 정규화하므로,routeByDeepLinkValue("community")는 post ID가 없는 레거시 Creator fallback으로 유지한다. - 현재 DM 푸시 계약은
${URISCHEME}://chat/{roomId}이며 Chat Action의DmRoom으로 처리한다. 기존message/message_id는 DM room ID로 재해석하지 않고 과거 알림 수신 호환용 legacy Message route로만 유지한다. - audio detail notification은 Content Action을 사용하고, 도메인 상세 이동이 아닌 audio player 화면 route는 기존 Access/system route를 유지한다.
- 현재 서버에서 더 이상 발행하지 않는 legacy message, audition, payment callback처럼 대응 도메인 Action이 없는 system-only route는 기존 직접 처리를 유지한다.
LiveRoomActivity가 foreground일 때 broadcast로 payload를 전달하는 기존 특수 경로와Intentflag/extra 계약을 유지한다.MainV2Activitycold start에서Constants.EXTRA_DATA또는 audio notification route가 있으면 기존 1초 지연 처리 동안LoadingDialog를 문구 없이 표시한다.- Live route는 공통 1초 대기 종료 후에도 room detail 조회가 진행 중이면 같은 문구 없는 로딩을 유지한다.
- Live 상태 조회가 완료되거나 실패하면 로딩을 해제하고,
LiveViewModel.toastLiveData의 실패 메시지를 기존 Toast 방식으로 표시한다. - Content, Creator, Community, Chat처럼 목적지 판단 자체에 별도 네트워크 조회가 없는 route는 공통 1초 대기가 끝나면 로딩을 해제하고 목적지로 이동한다.
onNewIntent처럼 기존 1초 지연이 없는 route에 인위적인 공통 대기를 추가하지 않는다. 단, Live의 실제 room detail 조회 로딩은 동일하게 표시한다.
Edge Cases
- 0 이하이거나 파싱할 수 없는 ID는 기존처럼 이동하지 않는다.
- Live payload에 room ID와 channel ID가 모두 있어도 room ID를 우선해 Creator Channel로 잘못 이동하지 않는다.
- Community payload에 post ID와 creator ID가 모두 있어도 게시글 상세를 우선한다.
- Community post ID가 없거나 유효하지 않은 경우에만 creator ID fallback을 사용한다.
DeepLinkActivity가 Main으로 Live payload를 전달하는 경우 cold start와onNewIntent모두 동일한 Live Action 진입점을 사용한다.- 일반 앱 실행에는 공통 딥링크 로딩을 표시하지 않는다.
- 공통 1초 대기가 끝나는 시점에 Live room detail 조회가 진행 중이면 로딩을 중간에 해제하지 않는다.
- Live 상태 조회가 실패해 목적지로 이동하지 못해도 로딩이 남아 있지 않고 실패 안내가 표시된다.
8. UX / UI Expectations
- 로그인, 본인인증, 성인 콘텐츠 설정 안내의 표시 순서와 문구를 유지한다.
- 허용된 사용자의 콘텐츠, 라이브, 커뮤니티, 채팅 진입 결과는 기존과 같아야 한다.
- 푸시/딥링크에서도 일반 화면과 같은 도메인 접근 정책을 적용하며, 진행 중 Live와 예약 Live의 기존 입장/상세 UX를 유지한다.
- cold start 푸시/딥링크의 공통 1초 대기와 Live의 추가 상태 조회 중에는 도메인에 종속되지 않은 문구 없는 spinner를 표시한다.
- 기존 Dialog 크기, Activity flag, Intent extra, Activity result 처리와 화면 새로고침 동작을 유지한다.
- 리팩토링 자체로 신규 화면, 버튼, Toast, loading UI를 추가하지 않는다.
9. Technical Constraints
- 변경 범위는
app/src/main/java/kr/co/vividnext/sodalive/v2, 대응 테스트, 사용자 승인을 받은app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt, 필요한 경우 이를 조립하는AppDI.kt, 본 작업 문서로 제한한다. - 레거시 파일은 직접 수정하지 않고 기존 기능을 호출하는
v2wrapper/adapter를 작성한다. 이번 후속 범위에서는 V2 진입점으로 유지할DeepLinkActivity만 명시적 예외다. - 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,git diff --check를 통과해야 한다.:app:ktlintCheck는 전체 기준선을 재실행하고, 저장소 기존 위반으로 실패하는 경우 Phase 변경 파일에 신규 ktlint 위반이 없음을 완료 gate로 삼아 실패 위치를plan-task.md에 기록한다. - 테스트 클래스가 추가되면
docs/agent-guides/build-test-style.md에 단일 실행 예시를 추가한다. - 작업 범위가 변경되면 구현 전에
plan-task.md를 먼저 갱신한다.
10. Metrics
- 로그인과 성인 접근 정책을 독립적으로 판단하는 구현이
v2내 한 곳에만 존재한다. CreatorChannelActivity와HomeOnAirLiveActivity에 화면 전용ensureLoginAndAdultAuth구현이 남지 않는다.- 대상 UI 호출부에서
SharedPreferenceManager.token으로 개별 행동 접근을 판단하지 않는다. - 오디오, 시리즈, 라이브, 크리에이터, 커뮤니티, 채팅의 합의된 호출부가 각각 단일 Action 진입점을 사용한다.
- 보호된 Creator, Community, Chat Action 호출부에 동일한 화면-local 로그인 guard가 중복되지 않는다.
- Creator Channel의 Community 작성, 수정, 삭제, 고정 변경 성공 경로가 명시적
CommunityChange를 거쳐 하나의 composition handler에서 projection을 갱신한다. - 공통 Action 공개 계약이 feature 전용 UI model 또는 DTO에 의존하지 않는다.
- 기존 Intent extra, Activity result, 로그인/인증/설정 UX를 검증하는 회귀 테스트가 통과한다.
- 푸시/딥링크의 Content, Creator, Community, Chat 이동은 해당 Action을 사용하고 Live 이동은
LiveActionCoordinator를 사용한다. - room ID가 있는 Live payload는 channel ID보다 우선하고, Community post ID는 creator ID보다 우선한다.
- Main의 cold start 푸시/딥링크 공통 1초 대기 중 문구 없는 로딩이 표시되고, Live는 추가 상태 조회까지 끊김 없이 유지되며 성공·실패 종료 시 해제된다.
- Live 상태 조회 실패 메시지가 사용자에게 노출된다.
- 각 Phase 완료 시 중복 제거 전후 호출부 목록과 검증 결과가
plan-task.md에 누적 기록된다.
11. Open Questions
- 없음. 전체 작업은 한 번에 패키지를 재구성하지 않고 Access부터 도메인별로 순차 진행한다.
- 각 도메인 Phase에서 조사 결과 실제 중복이 없거나 화면별 정책이 다른 동작은 공통 Action으로 만들지 않고 계획 문서에 근거를 기록한다.