Conversation
There was a problem hiding this comment.
🤖 AI 코드 리뷰 - 아키텍처 관점
약속 이동 라이브 액티비티 전체 플로우(위젯 익스텐션 추가, 딥링크 처리, 단계 진행 Intent, 마이페이지 권한 표시)를 한 PR에서 구현했다. Shared 폴더로 앱-위젯 공유 모델을 분리하고, DebugLiveActivity 폴더를 #if DEBUG로 격리해 임시 코드가 프로덕션에 유입되지 않도록 한 구조는 명확하다. 다만 LiveActivityPermissionStatus(도메인)와 LiveActivityPermissionState(프레젠테이션) 두 타입이 값 집합이 동일함에도 별도로 존재해 변환 계층이 중복되고, AdvanceRouteProgressIntent가 DIContainer를 직접 참조하는 부분이 아키텍처 경계를 침범한다. RouteProgressService가 도메인 서비스임에도 DI 컨테이너에 concrete 타입으로 직접 등록되어 있어 테스트 대체가 불가능한 구조다.
There was a problem hiding this comment.
🤖 AI 코드 리뷰 - 성능/최적화 관점
약속 이동 라이브 액티비티 기능을 추가하는 PR이다. 위젯 익스텐션 신규 추가, 딥링크 처리, RouteProgressService를 통한 단계 진행 처리, 마이페이지 실시간 현황 권한 표시 등이 포함된다. 성능 관점에서 주목할 부분은 두 곳이다. ActivityKitRouteLiveActivityRepository의 update/end에서 동일 appointmentID 액티비티를 매번 전체 스캔하는 방식, 그리고 RouteProgressBar에서 SwiftUI 렌더링 시 매 body 평가마다 Array(segments.enumerated())를 새로 할당하는 방식이다. 나머지 코드는 구조적으로 타당하며 명백한 성능 문제는 없다.
snughnu
left a comment
There was a problem hiding this comment.
특별하게 리뷰를 남겨야겠다는 부분은 딱히 없었습니다.
ai리뷰와 리뷰노트의 확인 사항 모두 확인했고,
추후 작업을 진행하면서 변경될 부분들(Mock, debug 등)도 확인해 뒀습니다!
UI 예쁘네요 수고하셨습니다. 🙊
JIRA
📝 작업 내용
📌 요약
🔍 상세
[1] 타깃 구성
WhereAreYouWidget: 라이브 액티비티 UI를 담는 위젯 익스텐션 타깃Shared폴더: 앱·위젯이 함께 컴파일하는 코드AppointmentRouteActivityAttributes(액티비티 데이터),AdvanceRouteProgressIntent(단계 버튼),AppointmentDeepLink(딥링크 URL),RouteProgressService(단계 진행 서비스 프로토콜)Info.plist:NSSupportsLiveActivities, URL 스킴whereareyou추가[2] 도메인
RouteStep: 노선명·노선 색·정거장 수 필드 추가 (도보·자동차는 nil)RouteProgressPlan/RouteProgressSegment/RouteCheckpoint/RouteProgressStatus: 이동 한 번의 고정 정보(구간·확인 지점)와 변하는 상태(도달 지점 수·도착 인원)RouteLiveActivityRepository→ActivityKitRouteLiveActivityRepository: 진행 중인 액티비티 조회·갱신·종료RouteProgressService→DefaultRouteProgressService: 다음 확인 지점으로 진행, 마지막 지점이면 종료[3] 잠금화면·다이나믹 아일랜드 UI
약속 이름/도착 인원(n/총원 도착)약속 시각 · 장소/약속까지 남은 시간: 약속 시각이 지나면 라벨이 "약속 시간", 시간이 빨간 "+경과 시간"으로 전환진행 바: 구간별 칩 — 도보 회색, 자차 indigo, 지하철·버스는 노선 색(버스는 🚌 + 번호), 지나온 구간은 흐리게, 현재 구간은 테두리현재 구간 1줄: "강남역까지 4정거장", "이수역까지 도보 6분" 등위치 공유 버튼/단계 버튼("출발했나요?" 등): 도착하면 둘 다 숨기고 "도착했어요"만 표시[4] 단계·종료 규칙
[5] 버튼 동작
단계 버튼:LiveActivityIntent로 앱을 띄우지 않고 앱 프로세스에서 처리 —@AppDependency로 주입받은RouteProgressService호출위치 공유 버튼: 딥링크 → 약속 탭 → 채팅방 진입 → 메시지 조회가 끝난 뒤 확인 없이 현재 위치 전송. 위치를 못 가져오면 설정 이동 알림액티비티 탭: 딥링크 → 약속 탭 → 약속 지도 표시[6] 실시간 현황 권한
LiveActivityPermissionStatus/LiveActivityPermissionRepository/ObserveLiveActivityPermissionUseCase: 위치 권한과 같은 구조로 허용 상태 조회·구독실시간 현황 설정행 추가 — 탭 시 설정 앱으로 이동 (앱에서 요청 불가)[7] DEBUG 전용 시작 메뉴
라이브 액티비티 시작 (DEBUG)→ 경로 시나리오(지하철 환승 / 버스 + 지하철 / 자차) 선택,약속 시간 지난 상태로 시작하위 메뉴DebugLiveActivity폴더에 모으고 전부#if DEBUG동작 없는 상태
💬 리뷰 노트
진행 단계는 저장하지 않습니다
단계 버튼으로 어디까지 왔는지는 앱이나 서버에 상태로 기록하지 않고, 라이브 액티비티 상태에만 둡니다.
2단계에서도 단계 도착과 위치 공유 모두 "채팅방에 일회성 메시지 + 약속 지도에 내 좌표 갱신"만 할 예정이라, 저장소를 두지 않았습니다.
단계 버튼 처리를 UseCase가 아닌 Service로 둔 이유
프로젝트에서 화면(ViewModel)이 부르는 동작은 UseCase로
ScreenFactory에서 만들고, 화면 밖에서 부르는 동작은 Service로 DI에 등록하고 있습니다.단계 버튼은 시스템이 실행하는
LiveActivityIntent가 화면 없이 부르므로RouteProgressService로 두었습니다.인텐트는 시스템이 직접 생성해 생성자로 의존성을 넘길 수 없어서, App Intents의
AppDependencyManager/@AppDependency를 사용했습니다.AppDelegate가 앱 시작 시 DI 컨테이너에서 서비스를 꺼내AppDependencyManager에 등록@AppDependency로RouteProgressService프로토콜만 주입받음 — DI 컨테이너를 모름Shared에 두었습니다같은 기준으로 기존
FCMTokenService,SessionValidationService도SignInService처럼 프로토콜(XxxService) + 구현(DefaultXxxService)으로 나누고 프로토콜 키로 등록했습니다. 호출부는 바뀌지 않았습니다.서버가 할 일을 임시로 앱이 하는 코드
push-to-start는 앱 코드를 거치지 않으므로, 3단계에서는 확인 지점·구간 목록을 서버가 만들어 보내야 합니다. 지금은 그 역할을 앱이 대신합니다.
나중에 한 번에 지울 수 있도록
DebugLiveActivity폴더에 모았습니다.RouteLiveActivityStarting+ActivityKitRouteLiveActivityRepository+Start: 시작을 정식 저장소 프로토콜에서 분리StartRouteLiveActivityUseCase,RouteCheckpointPlanner(3단계 서버 구현의 원본)ChatViewController+LiveActivityDebugMenu: DEBUG 메뉴이 폴더의 DEBUG 메뉴는 ViewModel·
ScreenFactory를 거치지 않고DIContainer에서 저장소를 직접 꺼내 유스케이스를 만듭니다. 지울 코드라ChatViewModel·ScreenFactory·DI 등록에 흔적을 남기지 않으려고 일부러 택한 방식이며, 파일 주석에도 적어 두었습니다.폴더 밖 연결점은
ChatViewController메뉴의#if DEBUG한 곳뿐이고, Release 빌드에는 포함되지 않습니다.도메인 모델과 액티비티 모델을 분리한 이유
도메인
RouteProgressPlan등과 액티비티 모델AppointmentRouteActivityAttributes가 거의 1:1이라 변환 코드가 있습니다.UserDTO처럼 외부 형식과 도메인을 분리하는 방식에 맞췄고, 액티비티 데이터는 4KB 제한이 있어 좌표·Place없이 표시용 값만 담도록 따로 두었습니다.잠금화면이 검게만 나오던 문제
"약속까지"와 남은 시간 사이 공백을 줄이려고 타이머
Text(timerInterval:)에.fixedSize()를 줬더니 배너 전체가 그려지지 않았습니다.같은 시기에 여백도 늘렸는데, 잠금화면 라이브 액티비티는 높이 160pt를 넘어도 같은 증상이 납니다.
👉 결론: 타이머는 고정 폭(44pt)으로 두고, 여백은 160pt 안에서 조정. 코드에 160pt 제한 주석 추가
시간이 지나면 단계 버튼이 반응하지 않던 문제
staleDate가 지나면 시스템이 액티비티 상태를.active에서.stale로 바꾸는데, 조회를.active만 하고 있어 버튼을 눌러도 대상을 찾지 못했습니다.지금은 약속 시각을
staleDate로 걸어 "+경과 시간" 표시에 쓰고 있어 계속 해당되는 조건입니다.👉 결론: 진행 중 판정에
.stale포함기존 동작을 바꾼 것
viewWillAppear에서 한 번 읽던 방식에서 구독 방식으로 바꿔, 설정 앱에서 권한을 바꾸고 돌아오면 바로 갱신됩니다. -> 실시간 권한을 추가하면서 통일했습니다.MyPageViewModel: 쓰이지 않던LocationPermissionAction,requestLocationPermission(),handlePermissionAction(),cancellables를 삭제했습니다. 같은 이름의 코드가 상세 화면LocationPermissionViewModel에도 있는데, 그쪽은 사용 중이라 그대로 두었습니다.MockAppointmentDetailRepository의 내 경로가 기존 다중 스텝 경로에서 시나리오 경로로 바뀌었고, 약속 시각이 13분 뒤 → 25분 뒤로 바뀌었습니다. 약속 지도 화면에도 그대로 반영됩니다.확인 부탁드립니다
Place.type == .subway)으로 추정하고 있습니다. 실제 길찾기 API를 붙일 때 탈것 정보를 받을 필드를RouteStep에 따로 두도록 수정해야 합니다. (탑승 교통 정보를 시내로만 추정하고 있었어서, 일반 열차나 시외 버스도 고려하도록 이 부분은 수정이 필요합니다.)추후 작업
DebugLiveActivity폴더 삭제📸 영상 / 이미지
WORK57.live.MP4
녹화 상태로 안 보이는 컴팩트 오른쪽 시간 표시 이미지