onboarding+liveactivity+habits: 3 fixes from device feedback (KC-61)
1. Onboarding Habits caption now explicitly mentions the toggles are changeable in Settings, not just times/custom reminder. 2. TaskLiveActivity: removed the checkbox-look circle glyph (hourglass instead). iOS has no API for an app to block the system's swipe/dismiss gesture on a Live Activity -- not attempted. Added a real Stop control via a new StopCountdownIntent (LiveActivityIntent, iOS 17+) embedded directly in the card (lock screen '×' + Dynamic Island 'Stop Countdown' button) -- the actual iOS-supported equivalent of 'long press for options', since Live Activities don't support notification-style long-press menus. 3. Habit notifications gain 'Not Today' (dismiss only, intentional no-op -- the lifetime-tally philosophy has no concept of a logged miss) and 'Stop Tracking' (destructive-styled; flips the habit's master toggle off and cancels every pending notification for that type across all its slots). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
26
KisaniCalWidgets/StopCountdownIntent.swift
Normal file
26
KisaniCalWidgets/StopCountdownIntent.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
import AppIntents
|
||||
import ActivityKit
|
||||
|
||||
/// Ends a running task Live Activity directly from its Lock Screen / Dynamic
|
||||
/// Island card — no need to open the app. Runs in the widget extension
|
||||
/// process (that's how LiveActivityIntent works), so it talks to ActivityKit
|
||||
/// directly rather than going through LiveActivityManager (app target only).
|
||||
@available(iOS 17.0, *)
|
||||
struct StopCountdownIntent: LiveActivityIntent {
|
||||
static var title: LocalizedStringResource = "Stop Countdown"
|
||||
static var description = IntentDescription("Ends this task's live countdown.")
|
||||
|
||||
@Parameter(title: "Task ID")
|
||||
var taskID: String
|
||||
|
||||
init() {}
|
||||
init(taskID: String) { self.taskID = taskID }
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
for activity in Activity<TaskActivityAttributes>.activities
|
||||
where activity.attributes.taskID == taskID {
|
||||
await activity.end(dismissalPolicy: .immediate)
|
||||
}
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ struct TaskLiveActivity: Widget {
|
||||
ActivityConfiguration(for: TaskActivityAttributes.self) { context in
|
||||
// ── Lock screen / banner ──
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: context.state.isComplete ? "checkmark.circle.fill" : "circle")
|
||||
.font(.system(size: 22))
|
||||
Image(systemName: context.state.isComplete ? "checkmark.circle.fill" : "hourglass")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(context.state.isComplete ? .green : accent)
|
||||
.frame(width: 24)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
@@ -40,6 +40,18 @@ struct TaskLiveActivity: Widget {
|
||||
.frame(width: 92, alignment: .trailing)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
// Explicit stop control — a Live Activity can't be prevented
|
||||
// from being swiped away by the system, so this gives an
|
||||
// intentional, in-card way to end tracking instead.
|
||||
if #available(iOS 17.0, *), !context.state.isComplete {
|
||||
Button(intent: StopCountdownIntent(taskID: context.attributes.taskID)) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 14)
|
||||
.padding(.trailing, 10)
|
||||
@@ -69,6 +81,16 @@ struct TaskLiveActivity: Widget {
|
||||
.font(.system(.callout, design: .monospaced))
|
||||
.lineLimit(1)
|
||||
}
|
||||
DynamicIslandExpandedRegion(.bottom) {
|
||||
if #available(iOS 17.0, *), !context.state.isComplete {
|
||||
Button(intent: StopCountdownIntent(taskID: context.attributes.taskID)) {
|
||||
Label("Stop Countdown", systemImage: "xmark.circle")
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.tint(accent)
|
||||
}
|
||||
}
|
||||
} compactLeading: {
|
||||
Image(systemName: "checklist").foregroundColor(accent)
|
||||
} compactTrailing: {
|
||||
|
||||
Reference in New Issue
Block a user