Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
42b08fb
FEAT: RouteStep 노선명·노선 색·정거장 수 필드 추가
sangYuLv Sep 26, 2026
eecf091
FEAT: 라이브 액티비티 확인용 Mock 내 경로 시나리오 추가
sangYuLv Sep 26, 2026
6d784b6
FEAT: 약속 이동 진행 도메인 모델 추가
sangYuLv Sep 26, 2026
fe9d667
FEAT: 약속 이동 라이브 액티비티 저장소 프로토콜 추가
sangYuLv Sep 26, 2026
e1e0f51
FEAT: 라이브 액티비티 단계 진행 서비스 추가
sangYuLv Sep 26, 2026
b99e5cd
FEAT: 약속 이동 라이브 액티비티 위젯 익스텐션 추가
sangYuLv Sep 26, 2026
ce851df
FEAT: ActivityKit 기반 라이브 액티비티 저장소 구현
sangYuLv Sep 26, 2026
800bae7
FEAT: 실시간 현황 허용 상태 조회 추가
sangYuLv Sep 26, 2026
4574d5a
FEAT: 라이브 액티비티 의존성 DI 등록
sangYuLv Sep 26, 2026
f9a5354
FEAT: 마이페이지 실시간 현황 설정 행 추가
sangYuLv Sep 26, 2026
3eff215
FEAT: 라이브 액티비티 탭·위치 공유 딥링크 처리
sangYuLv Sep 26, 2026
3fb35ca
FEAT: 라이브 액티비티 위치 공유 실패 알림 추가
sangYuLv Sep 26, 2026
ef92e91
FEAT: DEBUG 전용 라이브 액티비티 시작 메뉴 추가
sangYuLv Sep 26, 2026
0ef5d56
REFACTOR: 마이페이지 위치 권한 행 구독 방식 전환
sangYuLv Sep 26, 2026
df86d59
REMOVE: MyPageViewModel 미사용 위치 권한 액션·구독 보관 프로퍼티 제거
sangYuLv Sep 26, 2026
24ed44f
REFACTOR: 프리뷰 코드 제거
sangYuLv Sep 27, 2026
b364bfe
DESIGN: 실시간 현황 아이콘 변경
sangYuLv Sep 27, 2026
c1c5e3e
REFACTOR: FCMTokenService 프로토콜과 DefaultFCMTokenService 구현 분리
sangYuLv Sep 28, 2026
c834db4
REFACTOR: SessionValidationService 프로토콜과 DefaultSessionValidationServ…
sangYuLv Sep 28, 2026
ef8406d
REFACTOR: 단계 진행 인텐트의 RouteProgressService를 AppDependency 주입으로 전환
sangYuLv Sep 28, 2026
5bbc61e
FIX: 마지막 확인 지점이 도착 지점이 아닐 때 라이브 액티비티 미종료 해결
sangYuLv Sep 28, 2026
271b4fe
REFACTOR: 진행 바 ForEach의 중간 배열 할당 제거
sangYuLv Sep 28, 2026
5bad620
FIX: 같은 약속 라이브 액티비티 딥링크로 채팅 입력 상태가 사라지던 문제 해결
sangYuLv Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions WhereAreYou/Shared/AdvanceRouteProgressIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// AdvanceRouteProgressIntent.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-26.
//

import Foundation
import AppIntents

/// 라이브 액티비티의 단계 버튼("출발했나요?" 등) — 누르면 액티비티를 다음 확인 지점으로 넘긴다.
/// LiveActivityIntent라 앱 프로세스에서 실행된다. 처리는 앱이 AppDependencyManager에 등록한 RouteProgressService에 맡긴다
struct AdvanceRouteProgressIntent: LiveActivityIntent {

static var title: LocalizedStringResource = "이동 단계 진행"
static var isDiscoverable = false

@Parameter(title: "약속 ID")
var appointmentID: String

@AppDependency
private var routeProgressService: RouteProgressService

init() { }

init(appointmentID: String) {
self.appointmentID = appointmentID
}

func perform() async throws -> some IntentResult {
await routeProgressService.advance(appointmentID: appointmentID)
return .result()
}

}
62 changes: 62 additions & 0 deletions WhereAreYou/Shared/AppointmentDeepLink.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// AppointmentDeepLink.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-26.
//

