react-native-alarms 0.0.5 → 0.0.7

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 CHANGED
@@ -1,31 +1,87 @@
1
1
  /**
2
- * AlarmService - High-level alarm management API
2
+ * High-level alarm management API.
3
3
  *
4
- * This module provides a convenient wrapper around the native alarm module
5
- * with additional features like event listeners and state management.
4
+ * Provides a convenient wrapper around the native `Alarm` module for
5
+ * scheduling and managing alarms, handling permissions, querying alarm
6
+ * state, and subscribing to native alarm events.
7
+ *
8
+ * This API can also be used for time-sensitive reminders and custom
9
+ * scheduled experiences supported by the native alarm presentation flow.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { AlarmService } from 'react-native-alarms/service';
14
+ *
15
+ * const permission = await AlarmService.checkPermission();
16
+ *
17
+ * if (permission.status !== 'granted') {
18
+ * await AlarmService.requestPermission();
19
+ * }
20
+ *
21
+ * const alarmId = await AlarmService.scheduleAlarm({
22
+ * timestamp: Date.now() + 60_000,
23
+ * title: 'Reminder',
24
+ * body: 'Your scheduled event is ready.',
25
+ * });
26
+ * ```
6
27
  */
7
-
8
28
  import { NativeEventEmitter, NativeModules } from 'react-native';
9
29
  import type {
10
30
  AlarmConfig,
11
- Alarm,
31
+ AlarmProp,
12
32
  AlarmPermission,
13
33
  AlarmEvent,
14
- } from './specs/NativeAlarm';
34
+ } from './src/types';
35
+ import { AlarmRegistry } from './src/utils';
15
36
 
16
37
  const { Alarm: NativeAlarm } = NativeModules;
17
38
 
18
- // Guard against module not being linked
39
+ /**
40
+ * Resolves the native alarm module when it is linked.
41
+ *
42
+ * The module is loaded conditionally so importing the service does not
43
+ * immediately fail when the native module is unavailable.
44
+ */
19
45
  const alarm = NativeAlarm ? require('./specs/NativeAlarm').default : null;
20
46
 
47
+ /**
48
+ * Native event emitter used to subscribe to alarm lifecycle events.
49
+ */
21
50
  const alarmEmitter = NativeAlarm ? new NativeEventEmitter(NativeAlarm) : null;
22
51
 
52
+ /**
53
+ * Callback invoked when a native alarm event is received.
54
+ *
55
+ * @param alarm Event data emitted by the native alarm module.
56
+ */
23
57
  type AlarmEventListener = (alarm: AlarmEvent) => void;
24
58
 
25
59
  /**
26
- * High-level alarm service for managing alarms
60
+ * High-level service for scheduling and managing native alarms.
61
+ *
62
+ * All methods are static, so the service can be used directly without
63
+ * creating an instance.
64
+ *
65
+ * The service maintains JavaScript event listeners and forwards events
66
+ * received from the native module to registered callbacks.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const unsubscribe = AlarmService.onAlarmFired((alarm) => {
71
+ * console.log('Alarm fired:', alarm.title);
72
+ * });
73
+ *
74
+ * // Remove this listener when it is no longer needed.
75
+ * unsubscribe();
76
+ * ```
27
77
  */
