react-native-alarms 0.0.0-stage → 0.0.2
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/.bundle/config +2 -0
- package/AlarmService.d.ts +112 -0
- package/AlarmService.ts +196 -0
- package/README.md +385 -2
- package/android/app/build.gradle +119 -0
- package/android/app/debug.keystore +0 -0
- package/android/app/proguard-rules.pro +10 -0
- package/android/app/src/main/AndroidManifest.xml +81 -0
- package/android/app/src/main/java/com/reactnativealarm/MainActivity.kt +29 -0
- package/android/app/src/main/java/com/reactnativealarm/MainApplication.kt +29 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmData.kt +134 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmModule.kt +263 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmPackage.kt +24 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmReceiver.kt +43 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmRingActivity.kt +86 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmScheduler.kt +74 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmService.kt +285 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmStore.kt +13 -0
- package/android/app/src/main/java/com/reactnativealarm/alarm/BootReceiver.kt +29 -0
- package/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/values/strings.xml +3 -0
- package/android/app/src/main/res/values/styles.xml +9 -0
- package/android/build.gradle +21 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +49 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +98 -0
- package/android/settings.gradle +6 -0
- package/app.json +4 -0
- package/index.d.ts +20 -0
- package/index.js +12 -0
- package/index.ts +21 -0
- package/ios/.xcode.env +11 -0
- package/ios/Podfile +34 -0
- package/ios/Podfile.lock +2179 -0
- package/ios/ReactNativeAlarm/AppDelegate.swift +14 -0
- package/ios/ReactNativeAlarm/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/ios/ReactNativeAlarm/Images.xcassets/Contents.json +6 -0
- package/ios/ReactNativeAlarm/Info.plist +79 -0
- package/ios/ReactNativeAlarm/LaunchScreen.storyboard +47 -0
- package/ios/ReactNativeAlarm/PrivacyInfo.xcprivacy +37 -0
- package/ios/ReactNativeAlarm/SceneDelegate.swift +56 -0
- package/ios/ReactNativeAlarm.xcodeproj/project.pbxproj +530 -0
- package/ios/ReactNativeAlarm.xcodeproj/xcshareddata/xcschemes/ReactNativeAlarm.xcscheme +88 -0
- package/ios/ReactNativeAlarm.xcworkspace/contents.xcworkspacedata +10 -0
- package/ios/alarm/AlarmData.swift +97 -0
- package/ios/alarm/AlarmKitBackend.swift +212 -0
- package/ios/alarm/AlarmModule.m +53 -0
- package/ios/alarm/AlarmModule.swift +284 -0
- package/ios/alarm/AlarmStore.swift +51 -0
- package/ios/alarm/NotificationBackend.swift +227 -0
- package/package.json +91 -3
- package/specs/NativeAlarm.ts +95 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UserNotifications
|
|
3
|
+
|
|
4
|
+
/// Fallback backend for devices without AlarmKit (iOS < 26, or AlarmKit denied):
|
|
5
|
+
/// local notifications with Stop / Snooze actions.
|
|
6
|
+
///
|
|
7
|
+
/// Limitations compared with a real alarm: sound plays for at most ~30 s, the
|
|
8
|
+
/// silent switch and Focus modes are respected, and `interval` repeats are
|
|
9
|
+
/// pre-scheduled as a batch of exact one-off notifications (see `schedule`).
|
|
10
|
+
final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
11
|
+
static let shared = NotificationBackend()
|
|
12
|
+
|
|
13
|
+
static let category = "RN_ALARM"
|
|
14
|
+
static let actionSnooze = "RN_ALARM_SNOOZE"
|
|
15
|
+
static let actionStop = "RN_ALARM_STOP"
|
|
16
|
+
private static let prefix = "rnalarm:"
|
|
17
|
+
/// How many `interval` occurrences to pre-schedule (iOS caps pending requests at 64).
|
|
18
|
+
private static let intervalOccurrences = 12
|
|
19
|
+
|
|
20
|
+
private let center = UNUserNotificationCenter.current()
|
|
21
|
+
|
|
22
|
+
/// The alarm whose notification is currently being shown (foreground) or was tapped.
|
|
23
|
+
private(set) var ringing: AlarmData?
|
|
24
|
+
/// Wired by `AlarmModule` so native events reach JS.
|
|
25
|
+
var onEvent: ((String, AlarmData) -> Void)?
|
|
26
|
+
|
|
27
|
+
/// Installs the delegate and actions. Call once at launch so a tap on a
|
|
28
|
+
/// notification that starts the app is delivered to the module.
|
|
29
|
+
static func install() {
|
|
30
|
+
let center = UNUserNotificationCenter.current()
|
|
31
|
+
if center.delegate == nil { center.delegate = shared }
|
|
32
|
+
let snooze = UNNotificationAction(identifier: actionSnooze, title: "Snooze", options: [])
|
|
33
|
+
let stop = UNNotificationAction(identifier: actionStop, title: "Stop", options: [.destructive])
|
|
34
|
+
center.setNotificationCategories([
|
|
35
|
+
UNNotificationCategory(
|
|
36
|
+
identifier: category, actions: [snooze, stop], intentIdentifiers: [],
|
|
37
|
+
options: [.customDismissAction]
|
|
38
|
+
),
|
|
39
|
+
])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// MARK: - Permission
|
|
43
|
+
|
|
44
|
+
/// 'granted' | 'denied' | 'notDetermined', same vocabulary as Android.
|
|
45
|
+
func status(_ completion: @escaping (String) -> Void) {
|
|
46
|
+
center.getNotificationSettings { settings in
|
|
47
|
+
switch settings.authorizationStatus {
|
|
48
|
+
case .authorized, .provisional, .ephemeral: completion("granted")
|
|
49
|
+
case .denied: completion("denied")
|
|
50
|
+
default: completion("notDetermined")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func request(_ completion: @escaping (Bool) -> Void) {
|
|
56
|
+
center.requestAuthorization(options: [.alert, .sound, .badge]) { ok, _ in completion(ok) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: - Scheduling
|
|
60
|
+
|
|
61
|
+
private func ids(for alarmId: String) -> [String] {
|
|
62
|
+
let base = Self.prefix + alarmId
|
|
63
|
+
return [base, base + ":snooze"]
|
|
64
|
+
+ (1...7).map { base + ":wd\($0)" }
|
|
65
|
+
+ (0..<Self.intervalOccurrences).map { base + ":i\($0)" }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private func content(for a: AlarmData) -> UNMutableNotificationContent {
|
|
69
|
+
let c = UNMutableNotificationContent()
|
|
70
|
+
c.title = a.title.isEmpty ? "Alarm" : a.title
|
|
71
|
+
c.body = a.body.isEmpty ? "Alarm is ringing" : a.body
|
|
72
|
+
c.categoryIdentifier = Self.category
|
|
73
|
+
c.userInfo = ["alarmId": a.id]
|
|
74
|
+
c.sound = (a.sound.isEmpty || a.sound == "default")
|
|
75
|
+
? .default
|
|
76
|
+
: UNNotificationSound(named: UNNotificationSoundName(rawValue: a.sound))
|
|
77
|
+
// Breaks through Focus when the Time Sensitive Notifications capability is enabled;
|
|
78
|
+
// silently downgraded to a normal notification otherwise.
|
|
79
|
+
c.interruptionLevel = .timeSensitive
|
|
80
|
+
return c
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private func request(_ id: String, _ a: AlarmData, _ trigger: UNNotificationTrigger) -> UNNotificationRequest {
|
|
84
|
+
UNNotificationRequest(identifier: id, content: content(for: a), trigger: trigger)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func schedule(_ a: AlarmData, completion: @escaping (Error?) -> Void) {
|
|
88
|
+
cancel(a.id)
|
|
89
|
+
let cal = Calendar.current
|
|
90
|
+
let base = Self.prefix + a.id
|
|
91
|
+
let hm = cal.dateComponents([.hour, .minute], from: a.date)
|
|
92
|
+
var requests: [UNNotificationRequest] = []
|
|
93
|
+
|
|
94
|
+
switch a.repeatType {
|
|
95
|
+
case "daily":
|
|
96
|
+
var dc = DateComponents()
|
|
97
|
+
dc.hour = hm.hour
|
|
98
|
+
dc.minute = hm.minute
|
|
99
|
+
requests.append(request(base, a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)))
|
|
100
|
+
|
|
101
|
+
case "weekly":
|
|
102
|
+
let days = a.weekdays.isEmpty ? Array(1...7) : a.weekdays.filter { (1...7).contains($0) }
|
|
103
|
+
for d in days {
|
|
104
|
+
var dc = DateComponents()
|
|
105
|
+
dc.weekday = d
|
|
106
|
+
dc.hour = hm.hour
|
|
107
|
+
dc.minute = hm.minute
|
|
108
|
+
requests.append(request(base + ":wd\(d)", a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "interval":
|
|
112
|
+
// iOS repeating interval triggers count from "now", not from `timestamp`, so
|
|
113
|
+
// pre-schedule the next N exact occurrences instead.
|
|
114
|
+
var t = a.nextTrigger()
|
|
115
|
+
guard t > 0 else { return completion(nil) }
|
|
116
|
+
for k in 0..<Self.intervalOccurrences {
|
|
117
|
+
requests.append(request(base + ":i\(k)", a, exactTrigger(ms: t)))
|
|
118
|
+
t += a.intervalMs
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
let next = a.nextTrigger()
|
|
123
|
+
guard next > 0 else { return completion(nil) } // nothing in the future, same as Android
|
|
124
|
+
requests.append(request(base, a, exactTrigger(ms: next)))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
add(requests, completion: completion)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func exactTrigger(ms: Double) -> UNCalendarNotificationTrigger {
|
|
131
|
+
let dc = Calendar.current.dateComponents(
|
|
132
|
+
[.year, .month, .day, .hour, .minute, .second],
|
|
133
|
+
from: Date(timeIntervalSince1970: ms / 1000)
|
|
134
|
+
)
|
|
135
|
+
return UNCalendarNotificationTrigger(dateMatching: dc, repeats: false)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func add(_ requests: [UNNotificationRequest], completion: @escaping (Error?) -> Void) {
|
|
139
|
+
let group = DispatchGroup()
|
|
140
|
+
var firstError: Error?
|
|
141
|
+
for r in requests {
|
|
142
|
+
group.enter()
|
|
143
|
+
center.add(r) { error in
|
|
144
|
+
if firstError == nil { firstError = error }
|
|
145
|
+
group.leave()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
group.notify(queue: .main) { completion(firstError) }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// One-off re-ring in `minutes`, equivalent to Android's `AlarmScheduler.scheduleSnooze`.
|
|
152
|
+
func snooze(_ a: AlarmData, minutes: Int) {
|
|
153
|
+
center.removeDeliveredNotifications(withIdentifiers: ids(for: a.id))
|
|
154
|
+
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: Double(max(1, minutes)) * 60, repeats: false)
|
|
155
|
+
center.add(request(Self.prefix + a.id + ":snooze", a, trigger))
|
|
156
|
+
if ringing?.id == a.id { ringing = nil }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func cancel(_ id: String) {
|
|
160
|
+
center.removePendingNotificationRequests(withIdentifiers: ids(for: id))
|
|
161
|
+
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
162
|
+
if ringing?.id == id { ringing = nil }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func cancelAll() {
|
|
166
|
+
center.getPendingNotificationRequests { (requests: [UNNotificationRequest]) in
|
|
167
|
+
let ours = requests.map { $0.identifier }.filter { $0.hasPrefix(Self.prefix) }
|
|
168
|
+
self.center.removePendingNotificationRequests(withIdentifiers: ours)
|
|
169
|
+
}
|
|
170
|
+
center.getDeliveredNotifications { (notifications: [UNNotification]) in
|
|
171
|
+
let ours = notifications.map { $0.request.identifier }.filter { $0.hasPrefix(Self.prefix) }
|
|
172
|
+
self.center.removeDeliveredNotifications(withIdentifiers: ours)
|
|
173
|
+
}
|
|
174
|
+
ringing = nil
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Dismiss: clear the shown notification and, like Android, drop a pending snooze.
|
|
178
|
+
func stop(_ id: String) {
|
|
179
|
+
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
180
|
+
center.removePendingNotificationRequests(withIdentifiers: [Self.prefix + id + ":snooze"])
|
|
181
|
+
if let a = ringing, a.id == id {
|
|
182
|
+
ringing = nil
|
|
183
|
+
onEvent?(AlarmModule.eventDismissed, a)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// MARK: - UNUserNotificationCenterDelegate
|
|
188
|
+
|
|
189
|
+
private func alarm(from notification: UNNotification) -> AlarmData? {
|
|
190
|
+
guard let id = notification.request.content.userInfo["alarmId"] as? String else { return nil }
|
|
191
|
+
return AlarmStore.get(id)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func userNotificationCenter(
|
|
195
|
+
_ center: UNUserNotificationCenter,
|
|
196
|
+
willPresent notification: UNNotification,
|
|
197
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
198
|
+
) {
|
|
199
|
+
if let a = alarm(from: notification) {
|
|
200
|
+
ringing = a
|
|
201
|
+
onEvent?(AlarmModule.eventFired, a)
|
|
202
|
+
}
|
|
203
|
+
completionHandler([.banner, .list, .sound])
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func userNotificationCenter(
|
|
207
|
+
_ center: UNUserNotificationCenter,
|
|
208
|
+
didReceive response: UNNotificationResponse,
|
|
209
|
+
withCompletionHandler completionHandler: @escaping () -> Void
|
|
210
|
+
) {
|
|
211
|
+
defer { completionHandler() }
|
|
212
|
+
guard let a = alarm(from: response.notification) else { return }
|
|
213
|
+
switch response.actionIdentifier {
|
|
214
|
+
case Self.actionSnooze:
|
|
215
|
+
snooze(a, minutes: a.snoozeMinutes)
|
|
216
|
+
onEvent?(AlarmModule.eventSnoozed, a)
|
|
217
|
+
case UNNotificationDefaultActionIdentifier:
|
|
218
|
+
// Tapped: the app is now open; let JS show its ring UI (getRingingAlarm / onAlarmFired).
|
|
219
|
+
ringing = a
|
|
220
|
+
onEvent?(AlarmModule.eventFired, a)
|
|
221
|
+
default: // Stop action or swiped away
|
|
222
|
+
ringing = nil
|
|
223
|
+
center.removePendingNotificationRequests(withIdentifiers: [Self.prefix + a.id + ":snooze"])
|
|
224
|
+
onEvent?(AlarmModule.eventDismissed, a)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-alarms",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A powerful, cross-platform alarm management library for React Native",
|
|
5
|
+
"private": false,
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./service": {
|
|
14
|
+
"types": "./AlarmService.d.ts",
|
|
15
|
+
"default": "./AlarmService.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"android": "react-native run-android",
|
|
20
|
+
"ios": "react-native run-ios",
|
|
21
|
+
"lint": "eslint .",
|
|
22
|
+
"start": "react-native start",
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"prepare": "npm run build",
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"clean": "rm -rf dist types *.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/surajprasad13/react-native-alarms"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"react-native",
|
|
34
|
+
"alarm",
|
|
35
|
+
"notification",
|
|
36
|
+
"schedule",
|
|
37
|
+
"cross-platform",
|
|
38
|
+
"ios",
|
|
39
|
+
"android"
|
|
40
|
+
],
|
|
41
|
+
"author": {
|
|
42
|
+
"name": "Suraj Prasad",
|
|
43
|
+
"email": "surajvishwakarma830@gmail.com",
|
|
44
|
+
"url": "https://github.com/surajprasad13"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=18.0.0",
|
|
49
|
+
"react-native": ">=0.70.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"react": "19.3.0",
|
|
53
|
+
"react-native": "0.88.0",
|
|
54
|
+
"@react-native/new-app-screen": "0.88.0",
|
|
55
|
+
"react-native-safe-area-context": "^5.5.2"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@babel/core": "^7.25.2",
|
|
59
|
+
"@babel/preset-env": "^7.25.3",
|
|
60
|
+
"@babel/runtime": "^7.25.0",
|
|
61
|
+
"@react-native-community/cli": "20.2.0",
|
|
62
|
+
"@react-native-community/cli-platform-android": "20.2.0",
|
|
63
|
+
"@react-native-community/cli-platform-ios": "20.2.0",
|
|
64
|
+
"@react-native/babel-preset": "0.88.0",
|
|
65
|
+
"@react-native/eslint-config": "0.88.0",
|
|
66
|
+
"@react-native/jest-preset": "0.88.0",
|
|
67
|
+
"@react-native/metro-config": "0.88.0",
|
|
68
|
+
"@react-native/typescript-config": "0.88.0",
|
|
69
|
+
"@types/jest": "^29.5.13",
|
|
70
|
+
"@types/react": "^19.2.0",
|
|
71
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
72
|
+
"eslint": "^8.19.0",
|
|
73
|
+
"jest": "^29.6.3",
|
|
74
|
+
"prettier": "2.8.8",
|
|
75
|
+
"react-test-renderer": "19.3.0",
|
|
76
|
+
"typescript": "^6.0.3"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">= 22.11.0"
|
|
80
|
+
},
|
|
81
|
+
"codegenConfig": {
|
|
82
|
+
"name": "RNAlarmSpec",
|
|
83
|
+
"type": "modules",
|
|
84
|
+
"jsSrcsDir": "specs",
|
|
85
|
+
"android": {
|
|
86
|
+
"javaPackageName": "com.reactnativealarm.alarm"
|
|
87
|
+
},
|
|
88
|
+
"ios": {
|
|
89
|
+
"modulesProvider": {
|
|
90
|
+
"Alarm": "Alarm"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
6
94
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared contract of the native `Alarm` module.
|
|
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
|
|
11
|
+
*/
|
|
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
|
+
export interface Spec extends TurboModule {
|
|
67
|
+
// ---------- Permissions ----------
|
|
68
|
+
checkPermission(): Promise<AlarmPermission>;
|
|
69
|
+
requestPermission(): Promise<AlarmPermission>;
|
|
70
|
+
canScheduleExactAlarms(): Promise<boolean>;
|
|
71
|
+
/** Opens the vendor Autostart screen on Android; resolves false on iOS. */
|
|
72
|
+
openAutoStartSettings(): Promise<boolean>;
|
|
73
|
+
|
|
74
|
+
// ---------- Scheduling ----------
|
|
75
|
+
scheduleAlarm(config: AlarmConfig): Promise<string>;
|
|
76
|
+
updateAlarm(alarmId: string, config: AlarmConfig): Promise<boolean>;
|
|
77
|
+
cancelAlarm(alarmId: string): Promise<boolean>;
|
|
78
|
+
cancelAllAlarms(): Promise<void>;
|
|
79
|
+
|
|
80
|
+
// ---------- Control (while ringing) ----------
|
|
81
|
+
stopAlarm(alarmId: string): Promise<void>;
|
|
82
|
+
snoozeAlarm(alarmId: string, minutes: number): Promise<void>;
|
|
83
|
+
getRingingAlarm(): Promise<Alarm | null>;
|
|
84
|
+
|
|
85
|
+
// ---------- Queries ----------
|
|
86
|
+
getAlarm(alarmId: string): Promise<Alarm | null>;
|
|
87
|
+
getAllAlarms(): Promise<Alarm[]>;
|
|
88
|
+
isScheduled(alarmId: string): Promise<boolean>;
|
|
89
|
+
|
|
90
|
+
// ---------- Events (used by NativeEventEmitter) ----------
|
|
91
|
+
addListener(eventName: string): void;
|
|
92
|
+
removeListeners(count: number): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('Alarm');
|