import Foundation

/// 라이브 액티비티에서 앱으로 들어오는 링크 — 위젯은 URL을 만들고 앱은 URL을 해석한다.
/// 형식: whereareyou://appointment/{약속 ID}/{route | share-location}
enum AppointmentDeepLink {

/// 약속 지도 화면 열기
case route(appointmentID: String)
/// 채팅방을 열고 내 위치를 바로 전송
case shareLocation(appointmentID: String)

var appointmentID: String {
switch self {
case .route(let appointmentID), .shareLocation(let appointmentID):
return appointmentID
}
}

static let scheme = "whereareyou"
private static let host = "appointment"
private static let routePath = "route"
private static let shareLocationPath = "share-location"

var url: URL {
var components = URLComponents()
components.scheme = Self.scheme
components.host = Self.host
switch self {
case .route(let appointmentID):
components.path = "/\(appointmentID)/\(Self.routePath)"
case .shareLocation(let appointmentID):
components.path = "/\(appointmentID)/\(Self.shareLocationPath)"
}
// path에 들어가는 값은 약속 ID뿐이라 URL 생성이 실패하지 않는다
return components.url!
}

init?(url: URL) {
guard url.scheme == Self.scheme, url.host == Self.host else { return nil }

let pathComponents = url.pathComponents.filter { $0 != "/" }
guard pathComponents.count == 2 else { return nil }

let appointmentID = pathComponents[0]
switch pathComponents[1] {
case Self.routePath:
self = .route(appointmentID: appointmentID)
case Self.shareLocationPath:
self = .shareLocation(appointmentID: appointmentID)
default:
return nil
}
}

}
65 changes: 65 additions & 0 deletions WhereAreYou/Shared/AppointmentRouteActivityAttributes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// AppointmentRouteActivityAttributes.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-26.
//

import Foundation
import ActivityKit

/// 약속 이동 라이브 액티비티 데이터 — 앱과 위젯 익스텐션이 함께 쓰는 전송용 모델.
/// 속성과 상태를 합쳐 4KB를 넘을 수 없으므로 좌표·Place 없이 표시에 필요한 값만 담는다
struct AppointmentRouteActivityAttributes: ActivityAttributes {

/// 단계 버튼을 누를 때마다 바뀌는 값
struct ContentState: Codable, Hashable {
var reachedCheckpointCount: Int
var arrivedParticipantCount: Int
}

/// 진행 바의 구간 하나
struct Segment: Codable, Hashable {

/// 대중교통은 아이콘을 구분하려고 지하철·버스로 나눈다
enum Transport: String, Codable, Hashable {
case walk
case car
case subway
case bus
}

let transport: Transport
let destinationName: String
let estimatedMinutes: Int
let lineName: String?
let lineColorHex: String?
let stopCount: Int?
}

/// "~했나요?"로 묻는 확인 지점 하나
struct Checkpoint: Codable, Hashable {

enum Kind: String, Codable, Hashable {
case departure
case boarding
case alighting
case arrival
}

let kind: Kind
let placeName: String
let scheduledTime: Date
/// 이 지점에 도달한 뒤 이동 중인 구간 인덱스. 도착이면 구간 수와 같다
let segmentIndex: Int
}

let appointmentID: String
let appointmentName: String
let placeName: String
let appointmentTime: Date
let participantCount: Int
let segments: [Segment]
let checkpoints: [Checkpoint]

}
18 changes: 18 additions & 0 deletions WhereAreYou/Shared/RouteProgressService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// RouteProgressService.swift
// WhereAreYou
//
// Created by 이상유 on 2026-09-28.
//

import Foundation

/// 약속 이동 진행 서비스 — 라이브 액티비티 단계 버튼(LiveActivityIntent)이 화면 없이 부른다.
/// 인텐트가 이 프로토콜만 알도록 앱·위젯 공용 폴더에 둔다. 구현(DefaultRouteProgressService)은 앱 타깃에만 있고,
/// 앱 시작 시 AppDependencyManager에 등록해 인텐트의 @AppDependency로 주입한다
protocol RouteProgressService: Sendable {

/// 다음 확인 지점으로 넘긴다. 마지막 지점이면 액티비티를 종료한다
func advance(appointmentID: String) async

}
Loading
Loading