28
78
  export class AlarmService {
79
+ /**
80
+ * Registered JavaScript listeners grouped by event type.
81
+ *
82
+ * A `Set` prevents the same callback reference from being registered
83
+ * more than once for the same event.
84
+ */
29
85
  private static listeners: {
30
86
  fired: Set<AlarmEventListener>;
31
87
  dismissed: Set<AlarmEventListener>;
@@ -36,64 +92,113 @@ export class AlarmService {
36
92
  snoozed: new Set(),
37
93
  };
38
94
 
95
+ /**
96
+ * Native event subscriptions created by {@link initialize}.
97
+ */
39
98
  private static subscriptions: any[] = [];
40
99
 
41
100
  /**
42
- * Initialize the alarm service and set up event listeners
101
+ * Initializes native event forwarding.
102
+ *
103
+ * Registers listeners for alarm firing, dismissal, and snoozing.
104
+ * Calling this method when subscriptions already exist does not
105
+ * register duplicate native subscriptions.
106
+ *
107
+ * This method is called automatically by the event subscription helpers.
43
108
  */
44
- static initialize() {
109
+ static initialize(): void {
45
110
  if (!alarmEmitter || !this.subscriptions.length) {
46
111
  this.subscriptions = [
47
112
  alarmEmitter?.addListener('onAlarmFired', (data: any) => {
48
- this.listeners.fired.forEach(listener => listener(data as AlarmEvent));
113
+ this.listeners.fired.forEach(listener =>
114
+ listener(data as AlarmEvent),
115
+ );
49
116
  }),
50
117
  alarmEmitter?.addListener('onAlarmDismissed', (data: any) => {
51
- this.listeners.dismissed.forEach(listener => listener(data as AlarmEvent));
118
+ this.listeners.dismissed.forEach(listener =>
119
+ listener(data as AlarmEvent),
120
+ );
52
121
  }),
53
122
  alarmEmitter?.addListener('onAlarmSnoozed', (data: any) => {
54
- this.listeners.snoozed.forEach(listener => listener(data as AlarmEvent));
123
+ this.listeners.snoozed.forEach(listener =>
124
+ listener(data as AlarmEvent),
125
+ );
55
126
  }),
56
127
  ].filter(Boolean);
57
128
  }
58
129
  }
59
130
 
60
131
  /**
61
- * Cleanup event listeners
132
+ * Removes native event subscriptions created by the service.
133
+ *
134
+ * This stops forwarding native events to JavaScript listeners until
135
+ * {@link initialize} is called again.
136
+ *
137
+ * Registered JavaScript callbacks remain in their respective sets.
138
+ * Use the unsubscribe functions returned by the event helpers to
139
+ * remove individual callbacks.
62
140
  */
63
- static cleanup() {
141
+ static cleanup(): void {
64
142
  this.subscriptions.forEach(sub => sub?.remove());
65
143
  this.subscriptions = [];
66
144
  }
67
145
 
68
146
  /**
69
- * Register a listener for when alarms fire
147
+ * Subscribes to alarms when they fire.
148
+ *
149
+ * The service initializes native event forwarding automatically.
150
+ *
151
+ * @param listener Callback invoked with the fired alarm's event data.
152
+ * @returns A function that unregisters this callback.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const unsubscribe = AlarmService.onAlarmFired(alarm => {
157
+ * console.log('Fired:', alarm.alarmId, alarm.title);
158
+ * });
159
+ *
160
+ * // Later:
161
+ * unsubscribe();
162
+ * ```
70
163
  */
71
- static onAlarmFired(listener: AlarmEventListener) {
164
+ static onAlarmFired(listener: AlarmEventListener): () => boolean {
72
165
  this.initialize();
73
166
  this.listeners.fired.add(listener);
167
+
74
168
  return () => this.listeners.fired.delete(listener);
75
169
  }
76
170
 
77
171
  /**
78
- * Register a listener for when alarms are dismissed
172
+ * Subscribes to alarms when they are dismissed.
173
+ *
174
+ * @param listener Callback invoked with the dismissed alarm's event data.
175
+ * @returns A function that unregisters this callback.
79
176
  */
80
- static onAlarmDismissed(listener: AlarmEventListener) {
177
+ static onAlarmDismissed(listener: AlarmEventListener): () => boolean {
81
178
  this.initialize();
82
179
  this.listeners.dismissed.add(listener);
180
+
83
181
  return () => this.listeners.dismissed.delete(listener);
84
182
  }
85
183
 
86
184
  /**
87
- * Register a listener for when alarms are snoozed
185
+ * Subscribes to alarms when they are snoozed.
186
+ *
187
+ * @param listener Callback invoked with the snoozed alarm's event data.
188
+ * @returns A function that unregisters this callback.
88
189
  */
89
- static onAlarmSnoozed(listener: AlarmEventListener) {
190
+ static onAlarmSnoozed(listener: AlarmEventListener): () => boolean {
90
191
  this.initialize();
91
192
  this.listeners.snoozed.add(listener);
193
+
92
194
  return () => this.listeners.snoozed.delete(listener);
93
195
  }
94
196
 
95
197
  /**
96
- * Check the current alarm permissions
198
+ * Retrieves the current alarm permission and capability status.
199
+ *
200
+ * @returns A promise resolving to the current permission status.
201
+ * @throws Error if the native alarm module is not linked.
97
202
  */
98
203
  static async checkPermission(): Promise<AlarmPermission> {
99
204
  if (!alarm) throw new Error('Alarm module not linked');
@@ -101,7 +206,12 @@ export class AlarmService {
101
206
  }
102
207
 
103
208
  /**
104
- * Request alarm permissions
209
+ * Requests the permissions required for alarm scheduling.
210
+ *
211
+ * The permissions requested depend on the platform and its capabilities.
212
+ *
213
+ * @returns A promise resolving to the updated permission status.
214
+ * @throws Error if the native alarm module is not linked.
105
215
  */
106
216
  static async requestPermission(): Promise<AlarmPermission> {
107
217
  if (!alarm) throw new Error('Alarm module not linked');
@@ -109,15 +219,42 @@ export class AlarmService {
109
219
  }
110
220
 
111
221
  /**
112
- * Schedule a new alarm
222
+ * Schedules a new alarm or time-based reminder.
223
+ *
224
+ * @param config Configuration containing the trigger timestamp,
225
+ * title, optional message, repeat settings, and custom data.
226
+ * @returns A promise resolving to the scheduled alarm ID.
227
+ * @throws Error if the native alarm module is not linked.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const alarmId = await AlarmService.scheduleAlarm({
232
+ * timestamp: Date.now() + 5 * 60_000,
233
+ * title: 'Take a break',
234
+ * body: 'Your scheduled break starts now.',
235
+ * data: JSON.stringify({ type: 'break-reminder' }),
236
+ * });
237
+ * ```
113
238
  */
114
239
  static async scheduleAlarm(config: AlarmConfig): Promise<string> {
115
- if (!alarm) throw new Error('Alarm module not linked');
116
- return alarm.scheduleAlarm(config);
240
+ if (!alarm) {
241
+ throw new Error('Alarm module not linked');
242
+ }
243
+ const componentName = AlarmRegistry.resolve(config.presentation?.type);
244
+ return alarm.scheduleAlarm({
245
+ ...config,
246
+ componentName,
247
+ componentProps: config.presentation?.props ?? {},
248
+ });
117
249
  }
118
250
 
119
251
  /**
120
- * Update an existing alarm
252
+ * Updates an existing scheduled alarm.
253
+ *
254
+ * @param alarmId Identifier of the alarm to update.
255
+ * @param config Updated alarm configuration.
256
+ * @returns A promise resolving to `true` if the update succeeds.
257
+ * @throws Error if the native alarm module is not linked.
121
258
  */
122
259
  static async updateAlarm(
123
260
  alarmId: string,
@@ -128,7 +265,11 @@ export class AlarmService {
128
265
  }
129
266
 
130
267
  /**
131
- * Cancel an alarm
268
+ * Cancels a specific scheduled alarm.
269
+ *
270
+ * @param alarmId Identifier of the alarm to cancel.
271
+ * @returns A promise resolving to whether cancellation succeeded.
272
+ * @throws Error if the native alarm module is not linked.
132
273
  */
133
274
  static async cancelAlarm(alarmId: string): Promise<boolean> {
134
275
  if (!alarm) throw new Error('Alarm module not linked');
@@ -136,7 +277,10 @@ export class AlarmService {
136
277
  }
137
278
 
138
279
  /**
139
- * Cancel all alarms
280
+ * Cancels all scheduled alarms managed by the native module.
281
+ *
282
+ * @returns A promise that resolves when the operation completes.
283
+ * @throws Error if the native alarm module is not linked.
140
284
  */
141
285
  static async cancelAllAlarms(): Promise<void> {
142
286
  if (!alarm) throw new Error('Alarm module not linked');
@@ -144,7 +288,11 @@ export class AlarmService {
144
288
  }
145
289
 
146
290
  /**
147
- * Stop the currently ringing alarm
291
+ * Stops a currently ringing alarm.
292
+ *
293
+ * @param alarmId Identifier of the ringing alarm.
294
+ * @returns A promise that resolves when the operation completes.
295
+ * @throws Error if the native alarm module is not linked.
148
296
  */
149
297
  static async stopAlarm(alarmId: string): Promise<void> {
150
298
  if (!alarm) throw new Error('Alarm module not linked');
@@ -152,7 +300,17 @@ export class AlarmService {
152
300
  }
153
301
 
154
302
  /**
155
- * Snooze the currently ringing alarm
303
+ * Snoozes a currently ringing alarm.
304
+ *
305
+ * @param alarmId Identifier of the ringing alarm.
306
+ * @param minutes Snooze duration in minutes. Defaults to `5`.
307
+ * @returns A promise that resolves when the operation completes.
308
+ * @throws Error if the native alarm module is not linked.
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * await AlarmService.snoozeAlarm(alarmId, 10);
313
+ * ```
156
314
  */
157
315
  static async snoozeAlarm(alarmId: string, minutes?: number): Promise<void> {
158
316
  if (!alarm) throw new Error('Alarm module not linked');
@@ -160,37 +318,78 @@ export class AlarmService {
160
318
  }
161
319
 
162
320
  /**
163
- * Get the currently ringing alarm (if any)
321
+ * Retrieves the alarm that is currently ringing.
322
+ *
323
+ * @returns A promise resolving to the ringing alarm, or `null`
324
+ * if no alarm is currently ringing.
325
+ * @throws Error if the native alarm module is not linked.
164
326
  */
165
- static async getRingingAlarm(): Promise<Alarm | null> {
327
+ static async getRingingAlarm(): Promise<AlarmProp | null> {
166
328
  if (!alarm) throw new Error('Alarm module not linked');
167
329
  return alarm.getRingingAlarm();
168
330
  }
169
331
 
170
332
  /**
171
- * Get a specific alarm by ID
333
+ * Retrieves a scheduled alarm by its identifier.
334
+ *
335
+ * @param alarmId Identifier of the alarm to retrieve.
336
+ * @returns A promise resolving to the alarm, or `null` if not found.
337
+ * @throws Error if the native alarm module is not linked.
172
338
  */
173
- static async getAlarm(alarmId: string): Promise<Alarm | null> {
339
+ static async getAlarm(alarmId: string): Promise<AlarmProp | null> {
174
340
  if (!alarm) throw new Error('Alarm module not linked');
175
341
  return alarm.getAlarm(alarmId);
176
342
  }
177
343
 
178
344
  /**
179
- * Get all scheduled alarms
345
+ * Retrieves all alarms managed by the native module.
346
+ *
347
+ * @returns A promise resolving to the list of scheduled alarms.
348
+ * @throws Error if the native alarm module is not linked.
180
349
  */
181
- static async getAllAlarms(): Promise<Alarm[]> {
350
+ static async getAllAlarms(): Promise<AlarmProp[]> {
182
351
  if (!alarm) throw new Error('Alarm module not linked');
183
352
  return alarm.getAllAlarms();
184
353
  }
185
354
 
186
355
  /**
187
- * Check if an alarm is scheduled
356
+ * Checks whether an alarm is currently scheduled.
357
+ *
358
+ * @param alarmId Identifier of the alarm to check.
359
+ * @returns A promise resolving to `true` if scheduled, otherwise `false`.
360
+ * @throws Error if the native alarm module is not linked.
188
361
  */
189
362
  static async isScheduled(alarmId: string): Promise<boolean> {
190
363
  if (!alarm) throw new Error('Alarm module not linked');
191
364
  return alarm.isScheduled(alarmId);
192
365
  }
366
+
367
+ /**
368
+ * Opens the device manufacturer's Auto Start settings screen.
369
+ *
370
+ * This is primarily intended for Android devices whose manufacturers
371
+ * provide additional background-execution settings.
372
+ *
373
+ * On iOS, the native implementation resolves to `false`.
374
+ *
375
+ * @returns A promise resolving to `true` if the settings screen was
376
+ * opened, or `false` if unsupported or unavailable.
377
+ * @throws Error if the native alarm module is not linked.
378
+ */
379
+ static async openAutoStartSettings(): Promise<boolean> {
380
+ if (!alarm) throw new Error('Alarm module not linked');
381
+ return alarm.openAutoStartSettings();
382
+ }
193
383
  }
194
384
 
195
- // Export a singleton instance
385
+ /**
386
+ * Default export for convenient service access.
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * import AlarmService from 'react-native-alarms/service';
391
+ *
392
+ * const alarms = await AlarmService.getAllAlarms();
393
+ * ```
394
+ */
196
395
  export default AlarmService;
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 react-native-alarm contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # React Native Alarms 🔔
2
2
 
3
- A powerful, cross-platform alarm management library for React Native. Schedule, manage, and control alarms on both Android and iOS with a unified API.
3
+ A cross-platform native scheduling, reminder, and wake-up system for React Native. Schedule time-sensitive events on Android and iOS, deliver reminders, and present a registered custom React Native screen when a scheduled event fires—using one unified API.
4
+
5
+ Although it can power a traditional alarm clock, react-native-alarms is also useful for exact-time reminders, scheduled check-ins, custom lock-screen experiences, and time-based app workflows. Build the experience around your own UI and event data instead of being limited to a standard alarm screen.
6
+
7
+ > **Platform note:** The operating system controls background delivery and screen presentation. Exact timing, full-screen presentation, and behavior when an app is terminated depend on platform version, permissions, device settings, and the native scheduling mechanism. This library does not provide unrestricted background JavaScript execution.
8
+
4
9
 
5
10
  [![npm version](https://badge.fury.io/js/react-native-alarms.svg)](https://badge.fury.io/js/react-native-alarms)
6
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -65,6 +70,44 @@ AppRegistry.registerComponent('AlarmRing', () => AlarmRingScreen);
65
70
  Call `Alarm.stopAlarm(alarmId)` or `Alarm.snoozeAlarm(alarmId, minutes)` from its
66
71
  buttons; the screen closes itself.
67
72
 
73
+ #### Custom presentation component
74
+
75
+ You can register **multiple** ring screens and choose which one fires per alarm using the `presentation` field in `AlarmConfig`:
76
+
77
+ ```js
78
+ // index.js
79
+ import { AppRegistry } from 'react-native';
80
+ import { AlarmRingScreen } from './src/AlarmRingScreen';
81
+ import { ReminderScreen } from './src/ReminderScreen';
82
+
83
+ AppRegistry.registerComponent('AlarmRing', () => AlarmRingScreen); // default
84
+ AppRegistry.registerComponent('Reminder', () => ReminderScreen); // custom
85
+ ```
86
+
87
+ ```ts
88
+ import { AlarmRegistry, AlarmService } from 'react-native-alarms/service';
89
+
90
+ // 1. Map your key to the registered component name
91
+ AlarmRegistry.register('reminder', 'Reminder');
92
+
93
+ // 2. Pass it when scheduling
94
+ await AlarmService.scheduleAlarm({
95
+ timestamp: Date.now() + 60_000,
96
+ title: 'Standup',
97
+ presentation: {
98
+ type: 'reminder', // resolves to the 'Reminder' component
99
+ props: { color: '#6200EE' } // forwarded to the screen as componentProps
100
+ },
101
+ });
102
+ ```
103
+
104
+ The native layer maps `presentation` to two internal fields:
105
+
106
+ | Internal field | Type | Default | Description |
107
+ |---|---|---|---|
108
+ | `componentName` | `string` | `"AlarmRing"` | Registered `AppRegistry` component name shown when the alarm rings. |
109
+ | `componentProps` / `componentPropsJSON` | `object` / `string` | `{}` | Key-value props forwarded to the ring screen on **both platforms**. On Android they arrive as top-level bundle extras and a nested `componentProps` map; on iOS as a parsed `[String: Any]` dictionary. |
110
+
68
111
  ### Direct module access
69
112
 
70
113
  ```ts
@@ -179,9 +222,28 @@ type AlarmConfig = {
179
222
  snoozeMinutes?: number; // Snooze duration in minutes
180
223
  repeat?: AlarmRepeat; // Repeat configuration
181
224
  data?: string; // Custom JSON string payload
225
+ presentation?: AlarmPresentation; // Custom ring screen & props
226
+ };
227
+
228
+ type AlarmPresentation = {
229
+ /** Registered presentation type (key passed to AlarmRegistry.register). */
230
+ type?: string;
231
+ /**
232
+ * Props forwarded to the ring screen component.
233
+ * Available as `componentProps` on both Android and iOS.
234
+ */
235
+ props?: Record<string, unknown>;
182
236
  };
183
237
  ```
184
238
 
239
+ > **How the native layer stores these:** `AlarmConfig.presentation` is resolved by `AlarmService` before being handed off to the native module. The resulting fields stored in the native `AlarmData` model are:
240
+ >
241
+ > | Field | Type | Default | Description |
242
+ > |---|---|---|---|
243
+ > | `componentName` | `string` | `"AlarmRing"` | The `AppRegistry` name of the component shown at ring time. Derived from `AlarmRegistry.resolve(presentation.type)`. |
244
+ > | `componentProps` | `object` | `{}` | Parsed props object (from `presentation.props`). Available on both platforms at ring time as `componentProps`. |
245
+ > | `componentPropsJSON` | `string` | `"{}"` | JSON-serialised representation of `componentProps`, persisted alongside the alarm so props survive a device reboot. |
246
+
185
247
  #### `AlarmRepeat`
186
248
  Repeat configuration for recurring alarms.
187
249
 
@@ -361,6 +423,22 @@ const styles = StyleSheet.create({
361
423
  });
362
424
  ```
363
425
 
426
+ ## 📸 Screenshots, Demos & Examples
427
+
428
+ Explore how `react-native-alarms` works on Android and iOS.
429
+
430
+ ### 🖼️ Screenshots
431
+
432
+ | Sample 1 | Sample 2 | Sample 3 |
433
+ |---|---|---|
434
+ | <img src="src/assets/sample-1.png" width="180" alt="Sample 1" /> | <img src="src/assets/sample-2.png" width="180" alt="Sample 2" /> | <img src="src/assets/sample-3.png" width="180" alt="Sample 3" /> |
435
+
436
+ ### 🎥 Demo Video
437
+
438
+ Watch the library in action, including alarm scheduling, ringing, and Stop/Snooze interactions.
439
+
440
+ [▶️ Watch the React Native Alarms Demo](src/assets/recording.mp4)
441
+
364
442
  ## Troubleshooting
365
443
 
366
444
  ### "Alarm Native module is not linked"
@@ -417,27 +495,99 @@ For issues and questions, please visit:
417
495
 
418
496
  The library supports completely custom alarm UI screens with custom data payloads.
419
497
 
420
- ### Basic Custom Screen Example
498
+ ### Using `componentName` and `componentProps`
499
+
500
+ The `presentation` field lets you choose **which registered component** is displayed and pass **arbitrary props** directly to it — without encoding everything inside a JSON `data` string.
501
+
502
+ #### 1. Register your screens
503
+
504
+ ```js
505
+ // index.js
506
+ import { AppRegistry } from 'react-native';
507
+ import { AlarmRingScreen } from './src/AlarmRingScreen';
508
+ import { MeetingAlarmScreen } from './src/MeetingAlarmScreen';
509
+
510
+ AppRegistry.registerComponent('AlarmRing', () => AlarmRingScreen); // default fallback
511
+ AppRegistry.registerComponent('MeetingAlarm', () => MeetingAlarmScreen); // custom
512
+ ```
513
+
514
+ #### 2. Map a key to the component name
515
+
516
+ ```ts
517
+ import { AlarmRegistry } from 'react-native-alarms';
518
+
519
+ AlarmRegistry.register('meeting', 'MeetingAlarm');
520
+ ```
521
+
522
+ #### 3. Schedule with `presentation`
421
523
 
422
524
  ```typescript
423
- import { NativeModules } from 'react-native';
424
525
  import { AlarmService } from 'react-native-alarms/service';
425
526
 
426
- export function MyCustomAlarmScreen({ alarmId, title, data }) {
427
- const customData = JSON.parse(data || '{}');
527
+ await AlarmService.scheduleAlarm({
528
+ timestamp: Date.now() + 5 * 60 * 1000,
529
+ title: 'Team standup',
530
+ presentation: {
531
+ type: 'meeting', // resolves to 'MeetingAlarm'
532
+ props: {
533
+ roomName: 'Zoom Room A',
534
+ meetingUrl: 'https://zoom.us/j/123',
535
+ accentColor: '#6200EE',
536
+ },
537
+ },
538
+ });
539
+ ```
540
+
541
+ #### 4. Receive `componentProps` in your screen
542
+
543
+ The props passed via `presentation.props` are forwarded to the ring screen on both platforms:
544
+
545
+ ```typescript
546
+ // MeetingAlarmScreen.tsx
547
+ type Props = {
548
+ alarmId: string;
549
+ title: string;
550
+ snoozeEnabled: boolean;
551
+ snoozeMinutes: number;
552
+ data?: string;
553
+ // componentProps fields arrive as top-level props:
554
+ roomName?: string;
555
+ meetingUrl?: string;
556
+ accentColor?: string;
557
+ };
428
558
 
559
+ export function MeetingAlarmScreen({
560
+ alarmId,
561
+ title,
562
+ snoozeEnabled,
563
+ snoozeMinutes,
564
+ roomName,
565
+ meetingUrl,
566
+ accentColor = '#007AFF',
567
+ }: Props) {
429
568
  return (
430
- <View style={{ backgroundColor: customData.color }}>
431
- <Text>{title}</Text>
569
+ <View style={{ flex: 1, backgroundColor: accentColor }}>
570
+ <Text style={{ fontSize: 28, color: '#fff' }}>{title}</Text>
571
+ <Text style={{ color: '#fff' }}>{roomName}</Text>
572
+
573
+ {snoozeEnabled && (
574
+ <TouchableOpacity onPress={() => AlarmService.snoozeAlarm(alarmId, snoozeMinutes)}>
575
+ <Text>Snooze {snoozeMinutes} min</Text>
576
+ </TouchableOpacity>
577
+ )}
432
578
  <TouchableOpacity onPress={() => AlarmService.stopAlarm(alarmId)}>
433
- <Text>Stop</Text>
579
+ <Text>Dismiss</Text>
434
580
  </TouchableOpacity>
435
581
  </View>
436
582
  );
437
583
  }
438
584
  ```
439
585
 
440
- ### Passing Custom Data
586
+ > **Platform behaviour**
587
+ > - **Android**: `componentProps` entries are flattened into the launch `Bundle` alongside standard fields (`alarmId`, `title`, etc.), and also available as a nested `componentProps` bundle. The `AlarmRingActivity` uses `componentName` to resolve which registered React root to display.
588
+ > - **iOS**: `componentProps` (parsed from `componentPropsJSON`) is merged into the surface's initial properties passed to the React component.
589
+
590
+ ### Passing Custom Data (legacy approach)
441
591
 
442
592
  ```typescript
443
593
  await AlarmService.scheduleAlarm({