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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import UserNotifications
|
|
3
|
+
import UniformTypeIdentifiers
|
|
3
4
|
|
|
4
5
|
/// Fallback backend for devices without AlarmKit (iOS < 26, or AlarmKit denied):
|
|
5
6
|
/// local notifications with Stop / Snooze actions.
|
|
@@ -10,25 +11,25 @@ import UserNotifications
|
|
|
10
11
|
@objc(RNAlarmNotificationBackend)
|
|
11
12
|
final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
12
13
|
static let shared = NotificationBackend()
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
static let category = "RN_ALARM"
|
|
15
16
|
static let actionSnooze = "RN_ALARM_SNOOZE"
|
|
16
17
|
static let actionStop = "RN_ALARM_STOP"
|
|
17
18
|
private static let prefix = "rnalarm:"
|
|
18
19
|
/// How many `interval` occurrences to pre-schedule (iOS caps pending requests at 64).
|
|
19
20
|
private static let intervalOccurrences = 12
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
private let center = UNUserNotificationCenter.current()
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
/// The alarm whose notification is currently being shown (foreground) or was tapped.
|
|
24
25
|
private(set) var ringing: AlarmData?
|
|
25
26
|
/// Wired by `AlarmModule` so native events reach JS.
|
|
26
27
|
var onEvent: ((String, AlarmData) -> Void)?
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
/// Delegate that was installed before us (e.g. Notifee); non-alarm notifications go to it.
|
|
29
30
|
private weak var previousDelegate: UNUserNotificationCenterDelegate?
|
|
30
31
|
private static var installed = false
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
/// Installs the delegate and actions. Call once at launch so a tap on a
|
|
33
34
|
/// notification that starts the app is delivered to the module.
|
|
34
35
|
@objc static func install() {
|
|
@@ -51,9 +52,9 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
51
52
|
),
|
|
52
53
|
])
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
// MARK: - Permission
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
/// 'granted' | 'denied' | 'notDetermined', same vocabulary as Android.
|
|
58
59
|
func status(_ completion: @escaping (String) -> Void) {
|
|
59
60
|
center.getNotificationSettings { settings in
|
|
@@ -64,82 +65,128 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
func request(_ completion: @escaping (Bool) -> Void) {
|
|
69
70
|
center.requestAuthorization(options: [.alert, .sound, .badge]) { ok, _ in completion(ok) }
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
// MARK: - Scheduling
|
|
73
|
-
|
|
74
|
+
|
|
74
75
|
private func ids(for alarmId: String) -> [String] {
|
|
75
76
|
let base = Self.prefix + alarmId
|
|
76
77
|
return [base, base + ":snooze"]
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
+ (1...7).map { base + ":wd\($0)" }
|
|
79
|
+
+ (0..<Self.intervalOccurrences).map { base + ":i\($0)" }
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
-
private func content(for a: AlarmData) -> UNMutableNotificationContent {
|
|
81
|
+
|
|
82
|
+
private func content(for a: AlarmData, attachment: UNNotificationAttachment? = nil) -> UNMutableNotificationContent {
|
|
82
83
|
let c = UNMutableNotificationContent()
|
|
83
84
|
c.title = a.title.isEmpty ? "Alarm" : a.title
|
|
84
85
|
c.body = a.body.isEmpty ? "Alarm is ringing" : a.body
|
|
85
86
|
c.categoryIdentifier = Self.category
|
|
86
|
-
c.userInfo = ["alarmId": a.id]
|
|
87
|
+
c.userInfo = ["alarmId": a.id, "image": a.image ?? ""]
|
|
88
|
+
if let attachment { c.attachments = [attachment] }
|
|
87
89
|
c.sound = (a.sound.isEmpty || a.sound == "default")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
? .default
|
|
91
|
+
: UNNotificationSound(named: UNNotificationSoundName(rawValue: a.sound))
|
|
90
92
|
// Breaks through Focus when the Time Sensitive Notifications capability is enabled;
|
|
91
93
|
// silently downgraded to a normal notification otherwise.
|
|
92
94
|
c.interruptionLevel = .timeSensitive
|
|
93
95
|
return c
|
|
94
96
|
}
|
|
95
|
-
|
|
96
|
-
private func request(_ id: String, _ a: AlarmData, _ trigger: UNNotificationTrigger
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
private func request(_ id: String, _ a: AlarmData, _ trigger: UNNotificationTrigger,
|
|
99
|
+
_ attachment: UNNotificationAttachment? = nil) -> UNNotificationRequest {
|
|
100
|
+
UNNotificationRequest(identifier: id, content: content(for: a, attachment: attachment), trigger: trigger)
|
|
98
101
|
}
|
|
99
|
-
|
|
102
|
+
|
|
100
103
|
func schedule(_ a: AlarmData, completion: @escaping (Error?) -> Void) {
|
|
101
104
|
cancel(a.id)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
prepareAttachment(for: a.image) { attachment in
|
|
106
|
+
let cal = Calendar.current
|
|
107
|
+
let base = Self.prefix + a.id
|
|
108
|
+
let hm = cal.dateComponents([.hour, .minute], from: a.date)
|
|
109
|
+
var requests: [UNNotificationRequest] = []
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
var dc = DateComponents()
|
|
110
|
-
dc.hour = hm.hour
|
|
111
|
-
dc.minute = hm.minute
|
|
112
|
-
requests.append(request(base, a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)))
|
|
113
|
-
|
|
114
|
-
case "weekly":
|
|
115
|
-
let days = a.weekdays.isEmpty ? Array(1...7) : a.weekdays.filter { (1...7).contains($0) }
|
|
116
|
-
for d in days {
|
|
111
|
+
switch a.repeatType {
|
|
112
|
+
case "daily":
|
|
117
113
|
var dc = DateComponents()
|
|
118
|
-
dc.weekday = d
|
|
119
114
|
dc.hour = hm.hour
|
|
120
115
|
dc.minute = hm.minute
|
|
121
|
-
requests.append(request(base
|
|
122
|
-
}
|
|
116
|
+
requests.append(self.request(base, a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true), attachment))
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
case "weekly":
|
|
119
|
+
let days = a.weekdays.isEmpty ? Array(1...7) : a.weekdays.filter { (1...7).contains($0) }
|
|
120
|
+
for d in days {
|
|
121
|
+
var dc = DateComponents()
|
|
122
|
+
dc.weekday = d
|
|
123
|
+
dc.hour = hm.hour
|
|
124
|
+
dc.minute = hm.minute
|
|
125
|
+
requests.append(self.request(base + ":wd\(d)", a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true), attachment))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "interval":
|
|
129
|
+
// iOS repeating interval triggers count from "now", not from `timestamp`, so
|
|
130
|
+
// pre-schedule the next N exact occurrences instead.
|
|
131
|
+
var t = a.nextTrigger()
|
|
132
|
+
guard t > 0 else { return completion(nil) }
|
|
133
|
+
for k in 0..<Self.intervalOccurrences {
|
|
134
|
+
requests.append(self.request(base + ":i\(k)", a, self.exactTrigger(ms: t), attachment))
|
|
135
|
+
t += a.intervalMs
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
let next = a.nextTrigger()
|
|
140
|
+
guard next > 0 else { return completion(nil) } // nothing in the future, same as Android
|
|
141
|
+
requests.append(self.request(base, a, self.exactTrigger(ms: next), attachment))
|
|
132
142
|
}
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
let next = a.nextTrigger()
|
|
136
|
-
guard next > 0 else { return completion(nil) } // nothing in the future, same as Android
|
|
137
|
-
requests.append(request(base, a, exactTrigger(ms: next)))
|
|
144
|
+
self.add(requests, completion: completion)
|
|
138
145
|
}
|
|
139
|
-
|
|
140
|
-
add(requests, completion: completion)
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
private func prepareAttachment(for image: String?, completion: @escaping (UNNotificationAttachment?) -> Void) {
|
|
149
|
+
guard let image, !image.isEmpty else { return completion(nil) }
|
|
150
|
+
guard let url = URL(string: image) else {
|
|
151
|
+
NSLog("[react-native-alarms] Invalid image URI: %@", image)
|
|
152
|
+
return completion(nil)
|
|
153
|
+
}
|
|
154
|
+
if url.isFileURL {
|
|
155
|
+
do {
|
|
156
|
+
completion(try UNNotificationAttachment(identifier: "alarm-image", url: url, options: nil))
|
|
157
|
+
} catch {
|
|
158
|
+
NSLog("[react-native-alarms] Could not attach alarm image: %@", error.localizedDescription)
|
|
159
|
+
completion(nil)
|
|
160
|
+
}
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
guard url.scheme?.lowercased() == "https" else {
|
|
164
|
+
NSLog("[react-native-alarms] Alarm image URI must use file:// or https://: %@", image)
|
|
165
|
+
return completion(nil)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
URLSession.shared.downloadTask(with: url) { temporaryURL, response, error in
|
|
169
|
+
guard let temporaryURL, error == nil else {
|
|
170
|
+
NSLog("[react-native-alarms] Could not download alarm image: %@", error?.localizedDescription ?? "empty response")
|
|
171
|
+
return completion(nil)
|
|
172
|
+
}
|
|
173
|
+
let mimeType = response?.mimeType
|
|
174
|
+
let ext = url.pathExtension.isEmpty
|
|
175
|
+
? (mimeType.flatMap { UTType(mimeType: $0)?.preferredFilenameExtension } ?? "jpg")
|
|
176
|
+
: url.pathExtension
|
|
177
|
+
let destination = FileManager.default.temporaryDirectory
|
|
178
|
+
.appendingPathComponent(UUID().uuidString)
|
|
179
|
+
.appendingPathExtension(ext)
|
|
180
|
+
do {
|
|
181
|
+
try FileManager.default.moveItem(at: temporaryURL, to: destination)
|
|
182
|
+
completion(try UNNotificationAttachment(identifier: "alarm-image", url: destination, options: nil))
|
|
183
|
+
} catch {
|
|
184
|
+
NSLog("[react-native-alarms] Could not attach downloaded alarm image: %@", error.localizedDescription)
|
|
185
|
+
completion(nil)
|
|
186
|
+
}
|
|
187
|
+
}.resume()
|
|
188
|
+
}
|
|
189
|
+
|
|
143
190
|
private func exactTrigger(ms: Double) -> UNCalendarNotificationTrigger {
|
|
144
191
|
let dc = Calendar.current.dateComponents(
|
|
145
192
|
[.year, .month, .day, .hour, .minute, .second],
|
|
@@ -147,7 +194,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
147
194
|
)
|
|
148
195
|
return UNCalendarNotificationTrigger(dateMatching: dc, repeats: false)
|
|
149
196
|
}
|
|
150
|
-
|
|
197
|
+
|
|
151
198
|
private func add(_ requests: [UNNotificationRequest], completion: @escaping (Error?) -> Void) {
|
|
152
199
|
let group = DispatchGroup()
|
|
153
200
|
var firstError: Error?
|
|
@@ -160,27 +207,29 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
160
207
|
}
|
|
161
208
|
group.notify(queue: .main) { completion(firstError) }
|
|
162
209
|
}
|
|
163
|
-
|
|
210
|
+
|
|
164
211
|
/// One-off re-ring in `minutes`, equivalent to Android's `AlarmScheduler.scheduleSnooze`.
|
|
165
212
|
func snooze(_ a: AlarmData, minutes: Int) {
|
|
166
213
|
center.removeDeliveredNotifications(withIdentifiers: ids(for: a.id))
|
|
167
214
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: Double(max(1, minutes)) * 60, repeats: false)
|
|
168
|
-
|
|
169
|
-
|
|
215
|
+
prepareAttachment(for: a.image) { attachment in
|
|
216
|
+
self.center.add(self.request(Self.prefix + a.id + ":snooze", a, trigger, attachment))
|
|
217
|
+
if self.ringing?.id == a.id { self.ringing = nil }
|
|
218
|
+
}
|
|
170
219
|
}
|
|
171
|
-
|
|
220
|
+
|
|
172
221
|
/// Stops the notification's sound (by clearing the delivered notification) while the
|
|
173
222
|
/// in-app ring screen takes over; `ringing` stays set so the session is still live.
|
|
174
223
|
func silence(_ id: String) {
|
|
175
224
|
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
176
225
|
}
|
|
177
|
-
|
|
226
|
+
|
|
178
227
|
func cancel(_ id: String) {
|
|
179
228
|
center.removePendingNotificationRequests(withIdentifiers: ids(for: id))
|
|
180
229
|
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
181
230
|
if ringing?.id == id { ringing = nil }
|
|
182
231
|
}
|
|
183
|
-
|
|
232
|
+
|
|
184
233
|
func cancelAll() {
|
|
185
234
|
center.getPendingNotificationRequests { (requests: [UNNotificationRequest]) in
|
|
186
235
|
let ours = requests.map { $0.identifier }.filter { $0.hasPrefix(Self.prefix) }
|
|
@@ -192,7 +241,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
192
241
|
}
|
|
193
242
|
ringing = nil
|
|
194
243
|
}
|
|
195
|
-
|
|
244
|
+
|
|
196
245
|
/// Dismiss: clear the shown notification and, like Android, drop a pending snooze.
|
|
197
246
|
func stop(_ id: String) {
|
|
198
247
|
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
@@ -202,14 +251,14 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
202
251
|
onEvent?(AlarmModule.eventDismissed, a)
|
|
203
252
|
}
|
|
204
253
|
}
|
|
205
|
-
|
|
254
|
+
|
|
206
255
|
// MARK: - UNUserNotificationCenterDelegate
|
|
207
|
-
|
|
256
|
+
|
|
208
257
|
private func alarm(from notification: UNNotification) -> AlarmData? {
|
|
209
258
|
guard let id = notification.request.content.userInfo["alarmId"] as? String else { return nil }
|
|
210
259
|
return AlarmStore.get(id)
|
|
211
260
|
}
|
|
212
|
-
|
|
261
|
+
|
|
213
262
|
func userNotificationCenter(
|
|
214
263
|
_ center: UNUserNotificationCenter,
|
|
215
264
|
willPresent notification: UNNotification,
|
|
@@ -229,7 +278,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
229
278
|
// The ring screen takes over; keep the sound but skip the banner when it can show.
|
|
230
279
|
completionHandler(onEvent == nil ? [.banner, .list, .sound] : [.list, .sound])
|
|
231
280
|
}
|
|
232
|
-
|
|
281
|
+
|
|
233
282
|
func userNotificationCenter(
|
|
234
283
|
_ center: UNUserNotificationCenter,
|
|
235
284
|
didReceive response: UNNotificationResponse,
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-alarms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "A powerful, cross-platform alarm management library for React Native",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "index.ts",
|
|
7
|
-
"types": "index.
|
|
7
|
+
"types": "index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./index.
|
|
10
|
+
"types": "./index.ts",
|
|
11
11
|
"default": "./index.ts"
|
|
12
12
|
},
|
|
13
13
|
"./service": {
|
|
14
|
-
"types": "./AlarmService.
|
|
14
|
+
"types": "./AlarmService.ts",
|
|
15
15
|
"default": "./AlarmService.ts"
|
|
16
16
|
},
|
|
17
17
|
"./package.json": "./package.json"
|
package/specs/NativeAlarm.ts
CHANGED
|
@@ -1,109 +1,162 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
2
|
import { NativeModules, Platform, TurboModuleRegistry } from 'react-native';
|
|
3
|
+
import type { AlarmPermission, AlarmConfig, AlarmProp } from '../src/types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Both platforms register a legacy-style module named `Alarm` with these exact
|
|
8
|
-
* method names (Android: AlarmModule.kt, iOS: ios/alarm/AlarmModule.swift) and
|
|
9
|
-
* emit the same events through `NativeEventEmitter`:
|
|
10
|
-
* onAlarmFired | onAlarmDismissed | onAlarmSnoozed -> AlarmEvent
|
|
6
|
+
* Native Alarm module interface.
|
|
11
7
|
*/
|
|
12
|
-
|
|
13
|
-
/** type: 'none' | 'daily' | 'weekly' | 'interval' */
|
|
14
|
-
export type AlarmRepeat = {
|
|
15
|
-
type: string;
|
|
16
|
-
weekdays?: number[]; // 1 = Sunday ... 7 = Saturday (weekly)
|
|
17
|
-
intervalMs?: number; // interval
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type AlarmConfig = {
|
|
21
|
-
alarmId?: string;
|
|
22
|
-
timestamp: number; // ms since epoch
|
|
23
|
-
title: string;
|
|
24
|
-
body?: string;
|
|
25
|
-
sound?: string; // 'default' or a bundled sound file name
|
|
26
|
-
vibrate?: boolean; // Android only (iOS alarms always vibrate per system settings)
|
|
27
|
-
snoozeEnabled?: boolean;
|
|
28
|
-
snoozeMinutes?: number;
|
|
29
|
-
repeat?: AlarmRepeat;
|
|
30
|
-
data?: string; // JSON string payload
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type Alarm = {
|
|
34
|
-
alarmId: string;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
title: string;
|
|
37
|
-
body: string;
|
|
38
|
-
sound: string;
|
|
39
|
-
vibrate: boolean;
|
|
40
|
-
snoozeEnabled: boolean;
|
|
41
|
-
snoozeMinutes: number;
|
|
42
|
-
repeat: AlarmRepeat;
|
|
43
|
-
data: string;
|
|
44
|
-
enabled: boolean;
|
|
45
|
-
nextTriggerAt: number; // 0 if none
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** status: 'granted' | 'denied' | 'notDetermined' */
|
|
49
|
-
export type AlarmPermission = {
|
|
50
|
-
status: string;
|
|
51
|
-
notifications: boolean;
|
|
52
|
-
canScheduleExact: boolean;
|
|
53
|
-
canDrawOverlays: boolean; // Android only; always true on iOS
|
|
54
|
-
canUseFullScreenIntent: boolean; // Android only; always true on iOS
|
|
55
|
-
backend?: string; // iOS only: 'alarmkit' (iOS 26+) | 'notifications'
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export type AlarmEvent = {
|
|
59
|
-
alarmId: string;
|
|
60
|
-
timestamp: number;
|
|
61
|
-
title: string;
|
|
62
|
-
body: string;
|
|
63
|
-
data: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
8
|
export interface Spec extends TurboModule {
|
|
67
|
-
//
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Permissions
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the current permission status.
|
|
15
|
+
*/
|
|
68
16
|
checkPermission(): Promise<AlarmPermission>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Requests any required permissions.
|
|
20
|
+
*/
|
|
69
21
|
requestPermission(): Promise<AlarmPermission>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Checks whether exact alarms can be scheduled.
|
|
25
|
+
*
|
|
26
|
+
* Android only.
|
|
27
|
+
*/
|
|
70
28
|
canScheduleExactAlarms(): Promise<boolean>;
|
|
71
|
-
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Opens the device's Autostart settings screen.
|
|
32
|
+
*
|
|
33
|
+
* Android:
|
|
34
|
+
* Opens the vendor-specific Autostart page when available.
|
|
35
|
+
*
|
|
36
|
+
* iOS:
|
|
37
|
+
* Resolves `false`.
|
|
38
|
+
*/
|
|
72
39
|
openAutoStartSettings(): Promise<boolean>;
|
|
73
40
|
|
|
74
|
-
//
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Scheduling
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Schedules a new alarm.
|
|
47
|
+
*
|
|
48
|
+
* Returns the generated or provided alarm ID.
|
|
49
|
+
*/
|
|
75
50
|
scheduleAlarm(config: AlarmConfig): Promise<string>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Updates an existing alarm.
|
|
54
|
+
*
|
|
55
|
+
* Returns `true` if the update succeeds.
|
|
56
|
+
*/
|
|
76
57
|
updateAlarm(alarmId: string, config: AlarmConfig): Promise<boolean>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Cancels a specific alarm.
|
|
61
|
+
*/
|
|
77
62
|
cancelAlarm(alarmId: string): Promise<boolean>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cancels every scheduled alarm.
|
|
66
|
+
*/
|
|
78
67
|
cancelAllAlarms(): Promise<void>;
|
|
79
68
|
|
|
80
|
-
//
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Ringing Controls
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Stops the currently ringing alarm.
|
|
75
|
+
*/
|
|
81
76
|
stopAlarm(alarmId: string): Promise<void>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Snoozes the currently ringing alarm.
|
|
80
|
+
*
|
|
81
|
+
* @param alarmId Alarm identifier.
|
|
82
|
+
* @param minutes Snooze duration in minutes.
|
|
83
|
+
*/
|
|
82
84
|
snoozeAlarm(alarmId: string, minutes: number): Promise<void>;
|
|
83
|
-
getRingingAlarm(): Promise<Alarm | null>;
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Returns the currently ringing alarm, if one exists.
|
|
88
|
+
*/
|
|
89
|
+
getRingingAlarm(): Promise<AlarmProp | null>;
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Queries
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns a scheduled alarm by ID.
|
|
97
|
+
*/
|
|
98
|
+
getAlarm(alarmId: string): Promise<AlarmProp | null>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns every scheduled alarm.
|
|
102
|
+
*/
|
|
103
|
+
getAllAlarms(): Promise<AlarmProp[]>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks whether an alarm is scheduled.
|
|
107
|
+
*/
|
|
88
108
|
isScheduled(alarmId: string): Promise<boolean>;
|
|
89
109
|
|
|
90
|
-
//
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// NativeEventEmitter Support
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Internal listener registration.
|
|
116
|
+
*/
|
|
91
117
|
addListener(eventName: string): void;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Internal listener cleanup.
|
|
121
|
+
*/
|
|
92
122
|
removeListeners(count: number): void;
|
|
93
123
|
}
|
|
94
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Error shown when the native module is unavailable.
|
|
127
|
+
*/
|
|
95
128
|
const LINKING_ERROR =
|
|
96
129
|
"react-native-alarms: the native 'Alarm' module is not linked. " +
|
|
97
130
|
Platform.select({
|
|
98
131
|
ios: "Run 'pod install' in the ios folder, then rebuild the app.",
|
|
99
|
-
default:
|
|
132
|
+
default:
|
|
133
|
+
'Rebuild the app after installing the package. Expo Go is unsupported; use a development build.',
|
|
100
134
|
});
|
|
101
135
|
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
export function getNativeAlarmModule(): Spec | null {
|
|
137
|
+
return TurboModuleRegistry.get<Spec>('Alarm') ?? NativeModules.Alarm ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cross-platform native Alarm module.
|
|
142
|
+
*
|
|
143
|
+
* Supports both the legacy Native Module system and the New Architecture
|
|
144
|
+
* (TurboModules). The module is resolved lazily so importing it does not
|
|
145
|
+
* immediately throw if native linking is missing.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import Alarm from 'react-native-alarms';
|
|
150
|
+
*
|
|
151
|
+
* const id = await Alarm.scheduleAlarm({
|
|
152
|
+
* timestamp: Date.now() + 60000,
|
|
153
|
+
* title: 'Meeting',
|
|
154
|
+
* body: 'Standup starts now',
|
|
155
|
+
* });
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
104
158
|
const Alarm: Spec =
|
|
105
|
-
|
|
106
|
-
NativeModules.Alarm ??
|
|
159
|
+
getNativeAlarmModule() ??
|
|
107
160
|
new Proxy({} as Spec, {
|
|
108
161
|
get() {
|
|
109
162
|
throw new Error(LINKING_ERROR);
|