mycode/myApp/HaruDanim/Widgets/ActionControlWidget.swift
songyc macbook e4bba280ad feat(control): Control Center toggle for running an action (iOS 18 control)
제어 센터/잠금화면 하단/액션 버튼에 배치할 수 있는 '행동 실행' 컨트롤:
- 행동 하나를 골라(미선택 시 배치 첫 행동) 시간형은 시작/종료 토글,
  횟수형은 탭마다 +1 (토글 표시는 꺼짐으로 복귀)
- 전용 ToggleActionControlIntent(SetValueIntent + LiveActivityIntent) —
  앱 프로세스 실행이라 제어 센터에서 시작해도 다이나믹 아일랜드가 바로 뜸.
  단축어 앱에는 숨김(isDiscoverable=false), 프리미엄 게이트 적용
- DataChange.commit / IntentStore.performWrite에
  ControlCenter.reloadAllControls() 추가 — 앱/위젯/시리/워치 어느 경로로
  측정 상태가 바뀌어도 컨트롤 토글 표시가 따라온다

시뮬레이터에는 제어 센터에 컨트롤을 추가할 수단이 없어 빌드·인텐트
메타데이터 등록 확인까지 검증 (실기기에서 배치·토글 확인 필요).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-14 00:19:36 +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: value.symbolName
)
}
}
.displayName("행동 실행")
.description("행동 하나를 골라 제어 센터에서 바로 실행해요. 시간 측정은 시작/종료 토글, 횟수 기록은 탭마다 +1. (프리미엄)")
}
}