react-native-alarms 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AlarmService.ts +289 -89
- package/README.md +186 -12
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmData.kt +160 -8
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmModule.kt +3 -2
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmRingActivity.kt +3 -2
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmService.kt +55 -1
- package/android/local.properties +8 -0
- package/android-lib/src/main/AndroidManifest.xml +1 -0
- package/index.ts +7 -5
- package/ios/.xcode.env.local +1 -1
- package/ios/ReactNativeAlarm/Info.plist +2 -2
- package/ios/ReactNativeAlarm.xcodeproj/project.pbxproj +8 -0
- package/ios/alarm/AlarmData.swift +23 -6
- package/ios/alarm/AlarmKitBackend.swift +43 -40
- package/ios/alarm/AlarmModule.swift +69 -64
- package/ios/alarm/AlarmRingPresenter.swift +26 -19
- package/ios/alarm/AlarmStore.swift +9 -9
- package/ios/alarm/NotificationBackend.swift +113 -64
- package/package.json +4 -4
- package/specs/NativeAlarm.ts +127 -74
- package/src/types/index.ts +266 -0
- package/src/utils/AlarmRegistry.ts +57 -0
- package/src/utils/index.ts +1 -0
- package/.bundle/config +0 -2
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -0
- package/AlarmService.d.ts +0 -117
- package/commitlint.config.mjs +0 -1
- package/index.d.ts +0 -20
- package/src/assets/recording.mp4 +0 -0
- package/src/assets/sample-1.png +0 -0
- package/src/assets/sample-2.png +0 -0
- package/src/assets/sample-3.png +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## This file must *NOT* be checked into Version Control Systems,
|
|
2
|
+
# as it contains information specific to your local configuration.
|
|
3
|
+
#
|
|
4
|
+
# Location of the SDK. This is only used by Gradle.
|
|
5
|
+
# For customization when using a Version Control System, please read the
|
|
6
|
+
# header note.
|
|
7
|
+
#Fri Oct 23 16:38:30 IST 2026
|
|
8
|
+
sdk.dir=/Users/apple/Library/Android/sdk
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
2
|
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
3
4
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
4
5
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
|
5
6
|
<uses-permission
|
package/index.ts
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
export type {
|
|
9
9
|
AlarmRepeat,
|
|
10
10
|
AlarmConfig,
|
|
11
|
-
|
|
11
|
+
AlarmProp,
|
|
12
12
|
AlarmPermission,
|
|
13
13
|
AlarmEvent,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
AlarmEventMap,
|
|
15
|
+
AlarmEventName,
|
|
16
|
+
} from './src/types';
|
|
16
17
|
|
|
17
18
|
export { AlarmService } from './AlarmService';
|
|
18
19
|
|
|
19
|
-
import
|
|
20
|
-
|
|
20
|
+
import Alarm from './specs/NativeAlarm';
|
|
21
|
+
|
|
22
|
+
export default Alarm;
|
package/ios/.xcode.env.local
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export NODE_BINARY=/opt/homebrew/
|
|
1
|
+
export NODE_BINARY=/opt/homebrew/bin/node
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
27
27
|
<key>LSRequiresIPhoneOS</key>
|
|
28
28
|
<true/>
|
|
29
|
+
<key>NSAlarmKitUsageDescription</key>
|
|
30
|
+
<string>This app needs access to alarms to wake you up on time.</string>
|
|
29
31
|
<key>NSAppTransportSecurity</key>
|
|
30
32
|
<dict>
|
|
31
33
|
<key>NSAllowsArbitraryLoads</key>
|
|
@@ -73,7 +75,5 @@
|
|
|
73
75
|
</array>
|
|
74
76
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
75
77
|
<false/>
|
|
76
|
-
<key>NSAlarmKitUsageDescription</key>
|
|
77
|
-
<string>This app needs access to alarms to wake you up on time.</string>
|
|
78
78
|
</dict>
|
|
79
79
|
</plist>
|
|
@@ -223,10 +223,14 @@
|
|
|
223
223
|
inputFileListPaths = (
|
|
224
224
|
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
|
225
225
|
);
|
|
226
|
+
inputPaths = (
|
|
227
|
+
);
|
|
226
228
|
name = "[CP] Embed Pods Frameworks";
|
|
227
229
|
outputFileListPaths = (
|
|
228
230
|
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
|
229
231
|
);
|
|
232
|
+
outputPaths = (
|
|
233
|
+
);
|
|
230
234
|
runOnlyForDeploymentPostprocessing = 0;
|
|
231
235
|
shellPath = /bin/sh;
|
|
232
236
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-frameworks.sh\"\n";
|
|
@@ -262,10 +266,14 @@
|
|
|
262
266
|
inputFileListPaths = (
|
|
263
267
|
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-resources-${CONFIGURATION}-input-files.xcfilelist",
|
|
264
268
|
);
|
|
269
|
+
inputPaths = (
|
|
270
|
+
);
|
|
265
271
|
name = "[CP] Copy Pods Resources";
|
|
266
272
|
outputFileListPaths = (
|
|
267
273
|
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-resources-${CONFIGURATION}-output-files.xcfilelist",
|
|
268
274
|
);
|
|
275
|
+
outputPaths = (
|
|
276
|
+
);
|
|
269
277
|
runOnlyForDeploymentPostprocessing = 0;
|
|
270
278
|
shellPath = /bin/sh;
|
|
271
279
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeAlarm/Pods-ReactNativeAlarm-resources.sh\"\n";
|
|
@@ -7,6 +7,7 @@ struct AlarmData: Codable {
|
|
|
7
7
|
var timestamp: Double // ms since epoch
|
|
8
8
|
var title: String
|
|
9
9
|
var body: String
|
|
10
|
+
var image: String? = nil
|
|
10
11
|
var sound: String
|
|
11
12
|
var vibrate: Bool
|
|
12
13
|
var snoozeEnabled: Bool
|
|
@@ -16,11 +17,25 @@ struct AlarmData: Codable {
|
|
|
16
17
|
var intervalMs: Double
|
|
17
18
|
var data: String
|
|
18
19
|
var enabled: Bool = true
|
|
19
|
-
|
|
20
|
+
var componentName: String = "AlarmRing"
|
|
21
|
+
var componentPropsJSON: String = "{}"
|
|
22
|
+
|
|
20
23
|
static func now() -> Double { Date().timeIntervalSince1970 * 1000 }
|
|
21
|
-
|
|
24
|
+
|
|
22
25
|
var date: Date { Date(timeIntervalSince1970: timestamp / 1000) }
|
|
23
26
|
|
|
27
|
+
var componentProps: [String: Any] {
|
|
28
|
+
guard
|
|
29
|
+
let jsonData = componentPropsJSON.data(using: .utf8),
|
|
30
|
+
let object = try? JSONSerialization.jsonObject(with: jsonData),
|
|
31
|
+
let props = object as? [String: Any]
|
|
32
|
+
else {
|
|
33
|
+
return [:]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return props
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
/// Next time this alarm should ring, in ms since epoch, or 0 if never.
|
|
25
40
|
func nextTrigger(from: Double = AlarmData.now()) -> Double {
|
|
26
41
|
if !enabled { return 0 }
|
|
@@ -37,7 +52,7 @@ struct AlarmData: Codable {
|
|
|
37
52
|
return timestamp > from ? timestamp : 0
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
|
-
|
|
55
|
+
|
|
41
56
|
private func nextMatching(from: Double, ok: (Int) -> Bool) -> Double {
|
|
42
57
|
let cal = Calendar.current
|
|
43
58
|
let base = cal.dateComponents([.hour, .minute], from: date)
|
|
@@ -55,15 +70,16 @@ struct AlarmData: Codable {
|
|
|
55
70
|
}
|
|
56
71
|
return 0
|
|
57
72
|
}
|
|
58
|
-
|
|
73
|
+
|
|
59
74
|
// MARK: - JS bridge shape (same keys as Android)
|
|
60
|
-
|
|
75
|
+
|
|
61
76
|
func toDictionary() -> [String: Any] {
|
|
62
77
|
[
|
|
63
78
|
"alarmId": id,
|
|
64
79
|
"timestamp": timestamp,
|
|
65
80
|
"title": title,
|
|
66
81
|
"body": body,
|
|
82
|
+
"image": image ?? "",
|
|
67
83
|
"sound": sound,
|
|
68
84
|
"vibrate": vibrate,
|
|
69
85
|
"snoozeEnabled": snoozeEnabled,
|
|
@@ -74,7 +90,7 @@ struct AlarmData: Codable {
|
|
|
74
90
|
"nextTriggerAt": nextTrigger(),
|
|
75
91
|
]
|
|
76
92
|
}
|
|
77
|
-
|
|
93
|
+
|
|
78
94
|
/// Parses the `AlarmConfig` object JS passes to `scheduleAlarm` / `updateAlarm`.
|
|
79
95
|
static func from(config m: [String: Any], id: String) -> AlarmData {
|
|
80
96
|
let r = m["repeat"] as? [String: Any]
|
|
@@ -83,6 +99,7 @@ struct AlarmData: Codable {
|
|
|
83
99
|
timestamp: (m["timestamp"] as? NSNumber)?.doubleValue ?? 0,
|
|
84
100
|
title: m["title"] as? String ?? "",
|
|
85
101
|
body: m["body"] as? String ?? "",
|
|
102
|
+
image: m["image"] as? String,
|
|
86
103
|
sound: m["sound"] as? String ?? "default",
|
|
87
104
|
vibrate: (m["vibrate"] as? NSNumber)?.boolValue ?? true,
|
|
88
105
|
snoozeEnabled: (m["snoozeEnabled"] as? NSNumber)?.boolValue ?? true,
|
|
@@ -20,13 +20,13 @@ struct OpenAlarmAppIntent: LiveActivityIntent {
|
|
|
20
20
|
static let description = IntentDescription("Opens the ringing alarm's screen")
|
|
21
21
|
static let openAppWhenRun: Bool = true
|
|
22
22
|
static let isDiscoverable: Bool = false
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
@Parameter(title: "alarmID")
|
|
25
25
|
var alarmID: String
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
init() {}
|
|
28
28
|
init(alarmID: String) { self.alarmID = alarmID }
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
func perform() async throws -> some IntentResult {
|
|
31
31
|
AlarmKitBackend.shared.openFromAlert(alarmID)
|
|
32
32
|
return .result()
|
|
@@ -39,11 +39,11 @@ struct OpenAlarmAppIntent: LiveActivityIntent {
|
|
|
39
39
|
@available(iOS 26.0, *)
|
|
40
40
|
final class AlarmKitBackend {
|
|
41
41
|
static let shared = AlarmKitBackend()
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
private let manager = AlarmManager.shared
|
|
44
44
|
/// Wired by `AlarmModule` so native events reach JS.
|
|
45
45
|
var onEvent: ((String, AlarmData) -> Void)?
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
private var observer: Task<Void, Never>?
|
|
48
48
|
private var lastStates: [UUID: Alarm.State] = [:]
|
|
49
49
|
/// Alarms we are stopping as part of a programmatic snooze (so the observer
|
|
@@ -53,15 +53,15 @@ final class AlarmKitBackend {
|
|
|
53
53
|
/// (with its own TTS) is showing. The ring session stays alive until JS calls
|
|
54
54
|
/// stop/snooze, so the observer must not report these as dismissed.
|
|
55
55
|
private var silenced: [UUID: AlarmData] = [:]
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
private static let allWeekdays: [Locale.Weekday] = [
|
|
58
58
|
.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday,
|
|
59
59
|
]
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
// MARK: - Authorization
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
var isAuthorized: Bool { manager.authorizationState == .authorized }
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
/// 'granted' | 'denied' | 'notDetermined', same vocabulary as Android.
|
|
66
66
|
var status: String {
|
|
67
67
|
switch manager.authorizationState {
|
|
@@ -70,13 +70,13 @@ final class AlarmKitBackend {
|
|
|
70
70
|
default: return "notDetermined"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
func requestAuthorization() async -> Bool {
|
|
75
75
|
(try? await manager.requestAuthorization()) == .authorized
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// MARK: - Ids
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
/// Android ids are free-form strings; AlarmKit wants UUIDs. Real UUIDs pass through,
|
|
81
81
|
/// anything else is hashed deterministically so cancel/stop can find it again.
|
|
82
82
|
static func uuid(for id: String, snooze: Bool = false) -> UUID {
|
|
@@ -85,20 +85,20 @@ final class AlarmKitBackend {
|
|
|
85
85
|
let bytes = Array(SHA256.hash(data: Data(key.utf8)).prefix(16))
|
|
86
86
|
return bytes.withUnsafeBufferPointer { NSUUID(uuidBytes: $0.baseAddress!) as UUID }
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
private func stored(for uuid: UUID) -> AlarmData? {
|
|
90
90
|
AlarmStore.all().first {
|
|
91
91
|
Self.uuid(for: $0.id) == uuid || Self.uuid(for: $0.id, snooze: true) == uuid
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
// MARK: - Scheduling
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
func schedule(_ a: AlarmData) async throws {
|
|
98
98
|
cancel(a.id)
|
|
99
99
|
let next = a.nextTrigger()
|
|
100
100
|
guard next > 0 else { return } // nothing in the future; Android skips too
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
let hm = Calendar.current.dateComponents([.hour, .minute], from: a.date)
|
|
103
103
|
let time = Alarm.Schedule.Relative.Time(hour: hm.hour ?? 0, minute: hm.minute ?? 0)
|
|
104
104
|
let schedule: Alarm.Schedule
|
|
@@ -107,21 +107,21 @@ final class AlarmKitBackend {
|
|
|
107
107
|
schedule = .relative(.init(time: time, repeats: .weekly(Self.allWeekdays)))
|
|
108
108
|
case "weekly":
|
|
109
109
|
let days = a.weekdays.isEmpty
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
? Self.allWeekdays
|
|
111
|
+
: a.weekdays.filter { (1...7).contains($0) }.map { Self.allWeekdays[$0 - 1] }
|
|
112
112
|
schedule = .relative(.init(time: time, repeats: .weekly(days)))
|
|
113
113
|
default:
|
|
114
114
|
// none: one exact ring. interval: one exact ring, re-armed by the observer
|
|
115
115
|
// after it is dismissed (only while the app process is alive).
|
|
116
116
|
schedule = .fixed(Date(timeIntervalSince1970: next / 1000))
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
_ = try await manager.schedule(id: Self.uuid(for: a.id), configuration: configuration(for: a, schedule: schedule))
|
|
120
120
|
startObserving()
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
private func configuration(for a: AlarmData, schedule: Alarm.Schedule)
|
|
124
|
-
|
|
124
|
+
-> AlarmManager.AlarmConfiguration<RNAlarmMetadata>
|
|
125
125
|
{
|
|
126
126
|
let title = LocalizedStringResource(stringLiteral: a.title.isEmpty ? "Alarm" : a.title)
|
|
127
127
|
let stop = AlarmButton(text: "Stop", textColor: .white, systemImageName: "stop.fill")
|
|
@@ -132,15 +132,15 @@ final class AlarmKitBackend {
|
|
|
132
132
|
let alert = AlarmPresentation.Alert(
|
|
133
133
|
title: title, stopButton: stop, secondaryButton: open, secondaryButtonBehavior: .custom
|
|
134
134
|
)
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
let attributes = AlarmAttributes<RNAlarmMetadata>(
|
|
137
137
|
presentation: AlarmPresentation(alert: alert),
|
|
138
138
|
metadata: RNAlarmMetadata(),
|
|
139
139
|
tintColor: .blue
|
|
140
140
|
)
|
|
141
141
|
let sound: AlertConfiguration.AlertSound =
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
(a.sound.isEmpty || a.sound == "default") ? .default : .named(a.sound)
|
|
143
|
+
|
|
144
144
|
return AlarmManager.AlarmConfiguration(
|
|
145
145
|
schedule: schedule,
|
|
146
146
|
attributes: attributes,
|
|
@@ -148,18 +148,18 @@ final class AlarmKitBackend {
|
|
|
148
148
|
sound: sound
|
|
149
149
|
)
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
func cancel(_ id: String) {
|
|
153
153
|
for uuid in [Self.uuid(for: id), Self.uuid(for: id, snooze: true)] {
|
|
154
154
|
try? manager.cancel(id: uuid)
|
|
155
155
|
silenced.removeValue(forKey: uuid)
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
func cancelAll() {
|
|
160
160
|
for alarm in (try? manager.alarms) ?? [] { try? manager.cancel(id: alarm.id) }
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
func stop(_ id: String) {
|
|
164
164
|
for uuid in [Self.uuid(for: id), Self.uuid(for: id, snooze: true)] {
|
|
165
165
|
try? manager.stop(id: uuid)
|
|
@@ -171,7 +171,7 @@ final class AlarmKitBackend {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
/// "Open" tapped on the system alert (OpenAlarmAppIntent). The tap stops the
|
|
176
176
|
/// AlarmKit alarm, so adopt the session as `silenced` — still ringing as far as
|
|
177
177
|
/// JS is concerned — and show the React ring screen once the app is frontmost.
|
|
@@ -179,7 +179,7 @@ final class AlarmKitBackend {
|
|
|
179
179
|
guard let a = AlarmStore.get(id) else { return }
|
|
180
180
|
let current = (try? manager.alarms) ?? []
|
|
181
181
|
for uuid in [Self.uuid(for: a.id), Self.uuid(for: a.id, snooze: true)]
|
|
182
|
-
|
|
182
|
+
where current.contains(where: { $0.id == uuid && $0.state == .alerting }) {
|
|
183
183
|
silenced[uuid] = a
|
|
184
184
|
try? manager.stop(id: uuid)
|
|
185
185
|
}
|
|
@@ -187,20 +187,23 @@ final class AlarmKitBackend {
|
|
|
187
187
|
if !silenced.values.contains(where: { $0.id == a.id }) {
|
|
188
188
|
silenced[Self.uuid(for: a.id)] = a
|
|
189
189
|
}
|
|
190
|
-
DispatchQueue.main.async { AlarmRingPresenter.present(a, bridge: nil
|
|
190
|
+
DispatchQueue.main.async { AlarmRingPresenter.present(a, bridge: nil,
|
|
191
|
+
componentName: a.componentName,
|
|
192
|
+
componentProps: a.componentProps
|
|
193
|
+
) }
|
|
191
194
|
}
|
|
192
|
-
|
|
195
|
+
|
|
193
196
|
/// Stops the system alert's sound/UI while the in-app ring screen takes over.
|
|
194
197
|
/// The session stays "ringing" (see `silenced`) until JS stops or snoozes it.
|
|
195
198
|
func silence(_ id: String) {
|
|
196
199
|
let current = (try? manager.alarms) ?? []
|
|
197
200
|
for uuid in [Self.uuid(for: id), Self.uuid(for: id, snooze: true)]
|
|
198
|
-
|
|
201
|
+
where current.contains(where: { $0.id == uuid && $0.state == .alerting }) {
|
|
199
202
|
silenced[uuid] = stored(for: uuid)
|
|
200
203
|
try? manager.stop(id: uuid)
|
|
201
204
|
}
|
|
202
205
|
}
|
|
203
|
-
|
|
206
|
+
|
|
204
207
|
/// Silences the ringing alarm (if any) and re-rings once in `minutes`,
|
|
205
208
|
/// equivalent to Android's `AlarmScheduler.scheduleSnooze`.
|
|
206
209
|
func snooze(_ a: AlarmData, minutes: Int) async throws {
|
|
@@ -222,7 +225,7 @@ final class AlarmKitBackend {
|
|
|
222
225
|
)
|
|
223
226
|
startObserving()
|
|
224
227
|
}
|
|
225
|
-
|
|
228
|
+
|
|
226
229
|
func ringingAlarm() -> AlarmData? {
|
|
227
230
|
if let alerting = ((try? manager.alarms) ?? []).first(where: { $0.state == .alerting }) {
|
|
228
231
|
return stored(for: alerting.id)
|
|
@@ -230,9 +233,9 @@ final class AlarmKitBackend {
|
|
|
230
233
|
// Sound silenced for the in-app ring screen, but the session is still live.
|
|
231
234
|
return silenced.values.first
|
|
232
235
|
}
|
|
233
|
-
|
|
236
|
+
|
|
234
237
|
// MARK: - Events
|
|
235
|
-
|
|
238
|
+
|
|
236
239
|
/// Translates AlarmKit state changes into the Android-style events.
|
|
237
240
|
func startObserving() {
|
|
238
241
|
guard observer == nil else { return }
|
|
@@ -243,11 +246,11 @@ final class AlarmKitBackend {
|
|
|
243
246
|
}
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
|
-
|
|
249
|
+
|
|
247
250
|
private func handle(_ alarms: [Alarm]) {
|
|
248
251
|
var states: [UUID: Alarm.State] = [:]
|
|
249
252
|
for alarm in alarms { states[alarm.id] = alarm.state }
|
|
250
|
-
|
|
253
|
+
|
|
251
254
|
for (uuid, state) in states where lastStates[uuid] != state {
|
|
252
255
|
guard let a = stored(for: uuid) else { continue }
|
|
253
256
|
if state == .alerting {
|
|
@@ -258,7 +261,7 @@ final class AlarmKitBackend {
|
|
|
258
261
|
onEvent?(state == .countdown ? AlarmModule.eventSnoozed : AlarmModule.eventDismissed, a)
|
|
259
262
|
}
|
|
260
263
|
}
|
|
261
|
-
|
|
264
|
+
|
|
262
265
|
// One-off (`.fixed`) alarms disappear once stopped.
|
|
263
266
|
for (uuid, previous) in lastStates where states[uuid] == nil && previous == .alerting {
|
|
264
267
|
if silenced[uuid] != nil { continue } // silenced for the ring screen, not over yet
|
|
@@ -272,7 +275,7 @@ final class AlarmKitBackend {
|
|
|
272
275
|
}
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
|
-
|
|
278
|
+
|
|
276
279
|
lastStates = states
|
|
277
280
|
}
|
|
278
281
|
}
|