forked from stadiamaps/ferrostar
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFerrostarWidgetProvider.swift
More file actions
115 lines (100 loc) · 3.66 KB
/
Copy pathFerrostarWidgetProvider.swift
File metadata and controls
115 lines (100 loc) · 3.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import ActivityKit
import CoreLocation
import FerrostarCore
import FerrostarCoreFFI
import OSLog
private let logger = Logger(subsystem: "com.stadiamaps.ferrostar", category: "FerrostarWidgetProvider")
@available(iOS 16.2, *)
public class FerrostarWidgetProvider: WidgetProviding {
private var activity: Activity<TripActivityAttributes>?
private var lastUpdateDistance: CLLocationDistance?
public init() {
// No def
}
public func update(
visualInstruction: VisualInstruction,
spokenInstruction: SpokenInstruction?,
tripProgress: TripProgress
) {
let currentDistance = tripProgress.distanceToNextManeuver
// Check if we should update based on distance threshold
if shouldUpdate(currentDistance: currentDistance) {
lastUpdateDistance = currentDistance
Task {
do {
try await requestOrUpdate(
visualInstruction: visualInstruction,
spokenInstruction: spokenInstruction,
tripProgress: tripProgress
)
} catch {
logger.error("Failed to update Dynamic Island activity: \(error.localizedDescription)")
}
}
}
}
public func terminate() {
Task {
await activity?.end(nil, dismissalPolicy: .immediate)
lastUpdateDistance = nil
}
}
private func requestOrUpdate(
visualInstruction: VisualInstruction,
spokenInstruction: SpokenInstruction?,
tripProgress: TripProgress
) async throws {
let newState = TripActivityAttributes.ContentState(
instruction: visualInstruction,
distanceToNextManeuver: tripProgress.distanceToNextManeuver
)
let content = ActivityContent(state: newState, staleDate: nil)
guard let activity else {
activity = try Activity.request(attributes: .init(), content: content)
return
}
if let spokenInstruction {
logger.debug("Alert w/ \(spokenInstruction.text)")
}
// TODO: Evaluate Apple Maps (others) to determine the best strategy here.
// if let spokenInstruction {
// await activity.update(
// content,
// alertConfiguration: AlertConfiguration(
// title: "",
// body: "\(spokenInstruction.text)",
// sound: .named("None")
// )
// )
// } else {
await activity.update(content)
// }
}
private func shouldUpdate(currentDistance: CLLocationDistance) -> Bool {
guard let lastDistance = lastUpdateDistance else {
// First update
return true
}
let distanceChange = abs(lastDistance - currentDistance)
let threshold = updateThreshold(for: currentDistance)
return distanceChange >= threshold
}
private func updateThreshold(for distance: CLLocationDistance) -> CLLocationDistance {
// TODO: This could be way nicer, but it get's the job done as a starting point.
// Progressive scaling: closer to maneuver = more frequent updates
switch distance {
case 0 ..< 50: // < 50m: update every 5m
5
case 50 ..< 100: // 50-100m: update every 10m
10
case 100 ..< 200: // 100-200m: update every 15m
15
case 200 ..< 500: // 200-500m: update every 25m
25
case 500 ..< 1000: // 500m-1km: update every 50m
50
default: // > 1km: update every 100m
100
}
}
}