react-native-alarms 0.0.3 → 0.0.5
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/README.md +27 -1
- package/android-lib/build.gradle +36 -0
- package/android-lib/src/main/AndroidManifest.xml +49 -0
- package/android-lib/src/main/java/com/reactnativealarms/RNAlarmsPackage.kt +18 -0
- package/android-lib/src/main/res/values/styles.xml +3 -0
- package/index.d.ts +6 -2
- package/index.ts +2 -1
- package/ios/ReactNativeAlarm.xcodeproj/project.pbxproj +4 -0
- package/ios/alarm/AlarmKitBackend.swift +94 -27
- package/ios/alarm/AlarmModule.m +25 -0
- package/ios/alarm/AlarmModule.swift +36 -2
- package/ios/alarm/AlarmRingPresenter.swift +126 -0
- package/ios/alarm/NotificationBackend.swift +42 -7
- package/package.json +7 -20
- package/react-native-alarms.podspec +20 -0
- package/react-native.config.js +16 -0
- package/specs/NativeAlarm.ts +20 -2
- package/app.json +0 -4
- package/index.js +0 -12
- package/ios/Podfile.lock +0 -2179
|
@@ -7,6 +7,7 @@ import UserNotifications
|
|
|
7
7
|
/// Limitations compared with a real alarm: sound plays for at most ~30 s, the
|
|
8
8
|
/// silent switch and Focus modes are respected, and `interval` repeats are
|
|
9
9
|
/// pre-scheduled as a batch of exact one-off notifications (see `schedule`).
|
|
10
|
+
@objc(RNAlarmNotificationBackend)
|
|
10
11
|
final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
11
12
|
static let shared = NotificationBackend()
|
|
12
13
|
|
|
@@ -24,11 +25,23 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
24
25
|
/// Wired by `AlarmModule` so native events reach JS.
|
|
25
26
|
var onEvent: ((String, AlarmData) -> Void)?
|
|
26
27
|
|
|
28
|
+
/// Delegate that was installed before us (e.g. Notifee); non-alarm notifications go to it.
|
|
29
|
+
private weak var previousDelegate: UNUserNotificationCenterDelegate?
|
|
30
|
+
private static var installed = false
|
|
31
|
+
|
|
27
32
|
/// Installs the delegate and actions. Call once at launch so a tap on a
|
|
28
33
|
/// notification that starts the app is delivered to the module.
|
|
29
|
-
static func install() {
|
|
34
|
+
@objc static func install() {
|
|
30
35
|
let center = UNUserNotificationCenter.current()
|
|
31
|
-
|
|
36
|
+
// Only once: a library that took over after us forwards to us, and taking the
|
|
37
|
+
// delegate back would make the two forward to each other forever.
|
|
38
|
+
if !installed {
|
|
39
|
+
installed = true
|
|
40
|
+
if center.delegate !== shared {
|
|
41
|
+
shared.previousDelegate = center.delegate
|
|
42
|
+
center.delegate = shared
|
|
43
|
+
}
|
|
44
|
+
}
|
|
32
45
|
let snooze = UNNotificationAction(identifier: actionSnooze, title: "Snooze", options: [])
|
|
33
46
|
let stop = UNNotificationAction(identifier: actionStop, title: "Stop", options: [.destructive])
|
|
34
47
|
center.setNotificationCategories([
|
|
@@ -156,6 +169,12 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
156
169
|
if ringing?.id == a.id { ringing = nil }
|
|
157
170
|
}
|
|
158
171
|
|
|
172
|
+
/// Stops the notification's sound (by clearing the delivered notification) while the
|
|
173
|
+
/// in-app ring screen takes over; `ringing` stays set so the session is still live.
|
|
174
|
+
func silence(_ id: String) {
|
|
175
|
+
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
176
|
+
}
|
|
177
|
+
|
|
159
178
|
func cancel(_ id: String) {
|
|
160
179
|
center.removePendingNotificationRequests(withIdentifiers: ids(for: id))
|
|
161
180
|
center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
|
|
@@ -196,11 +215,19 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
196
215
|
willPresent notification: UNNotification,
|
|
197
216
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
198
217
|
) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
218
|
+
guard let a = alarm(from: notification) else {
|
|
219
|
+
if let previous = previousDelegate,
|
|
220
|
+
previous.responds(to: #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:willPresent:withCompletionHandler:))) {
|
|
221
|
+
previous.userNotificationCenter?(center, willPresent: notification, withCompletionHandler: completionHandler)
|
|
222
|
+
} else {
|
|
223
|
+
completionHandler([.banner, .list, .sound])
|
|
224
|
+
}
|
|
225
|
+
return
|
|
202
226
|
}
|
|
203
|
-
|
|
227
|
+
ringing = a
|
|
228
|
+
onEvent?(AlarmModule.eventFired, a)
|
|
229
|
+
// The ring screen takes over; keep the sound but skip the banner when it can show.
|
|
230
|
+
completionHandler(onEvent == nil ? [.banner, .list, .sound] : [.list, .sound])
|
|
204
231
|
}
|
|
205
232
|
|
|
206
233
|
func userNotificationCenter(
|
|
@@ -208,8 +235,16 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
|
|
|
208
235
|
didReceive response: UNNotificationResponse,
|
|
209
236
|
withCompletionHandler completionHandler: @escaping () -> Void
|
|
210
237
|
) {
|
|
238
|
+
guard let a = alarm(from: response.notification) else {
|
|
239
|
+
if let previous = previousDelegate,
|
|
240
|
+
previous.responds(to: #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:didReceive:withCompletionHandler:))) {
|
|
241
|
+
previous.userNotificationCenter?(center, didReceive: response, withCompletionHandler: completionHandler)
|
|
242
|
+
} else {
|
|
243
|
+
completionHandler()
|
|
244
|
+
}
|
|
245
|
+
return
|
|
246
|
+
}
|
|
211
247
|
defer { completionHandler() }
|
|
212
|
-
guard let a = alarm(from: response.notification) else { return }
|
|
213
248
|
switch response.actionIdentifier {
|
|
214
249
|
case Self.actionSnooze:
|
|
215
250
|
snooze(a, minutes: a.snoozeMinutes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-alarms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A powerful, cross-platform alarm management library for React Native",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "index.ts",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"./service": {
|
|
14
14
|
"types": "./AlarmService.d.ts",
|
|
15
15
|
"default": "./AlarmService.ts"
|
|
16
|
-
}
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"android": "react-native run-android",
|
|
@@ -45,13 +46,10 @@
|
|
|
45
46
|
},
|
|
46
47
|
"license": "MIT",
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"react": "
|
|
49
|
-
"react-native": "
|
|
49
|
+
"react": "*",
|
|
50
|
+
"react-native": "*"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"react": "19.3.0",
|
|
53
|
-
"react-native": "0.88.0",
|
|
54
|
-
"@react-native/new-app-screen": "0.88.0",
|
|
55
53
|
"react-native-safe-area-context": "^5.5.2"
|
|
56
54
|
},
|
|
57
55
|
"devDependencies": {
|
|
@@ -72,23 +70,12 @@
|
|
|
72
70
|
"eslint": "^8.19.0",
|
|
73
71
|
"jest": "^29.6.3",
|
|
74
72
|
"prettier": "2.8.8",
|
|
73
|
+
"react": "19.3.0",
|
|
74
|
+
"react-native": "0.88.0",
|
|
75
75
|
"react-test-renderer": "19.3.0",
|
|
76
76
|
"typescript": "^6.0.3"
|
|
77
77
|
},
|
|
78
78
|
"engines": {
|
|
79
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
80
|
}
|
|
94
81
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'react-native-alarms'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.authors = package['author']['name']
|
|
11
|
+
s.homepage = package['repository']['url']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => "v#{s.version}" }
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => '15.0' }
|
|
15
|
+
s.swift_version = '5.9'
|
|
16
|
+
s.source_files = 'ios/alarm/**/*.{h,m,mm,swift}'
|
|
17
|
+
s.frameworks = 'UserNotifications', 'UIKit'
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Autolinking config for apps that install this package. The root android/ folder is
|
|
2
|
+
// the example app, so Android points at android-lib/ instead.
|
|
3
|
+
module.exports = {
|
|
4
|
+
dependency: {
|
|
5
|
+
platforms: {
|
|
6
|
+
android: {
|
|
7
|
+
sourceDir: './android-lib',
|
|
8
|
+
packageImportPath: 'import com.reactnativealarms.RNAlarmsPackage;',
|
|
9
|
+
packageInstance: 'new RNAlarmsPackage()',
|
|
10
|
+
},
|
|
11
|
+
ios: {
|
|
12
|
+
podspecPath: './react-native-alarms.podspec',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
package/specs/NativeAlarm.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
|
-
import { TurboModuleRegistry } from 'react-native';
|
|
2
|
+
import { NativeModules, Platform, TurboModuleRegistry } from 'react-native';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared contract of the native `Alarm` module.
|
|
@@ -92,4 +92,22 @@ export interface Spec extends TurboModule {
|
|
|
92
92
|
removeListeners(count: number): void;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
const LINKING_ERROR =
|
|
96
|
+
"react-native-alarms: the native 'Alarm' module is not linked. " +
|
|
97
|
+
Platform.select({
|
|
98
|
+
ios: "Run 'pod install' in the ios folder, then rebuild the app.",
|
|
99
|
+
default: 'Rebuild the app after installing the package.',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Both platforms register a legacy module, reachable through either registry
|
|
103
|
+
// depending on the architecture. Throw on first use, not on import.
|
|
104
|
+
const Alarm: Spec =
|
|
105
|
+
TurboModuleRegistry.get<Spec>('Alarm') ??
|
|
106
|
+
NativeModules.Alarm ??
|
|
107
|
+
new Proxy({} as Spec, {
|
|
108
|
+
get() {
|
|
109
|
+
throw new Error(LINKING_ERROR);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export default Alarm;
|
package/app.json
DELETED
package/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @format
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { AppRegistry } from 'react-native';
|
|
6
|
-
import App from './App';
|
|
7
|
-
import { name as appName } from './app.json';
|
|
8
|
-
import { AlarmRingScreen } from './AlarmRingScreen';
|
|
9
|
-
|
|
10
|
-
AppRegistry.registerComponent(appName, () => App);
|
|
11
|
-
|
|
12
|
-
AppRegistry.registerComponent('AlarmRing', () => AlarmRingScreen);
|