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.
@@ -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
- Alarm,
11
+ AlarmProp,
12
12
  AlarmPermission,
13
13
  AlarmEvent,
14
- Spec as AlarmModuleSpec,
15
- } from './specs/NativeAlarm';
14
+ AlarmEventMap,
15
+ AlarmEventName,
16
+ } from './src/types';
16
17
 
17
18
  export { AlarmService } from './AlarmService';
18
19
 
19
- import NativeAlarm from './specs/NativeAlarm';
20
- export default NativeAlarm;
20
+ import Alarm from './specs/NativeAlarm';
21
+
22
+ export default Alarm;
@@ -1 +1 @@
1
- export NODE_BINARY=/opt/homebrew/Cellar/node/26.10.0_1/bin/node
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
- ? 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
  }