mycode/myApp/HaruDanim/Widgets/ActionControlWidget.swift
songyc macbook 5b526fa650 feat(compat): iOS 하한 18.0 인하 — 글라스 버블 머티리얼 폴백·사용자 심볼 안전 렌더·Release 인라이너 크래시 우회
- 배포 타깃 26.0→18.0 (앱·위젯. 워치 10.0 불변)
- RadialNavigationView: glassEffect 계열을 @available(iOS 26) 선언 격리 + 18 머티리얼 원 폴백 (배치·연출 동일)
- DiaryZoomContainer.Coordinator → 비제네릭 톱레벨 DiaryZoomCoordinator + AnyView 소거 (하한 18 Release wholemodule에서 swift-frontend SILPerformanceInliner 무한 재귀 크래시 실측·우회)
- Image(safeSymbol:)/SymbolCompat: 카탈로그의 상위 OS 전용 심볼(18 기준 3개)이 교차 기기에서 빈 아이콘이 되지 않게 사용자 심볼 렌더 48곳+워치 7곳 폴백
- -symbolAuditDump 검증 인자 추가, iOS 18.5 시뮬 QA(빌드·radial 폴백·일기 줌·progressSelfTest 49 ALL PASS)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NNxA172hNDRpDsJ2qztKPf
2026-08-11 16:31:40 +09:00

101 lines
3.7 KiB
Swift

//
// ActionControlWidget.swift
// Haru_DanimWidgets
//
// (iOS 18+): / /
// . = / , = +1.
// ToggleActionControlIntent(LiveActivityIntent)
// .
//
import AppIntents
import SwiftUI
import WidgetKit
// MARK: - ( )
struct ActionControlConfigIntent: ControlConfigurationIntent {
static let title: LocalizedStringResource = "행동 선택"
static let description = IntentDescription("제어 센터 버튼으로 실행할 행동을 선택하세요.")
@Parameter(title: "행동")
var action: ActionEntity?
}
// MARK: -
///
struct ActionControlValue {
var actionID: String
var name: String
var symbolName: String
var isCount: Bool
var isRunning: Bool
var locked: Bool
}
struct ActionControlProvider: AppIntentControlValueProvider {
func previewValue(configuration: ActionControlConfigIntent) -> ActionControlValue {
ActionControlValue(
actionID: "", name: String(localized: "독서"), symbolName: "book.fill",
isCount: false, isRunning: false, locked: false
)
}
func currentValue(configuration: ActionControlConfigIntent) async throws -> ActionControlValue {
await MainActor.run {
IntentStore.refresh()
guard WidgetStore.isUnlocked else {
return ActionControlValue(
actionID: "", name: String(localized: "프리미엄 기능"), symbolName: "crown.fill",
isCount: false, isRunning: false, locked: true
)
}
//
guard let model = WidgetStore.action(configuration.action?.id)
?? WidgetStore.defaultActions(1).first else {
return ActionControlValue(
actionID: "", name: String(localized: "행동 없음"), symbolName: "square.grid.2x2",
isCount: false, isRunning: false, locked: false
)
}
return ActionControlValue(
actionID: model.uuid.uuidString,
name: model.name,
symbolName: model.symbolName,
isCount: model.trackingType == .count,
isRunning: model.isRunning,
locked: false
)
}
}
}
// MARK: -
struct ActionRunControl: ControlWidget {
var body: some ControlWidgetConfiguration {
AppIntentControlConfiguration(
kind: "HaruActionRunControl",
provider: ActionControlProvider()
) { value in
ControlWidgetToggle(
value.name,
isOn: value.isRunning,
action: ToggleActionControlIntent(actionID: value.actionID)
) { isOn in
Label(
value.locked
? String(localized: "프리미엄 필요")
: value.isCount
? String(localized: "탭해서 +1")
: isOn ? String(localized: "측정 중") : String(localized: "시작"),
systemImage: SymbolCompat.safe(value.symbolName)
)
}
}
.displayName("행동 실행")
.description("행동 하나를 골라 제어 센터에서 바로 실행해요. 시간 측정은 시작/종료 토글, 횟수 기록은 탭마다 +1. (프리미엄)")
}
}