react-native-alarms 0.0.6 → 0.0.7

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.
@@ -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
- ? Self.allWeekdays
111
- : a.weekdays.filter { (1...7).contains($0) }.map { Self.allWeekdays[$0 - 1] }
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
- -> AlarmManager.AlarmConfiguration<RNAlarmMetadata>
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
- (a.sound.isEmpty || a.sound == "default") ? .default : .named(a.sound)
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
- where current.contains(where: { $0.id == uuid && $0.state == .alerting }) {
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
- where current.contains(where: { $0.id == uuid && $0.state == .alerting }) {
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
  }
@@ -14,20 +14,20 @@ final class AlarmModule: RCTEventEmitter {
14
14
  static let eventFired = "onAlarmFired"
15
15
  static let eventDismissed = "onAlarmDismissed"
16
16
  static let eventSnoozed = "onAlarmSnoozed"
17
-
17
+
18
18
  private var hasListeners = false
19
19
  private var activeObserver: NSObjectProtocol?
20
-
20
+
21
21
  override init() {
22
22
  super.init()
23
23
  NotificationBackend.install()
24
24
  NotificationBackend.shared.onEvent = { [weak self] name, alarm in self?.handle(name, alarm) }
25
- #if canImport(AlarmKit)
25
+ #if canImport(AlarmKit)
26
26
  if #available(iOS 26.0, *) {
27
27
  AlarmKitBackend.shared.onEvent = { [weak self] name, alarm in self?.handle(name, alarm) }
28
28
  AlarmKitBackend.shared.startObserving()
29
29
  }
30
- #endif
30
+ #endif
31
31
  // App launched by tapping the alarm notification/alert before this module existed.
32
32
  DispatchQueue.main.async { [weak self] in self?.presentIfRinging() }
33
33
  // Tapping an AlarmKit alert (and some notification taps on a warm start) only
@@ -36,40 +36,44 @@ final class AlarmModule: RCTEventEmitter {
36
36
  forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
37
37
  ) { [weak self] _ in self?.presentIfRinging() }
38
38
  }
39
-
39
+
40
40
  deinit {
41
41
  if let activeObserver { NotificationCenter.default.removeObserver(activeObserver) }
42
42
  }
43
-
43
+
44
44
  /// Shows the ring screen when a backend says an alarm is ringing right now.
45
45
  private func presentIfRinging() {
46
46
  var ringing = NotificationBackend.shared.ringing
47
- #if canImport(AlarmKit)
47
+ #if canImport(AlarmKit)
48
48
  if #available(iOS 26.0, *), ringing == nil { ringing = AlarmKitBackend.shared.ringingAlarm() }
49
- #endif
50
- if let ringing { AlarmRingPresenter.present(ringing, bridge: bridge) }
49
+ #endif
50
+ if let ringing { AlarmRingPresenter.present(ringing, bridge: bridge,
51
+ componentName: ringing.componentName ?? "AlarmRing",
52
+ componentProps: ringing.componentProps ?? [:]) }
51
53
  }
52
-
54
+
53
55
  private func handle(_ name: String, _ alarm: AlarmData) {
54
56
  if name == Self.eventFired {
55
- AlarmRingPresenter.present(alarm, bridge: bridge)
57
+ AlarmRingPresenter.present(alarm, bridge: bridge,
58
+ componentName: alarm.componentName ?? "AlarmRing",
59
+ componentProps: alarm.componentProps ?? [:])
56
60
  } else {
57
61
  AlarmRingPresenter.dismiss(alarm.id)
58
62
  }
59
63
  emit(name, alarm)
60
64
  }
61
-
65
+
62
66
  @objc override static func requiresMainQueueSetup() -> Bool { false }
63
-
67
+
64
68
  // MARK: - Events
65
-
69
+
66
70
  override func supportedEvents() -> [String]! {
67
71
  [Self.eventFired, Self.eventDismissed, Self.eventSnoozed]
68
72
  }
69
-
73
+
70
74
  override func startObserving() { hasListeners = true }
71
75
  override func stopObserving() { hasListeners = false }
72
-
76
+
73
77
  private func emit(_ name: String, _ a: AlarmData) {
74
78
  guard hasListeners else { return }
75
79
  sendEvent(withName: name, body: [
@@ -80,38 +84,38 @@ final class AlarmModule: RCTEventEmitter {
80
84
  "data": a.data,
81
85
  ])
82
86
  }
83
-
87
+
84
88
  // MARK: - Backend selection
85
-
89
+
86
90
  private var useAlarmKit: Bool {
87
- #if canImport(AlarmKit)
91
+ #if canImport(AlarmKit)
88
92
  if #available(iOS 26.0, *) { return AlarmKitBackend.shared.isAuthorized }
89
- #endif
93
+ #endif
90
94
  return false
91
95
  }
92
-
96
+
93
97
  private func cancelNative(_ id: String) {
94
98
  AlarmRingPresenter.dismiss(id)
95
99
  NotificationBackend.shared.cancel(id)
96
- #if canImport(AlarmKit)
100
+ #if canImport(AlarmKit)
97
101
  if #available(iOS 26.0, *) { AlarmKitBackend.shared.cancel(id) }
98
- #endif
102
+ #endif
99
103
  }
100
-
104
+
101
105
  // MARK: - Permissions
102
-
106
+
103
107
  /// Same keys as Android's `permissionMap()`. The Android-only flags are always
104
108
  /// true here so shared UI code can treat them as granted.
105
109
  private func permissionMap(_ done: @escaping ([String: Any]) -> Void) {
106
110
  NotificationBackend.shared.status { notifications in
107
111
  var status = notifications
108
112
  var backend = "notifications"
109
- #if canImport(AlarmKit)
113
+ #if canImport(AlarmKit)
110
114
  if #available(iOS 26.0, *) {
111
115
  status = AlarmKitBackend.shared.status
112
116
  backend = "alarmkit"
113
117
  }
114
- #endif
118
+ #endif
115
119
  done([
116
120
  "status": status,
117
121
  "notifications": status == "granted" || notifications == "granted",
@@ -122,15 +126,15 @@ final class AlarmModule: RCTEventEmitter {
122
126
  ])
123
127
  }
124
128
  }
125
-
129
+
126
130
  private func notificationStatus() async -> String {
127
131
  await withCheckedContinuation { c in NotificationBackend.shared.status { c.resume(returning: $0) } }
128
132
  }
129
-
133
+
130
134
  private func requestNotifications() async {
131
135
  _ = await withCheckedContinuation { c in NotificationBackend.shared.request { c.resume(returning: $0) } }
132
136
  }
133
-
137
+
134
138
  /// iOS never re-prompts after a denial, so (like Android for its special
135
139
  /// permissions) send the user to the app's Settings page.
136
140
  @MainActor private func openSettings() {
@@ -138,16 +142,16 @@ final class AlarmModule: RCTEventEmitter {
138
142
  UIApplication.shared.open(url)
139
143
  }
140
144
  }
141
-
145
+
142
146
  @objc(checkPermission:reject:)
143
147
  func checkPermission(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
144
148
  permissionMap { resolve($0) }
145
149
  }
146
-
150
+
147
151
  @objc(requestPermission:reject:)
148
152
  func requestPermission(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
149
153
  Task {
150
- #if canImport(AlarmKit)
154
+ #if canImport(AlarmKit)
151
155
  if #available(iOS 26.0, *) {
152
156
  let kit = AlarmKitBackend.shared
153
157
  if kit.status == "notDetermined" { _ = await kit.requestAuthorization() }
@@ -157,7 +161,7 @@ final class AlarmModule: RCTEventEmitter {
157
161
  }
158
162
  await self.openSettings()
159
163
  }
160
- #endif
164
+ #endif
161
165
  // Notifications back the fallback path.
162
166
  let notifications = await self.notificationStatus()
163
167
  if notifications == "notDetermined" {
@@ -168,42 +172,42 @@ final class AlarmModule: RCTEventEmitter {
168
172
  self.permissionMap { resolve($0) }
169
173
  }
170
174
  }
171
-
175
+
172
176
  @objc(canScheduleExactAlarms:reject:)
173
177
  func canScheduleExactAlarms(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
174
178
  permissionMap { (map: [String: Any]) in
175
179
  resolve((map["canScheduleExact"] as? Bool) ?? false)
176
180
  }
177
181
  }
178
-
182
+
179
183
  /// Android OEM concept; iOS has no such screen.
180
184
  @objc(openAutoStartSettings:reject:)
181
185
  func openAutoStartSettings(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
182
186
  resolve(false)
183
187
  }
184
-
188
+
185
189
  // MARK: - Scheduling
186
-
190
+
187
191
  @objc(scheduleAlarm:resolve:reject:)
188
192
  func scheduleAlarm(_ config: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
189
193
  let dict = config as? [String: Any] ?? [:]
190
194
  let id = (dict["alarmId"] as? String) ?? UUID().uuidString
191
195
  arm(AlarmData.from(config: dict, id: id), code: "E_SCHEDULE", reject: reject) { resolve(id) }
192
196
  }
193
-
197
+
194
198
  @objc(updateAlarm:config:resolve:reject:)
195
199
  func updateAlarm(_ alarmId: String, config: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
196
200
  guard AlarmStore.get(alarmId) != nil else { return resolve(false) }
197
201
  let dict = config as? [String: Any] ?? [:]
198
202
  arm(AlarmData.from(config: dict, id: alarmId), code: "E_UPDATE", reject: reject) { resolve(true) }
199
203
  }
200
-
204
+
201
205
  /// Cancel → persist → schedule, the same sequence as Android.
202
206
  private func arm(_ alarm: AlarmData, code: String, reject: @escaping RCTPromiseRejectBlock, done: @escaping () -> Void) {
203
207
  cancelNative(alarm.id)
204
208
  AlarmStore.save(alarm)
205
-
206
- #if canImport(AlarmKit)
209
+
210
+ #if canImport(AlarmKit)
207
211
  if #available(iOS 26.0, *), useAlarmKit {
208
212
  Task {
209
213
  do {
@@ -215,8 +219,8 @@ final class AlarmModule: RCTEventEmitter {
215
219
  }
216
220
  return
217
221
  }
218
- #endif
219
-
222
+ #endif
223
+
220
224
  NotificationBackend.shared.status { status in
221
225
  guard status == "granted" else {
222
226
  // Mirrors Android's SecurityException when the exact-alarm permission is missing.
@@ -227,7 +231,7 @@ final class AlarmModule: RCTEventEmitter {
227
231
  }
228
232
  }
229
233
  }
230
-
234
+
231
235
  @objc(cancelAlarm:resolve:reject:)
232
236
  func cancelAlarm(_ alarmId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
233
237
  let existed = AlarmStore.get(alarmId) != nil
@@ -235,31 +239,31 @@ final class AlarmModule: RCTEventEmitter {
235
239
  AlarmStore.remove(alarmId)
236
240
  resolve(existed)
237
241
  }
238
-
242
+
239
243
  @objc(cancelAllAlarms:reject:)
240
244
  func cancelAllAlarms(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
241
245
  AlarmRingPresenter.dismiss()
242
246
  AlarmStore.all().forEach { cancelNative($0.id) }
243
247
  NotificationBackend.shared.cancelAll()
244
- #if canImport(AlarmKit)
248
+ #if canImport(AlarmKit)
245
249
  if #available(iOS 26.0, *) { AlarmKitBackend.shared.cancelAll() }
246
- #endif
250
+ #endif
247
251
  AlarmStore.clear()
248
252
  resolve(nil)
249
253
  }
250
-
254
+
251
255
  // MARK: - Control (while ringing)
252
-
256
+
253
257
  @objc(stopAlarm:resolve:reject:)
254
258
  func stopAlarm(_ alarmId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
255
259
  AlarmRingPresenter.dismiss(alarmId)
256
260
  NotificationBackend.shared.stop(alarmId)
257
- #if canImport(AlarmKit)
261
+ #if canImport(AlarmKit)
258
262
  if #available(iOS 26.0, *) { AlarmKitBackend.shared.stop(alarmId) }
259
- #endif
263
+ #endif
260
264
  resolve(nil)
261
265
  }
262
-
266
+
263
267
  @objc(snoozeAlarm:minutes:resolve:reject:)
264
268
  func snoozeAlarm(_ alarmId: String, minutes: Double, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
265
269
  guard let alarm = AlarmStore.get(alarmId) else {
@@ -267,8 +271,8 @@ final class AlarmModule: RCTEventEmitter {
267
271
  }
268
272
  let mins = minutes > 0 ? Int(minutes) : alarm.snoozeMinutes
269
273
  AlarmRingPresenter.dismiss(alarmId)
270
-
271
- #if canImport(AlarmKit)
274
+
275
+ #if canImport(AlarmKit)
272
276
  if #available(iOS 26.0, *), useAlarmKit {
273
277
  Task {
274
278
  do {
@@ -280,36 +284,36 @@ final class AlarmModule: RCTEventEmitter {
280
284
  }
281
285
  return
282
286
  }
283
- #endif
284
-
287
+ #endif
288
+
285
289
  let wasRinging = NotificationBackend.shared.ringing?.id == alarmId
286
290
  NotificationBackend.shared.snooze(alarm, minutes: mins)
287
291
  if wasRinging { emit(Self.eventSnoozed, alarm) }
288
292
  resolve(nil)
289
293
  }
290
-
294
+
291
295
  /// The alarm currently ringing (if any), so JS can show the ring UI on launch.
292
296
  @objc(getRingingAlarm:reject:)
293
297
  func getRingingAlarm(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
294
298
  var ringing = NotificationBackend.shared.ringing
295
- #if canImport(AlarmKit)
299
+ #if canImport(AlarmKit)
296
300
  if #available(iOS 26.0, *), ringing == nil { ringing = AlarmKitBackend.shared.ringingAlarm() }
297
- #endif
301
+ #endif
298
302
  resolve(ringing?.toDictionary())
299
303
  }
300
-
304
+
301
305
  // MARK: - Queries
302
-
306
+
303
307
  @objc(getAlarm:resolve:reject:)
304
308
  func getAlarm(_ alarmId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
305
309
  resolve(AlarmStore.get(alarmId)?.toDictionary())
306
310
  }
307
-
311
+
308
312
  @objc(getAllAlarms:reject:)
309
313
  func getAllAlarms(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
310
314
  resolve(AlarmStore.all().sorted { $0.timestamp < $1.timestamp }.map { $0.toDictionary() })
311
315
  }
312
-
316
+
313
317
  @objc(isScheduled:resolve:reject:)
314
318
  func isScheduled(_ alarmId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
315
319
  let a = AlarmStore.get(alarmId)