- 세트 엔진(수축→이완×횟수, 중단=미기록) iOS·워치 공용, 완주만 records.json 저장 - 첫 화면 시작 버튼 + 세팅(시간·횟수·진동 패턴)/설정(테마) 시트, 아래 스와이프 통계 - 통계: 하루(시간대별)/주간/월간 꺾은선, 달력 기준↔롤링(지난 7일·30일) 전환 - Live Activity(수축/이완·n/총·카운트다운), 백그라운드 무음 오디오로 타이머·진동 유지 - 단축어 '케겔 운동 시작', 워치 앱(즉시 시작·워치 전용 진동·physical-therapy 세션·기록 폰 병합) - 하루 다님 팔레트 계승(라이트/다크/시스템), 앱 아이콘 SVG→PNG 등록 - 검증: Debug·Release·워치 빌드 그린, 시뮬 QA 10장(26·18.5·워치 46/40mm), 완주·기록 경로 확인 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPmp29DQHY6Ahfss5SRopT
50 lines
1.5 KiB
Swift
50 lines
1.5 KiB
Swift
//
|
|
// KegingIntents.swift
|
|
// Keging
|
|
//
|
|
// 단축어 — "케겔 운동 시작" 하나. 앱을 열고 저장된 세팅대로 즉시 시작한다.
|
|
//
|
|
|
|
import AppIntents
|
|
import Foundation
|
|
|
|
struct StartKegelIntent: AppIntent {
|
|
static var title: LocalizedStringResource = "케겔 운동 시작"
|
|
static var description = IntentDescription("설정해 둔 수축·이완 사이클로 케겔 운동을 바로 시작해요.")
|
|
static var openAppWhenRun: Bool = true
|
|
|
|
@MainActor
|
|
func perform() async throws -> some IntentResult {
|
|
AppLaunchState.shared.startRequested = true
|
|
NotificationCenter.default.post(name: .startWorkoutRequested, object: nil)
|
|
return .result()
|
|
}
|
|
}
|
|
|
|
struct KegingShortcuts: AppShortcutsProvider {
|
|
static var appShortcuts: [AppShortcut] {
|
|
AppShortcut(
|
|
intent: StartKegelIntent(),
|
|
phrases: [
|
|
"\(.applicationName) 시작",
|
|
"\(.applicationName) 운동 시작",
|
|
"\(.applicationName)에서 케겔 운동 시작"
|
|
],
|
|
shortTitle: "케겔 운동 시작",
|
|
systemImageName: "timer"
|
|
)
|
|
}
|
|
}
|
|
|
|
extension Notification.Name {
|
|
static let startWorkoutRequested = Notification.Name("startWorkoutRequested")
|
|
}
|
|
|
|
/// 단축어가 콜드 스타트로 앱을 열 때 알림보다 화면이 늦게 붙는 경합 대비 플래그
|
|
@MainActor
|
|
final class AppLaunchState {
|
|
static let shared = AppLaunchState()
|
|
var startRequested = false
|
|
private init() {}
|
|
}
|