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.
@@ -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
- if center.delegate == nil { center.delegate = shared }
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
- if let a = alarm(from: notification) {
200
- ringing = a
201
- onEvent?(AlarmModule.eventFired, a)
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
- completionHandler([.banner, .list, .sound])
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",
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": ">=18.0.0",
49
- "react-native": ">=0.70.0"
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
+ };
@@ -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
- export default TurboModuleRegistry.getEnforcing<Spec>('Alarm');
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
@@ -1,4 +0,0 @@
1
- {
2
- "name": "ReactNativeAlarm",
3
- "displayName": "ReactNativeAlarm"
4
- }
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);