react-native-alarms 0.0.6 → 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.d.ts +137 -33
- package/AlarmService.ts +224 -37
- package/README.md +143 -9
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmData.kt +154 -8
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmModule.kt +2 -2
- package/android/app/src/main/java/com/reactnativealarm/alarm/AlarmRingActivity.kt +3 -2
- package/android/local.properties +8 -0
- package/index.d.ts +3 -3
- package/index.ts +5 -5
- package/ios/alarm/AlarmData.swift +20 -6
- package/ios/alarm/AlarmKitBackend.swift +43 -40
- package/ios/alarm/AlarmModule.swift +68 -64
- package/ios/alarm/AlarmRingPresenter.swift +26 -19
- package/ios/alarm/AlarmStore.swift +9 -9
- package/ios/alarm/NotificationBackend.swift +33 -33
- package/package.json +1 -1
- package/specs/NativeAlarm.ts +120 -71
- package/src/types/index.ts +246 -0
- package/src/utils/AlarmRegistry.ts +57 -0
- package/src/utils/index.ts +1 -0
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# React Native Alarms 🔔
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
[](https://badge.fury.io/js/react-native-alarms)
|
|
6
11
|
[](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
|
|
|
@@ -433,27 +495,99 @@ For issues and questions, please visit:
|
|
|
433
495
|
|
|
434
496
|
The library supports completely custom alarm UI screens with custom data payloads.
|
|
435
497
|
|
|
436
|
-
###
|
|
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`
|
|
437
523
|
|
|
438
524
|
```typescript
|
|
439
|
-
import { NativeModules } from 'react-native';
|
|
440
525
|
import { AlarmService } from 'react-native-alarms/service';
|
|
441
526
|
|
|
442
|
-
|
|
443
|
-
|
|
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
|
+
};
|
|
444
558
|
|
|
559
|
+
export function MeetingAlarmScreen({
|
|
560
|
+
alarmId,
|
|
561
|
+
title,
|
|
562
|
+
snoozeEnabled,
|
|
563
|
+
snoozeMinutes,
|
|
564
|
+
roomName,
|
|
565
|
+
meetingUrl,
|
|
566
|
+
accentColor = '#007AFF',
|
|
567
|
+
}: Props) {
|
|
445
568
|
return (
|
|
446
|
-
<View style={{ backgroundColor:
|
|
447
|
-
<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
|
+
)}
|
|
448
578
|
<TouchableOpacity onPress={() => AlarmService.stopAlarm(alarmId)}>
|
|
449
|
-
<Text>
|
|
579
|
+
<Text>Dismiss</Text>
|
|
450
580
|
</TouchableOpacity>
|
|
451
581
|
</View>
|
|
452
582
|
);
|
|
453
583
|
}
|
|
454
584
|
```
|
|
455
585
|
|
|
456
|
-
|
|
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)
|
|
457
591
|
|
|
458
592
|
```typescript
|
|
459
593
|
await AlarmService.scheduleAlarm({
|
|
@@ -3,6 +3,7 @@ package com.alarm
|
|
|
3
3
|
import android.os.Bundle
|
|
4
4
|
import com.facebook.react.bridge.Arguments
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.facebook.react.bridge.WritableArray
|
|
6
7
|
import com.facebook.react.bridge.WritableMap
|
|
7
8
|
import org.json.JSONArray
|
|
8
9
|
import org.json.JSONObject
|
|
@@ -21,8 +22,29 @@ data class AlarmData(
|
|
|
21
22
|
val weekdays: List<Int>,
|
|
22
23
|
val intervalMs: Long,
|
|
23
24
|
val data: String,
|
|
24
|
-
var enabled: Boolean = true
|
|
25
|
+
var enabled: Boolean = true,
|
|
26
|
+
val componentName: String = "AlarmRing",
|
|
27
|
+
val componentPropsJSON: String = "{}"
|
|
25
28
|
) {
|
|
29
|
+
val componentProps: Map<String, Any>
|
|
30
|
+
get() {
|
|
31
|
+
return try {
|
|
32
|
+
val obj = JSONObject(componentPropsJSON)
|
|
33
|
+
val map = mutableMapOf<String, Any>()
|
|
34
|
+
val keys = obj.keys()
|
|
35
|
+
while (keys.hasNext()) {
|
|
36
|
+
val key = keys.next()
|
|
37
|
+
val value = obj.get(key)
|
|
38
|
+
if (value != JSONObject.NULL) {
|
|
39
|
+
map[key] = value
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
map
|
|
43
|
+
} catch (_: Exception) {
|
|
44
|
+
emptyMap()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
fun nextTrigger(from: Long = System.currentTimeMillis()): Long {
|
|
27
49
|
if (!enabled) return 0
|
|
28
50
|
return when (repeatType) {
|
|
@@ -59,6 +81,8 @@ data class AlarmData(
|
|
|
59
81
|
put("snoozeMinutes", snoozeMinutes); put("repeatType", repeatType)
|
|
60
82
|
put("weekdays", JSONArray(weekdays)); put("intervalMs", intervalMs)
|
|
61
83
|
put("data", data); put("enabled", enabled)
|
|
84
|
+
put("componentName", componentName)
|
|
85
|
+
put("componentPropsJSON", componentPropsJSON)
|
|
62
86
|
}.toString()
|
|
63
87
|
|
|
64
88
|
fun toWritableMap(): WritableMap = Arguments.createMap().apply {
|
|
@@ -78,6 +102,13 @@ data class AlarmData(
|
|
|
78
102
|
putString("data", data)
|
|
79
103
|
putBoolean("enabled", enabled)
|
|
80
104
|
putDouble("nextTriggerAt", nextTrigger().toDouble())
|
|
105
|
+
putString("componentName", componentName)
|
|
106
|
+
putString("componentPropsJSON", componentPropsJSON)
|
|
107
|
+
try {
|
|
108
|
+
putMap("componentProps", jsonToWritableMap(JSONObject(componentPropsJSON)))
|
|
109
|
+
} catch (_: Exception) {
|
|
110
|
+
putMap("componentProps", Arguments.createMap())
|
|
111
|
+
}
|
|
81
112
|
}
|
|
82
113
|
|
|
83
114
|
/** Initial props for the React ring screen hosted by [AlarmRingActivity]. */
|
|
@@ -89,19 +120,70 @@ data class AlarmData(
|
|
|
89
120
|
putBoolean("snoozeEnabled", snoozeEnabled)
|
|
90
121
|
putInt("snoozeMinutes", snoozeMinutes)
|
|
91
122
|
putString("data", data)
|
|
123
|
+
putString("componentName", componentName)
|
|
124
|
+
putString("componentPropsJSON", componentPropsJSON)
|
|
125
|
+
try {
|
|
126
|
+
val propsObj = JSONObject(componentPropsJSON)
|
|
127
|
+
val propsBundle = jsonToBundle(propsObj)
|
|
128
|
+
putBundle("componentProps", propsBundle)
|
|
129
|
+
for (key in propsBundle.keySet()) {
|
|
130
|
+
if (!containsKey(key)) {
|
|
131
|
+
val v = propsBundle.get(key)
|
|
132
|
+
when (v) {
|
|
133
|
+
is String -> putString(key, v)
|
|
134
|
+
is Boolean -> putBoolean(key, v)
|
|
135
|
+
is Int -> putInt(key, v)
|
|
136
|
+
is Long -> putLong(key, v)
|
|
137
|
+
is Double -> putDouble(key, v)
|
|
138
|
+
is Bundle -> putBundle(key, v)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (_: Exception) {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private fun jsonToBundle(jsonObject: JSONObject): Bundle {
|
|
146
|
+
val bundle = Bundle()
|
|
147
|
+
val iter = jsonObject.keys()
|
|
148
|
+
while (iter.hasNext()) {
|
|
149
|
+
val key = iter.next()
|
|
150
|
+
when (val value = jsonObject.opt(key)) {
|
|
151
|
+
is JSONObject -> bundle.putBundle(key, jsonToBundle(value))
|
|
152
|
+
is JSONArray -> bundle.putString(key, value.toString())
|
|
153
|
+
is Boolean -> bundle.putBoolean(key, value)
|
|
154
|
+
is Int -> bundle.putInt(key, value)
|
|
155
|
+
is Long -> bundle.putLong(key, value)
|
|
156
|
+
is Double -> bundle.putDouble(key, value)
|
|
157
|
+
is String -> bundle.putString(key, value)
|
|
158
|
+
else -> if (value != null && value != JSONObject.NULL) {
|
|
159
|
+
bundle.putString(key, value.toString())
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return bundle
|
|
92
164
|
}
|
|
93
165
|
|
|
94
166
|
companion object {
|
|
95
167
|
fun fromJson(s: String): AlarmData {
|
|
96
168
|
val o = JSONObject(s)
|
|
97
169
|
val wd = o.optJSONArray("weekdays")
|
|
170
|
+
val compName = o.optString("componentName", "AlarmRing").trim()
|
|
98
171
|
return AlarmData(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
o.optString("
|
|
103
|
-
|
|
104
|
-
|
|
172
|
+
id = o.getString("id"),
|
|
173
|
+
timestamp = o.getLong("timestamp"),
|
|
174
|
+
title = o.optString("title"),
|
|
175
|
+
body = o.optString("body"),
|
|
176
|
+
sound = o.optString("sound", "default"),
|
|
177
|
+
vibrate = o.optBoolean("vibrate", true),
|
|
178
|
+
snoozeEnabled = o.optBoolean("snoozeEnabled", true),
|
|
179
|
+
snoozeMinutes = o.optInt("snoozeMinutes", 5),
|
|
180
|
+
repeatType = o.optString("repeatType", "none"),
|
|
181
|
+
weekdays = if (wd == null) emptyList() else (0 until wd.length()).map { wd.getInt(it) },
|
|
182
|
+
intervalMs = o.optLong("intervalMs", 0),
|
|
183
|
+
data = o.optString("data"),
|
|
184
|
+
enabled = o.optBoolean("enabled", true),
|
|
185
|
+
componentName = if (compName.isEmpty()) "AlarmRing" else compName,
|
|
186
|
+
componentPropsJSON = o.optString("componentPropsJSON", "{}").ifBlank { "{}" }
|
|
105
187
|
)
|
|
106
188
|
}
|
|
107
189
|
|
|
@@ -109,6 +191,24 @@ data class AlarmData(
|
|
|
109
191
|
val r = if (m.hasKey("repeat") && !m.isNull("repeat")) m.getMap("repeat") else null
|
|
110
192
|
val wd = r?.takeIf { it.hasKey("weekdays") }?.getArray("weekdays")
|
|
111
193
|
?.let { a -> (0 until a.size()).map { a.getInt(it) } } ?: emptyList()
|
|
194
|
+
|
|
195
|
+
val componentPropsStr = when {
|
|
196
|
+
m.hasKey("componentPropsJSON") && !m.isNull("componentPropsJSON") ->
|
|
197
|
+
m.getString("componentPropsJSON") ?: "{}"
|
|
198
|
+
m.hasKey("componentProps") && !m.isNull("componentProps") -> {
|
|
199
|
+
val propsMap = m.getMap("componentProps")
|
|
200
|
+
try {
|
|
201
|
+
val hashMap = propsMap?.toHashMap() ?: emptyMap<String, Any>()
|
|
202
|
+
JSONObject(hashMap).toString()
|
|
203
|
+
} catch (_: Exception) {
|
|
204
|
+
"{}"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else -> "{}"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
val compName = m.str("componentName", "AlarmRing").trim()
|
|
211
|
+
|
|
112
212
|
return AlarmData(
|
|
113
213
|
id = id,
|
|
114
214
|
timestamp = m.getDouble("timestamp").toLong(),
|
|
@@ -121,10 +221,56 @@ data class AlarmData(
|
|
|
121
221
|
repeatType = r?.str("type", "none") ?: "none",
|
|
122
222
|
weekdays = wd,
|
|
123
223
|
intervalMs = if (r != null && r.hasKey("intervalMs")) r.getDouble("intervalMs").toLong() else 0,
|
|
124
|
-
data = m.str("data")
|
|
224
|
+
data = m.str("data"),
|
|
225
|
+
componentName = if (compName.isEmpty()) "AlarmRing" else compName,
|
|
226
|
+
componentPropsJSON = componentPropsStr
|
|
125
227
|
)
|
|
126
228
|
}
|
|
127
229
|
|
|
230
|
+
private fun jsonToWritableMap(jsonObject: JSONObject): WritableMap {
|
|
231
|
+
val map = Arguments.createMap()
|
|
232
|
+
val iter = jsonObject.keys()
|
|
233
|
+
while (iter.hasNext()) {
|
|
234
|
+
val key = iter.next()
|
|
235
|
+
when (val value = jsonObject.opt(key)) {
|
|
236
|
+
is JSONObject -> map.putMap(key, jsonToWritableMap(value))
|
|
237
|
+
is JSONArray -> map.putArray(key, jsonToWritableArray(value))
|
|
238
|
+
is Boolean -> map.putBoolean(key, value)
|
|
239
|
+
is Int -> map.putInt(key, value)
|
|
240
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
241
|
+
is Double -> map.putDouble(key, value)
|
|
242
|
+
is String -> map.putString(key, value)
|
|
243
|
+
else -> if (value == null || value == JSONObject.NULL) {
|
|
244
|
+
map.putNull(key)
|
|
245
|
+
} else {
|
|
246
|
+
map.putString(key, value.toString())
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return map
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private fun jsonToWritableArray(jsonArray: JSONArray): WritableArray {
|
|
254
|
+
val array = Arguments.createArray()
|
|
255
|
+
for (i in 0 until jsonArray.length()) {
|
|
256
|
+
when (val value = jsonArray.opt(i)) {
|
|
257
|
+
is JSONObject -> array.pushMap(jsonToWritableMap(value))
|
|
258
|
+
is JSONArray -> array.pushArray(jsonToWritableArray(value))
|
|
259
|
+
is Boolean -> array.pushBoolean(value)
|
|
260
|
+
is Int -> array.pushInt(value)
|
|
261
|
+
is Long -> array.pushDouble(value.toDouble())
|
|
262
|
+
is Double -> array.pushDouble(value)
|
|
263
|
+
is String -> array.pushString(value)
|
|
264
|
+
else -> if (value == null || value == JSONObject.NULL) {
|
|
265
|
+
array.pushNull()
|
|
266
|
+
} else {
|
|
267
|
+
array.pushString(value.toString())
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return array
|
|
272
|
+
}
|
|
273
|
+
|
|
128
274
|
private fun ReadableMap.str(k: String, d: String = "") =
|
|
129
275
|
if (hasKey(k) && !isNull(k)) getString(k) ?: d else d
|
|
130
276
|
|
|
@@ -31,14 +31,14 @@ class AlarmModule(private val ctx: ReactApplicationContext) : ReactContextBaseJa
|
|
|
31
31
|
private fun notificationsGranted(): Boolean =
|
|
32
32
|
if (Build.VERSION.SDK_INT >= 33)
|
|
33
33
|
ContextCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) ==
|
|
34
|
-
|
|
34
|
+
PackageManager.PERMISSION_GRANTED
|
|
35
35
|
else NotificationManagerCompat.from(ctx).areNotificationsEnabled()
|
|
36
36
|
|
|
37
37
|
private fun canDrawOverlays(): Boolean = Settings.canDrawOverlays(ctx)
|
|
38
38
|
|
|
39
39
|
private fun canUseFullScreenIntent(): Boolean =
|
|
40
40
|
Build.VERSION.SDK_INT < 34 ||
|
|
41
|
-
|
|
41
|
+
ctx.getSystemService(NotificationManager::class.java).canUseFullScreenIntent()
|
|
42
42
|
|
|
43
43
|
private fun permissionMap(): WritableMap = Arguments.createMap().apply {
|
|
44
44
|
val ok = notificationsGranted()
|
|
@@ -18,7 +18,8 @@ import java.lang.ref.WeakReference
|
|
|
18
18
|
*/
|
|
19
19
|
class AlarmRingActivity : ReactActivity() {
|
|
20
20
|
|
|
21
|
-
override fun getMainComponentName(): String =
|
|
21
|
+
override fun getMainComponentName(): String =
|
|
22
|
+
AlarmService.current?.componentName?.takeIf { it.isNotBlank() } ?: COMPONENT_NAME
|
|
22
23
|
|
|
23
24
|
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
|
24
25
|
object : DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) {
|
|
@@ -45,7 +46,7 @@ class AlarmRingActivity : ReactActivity() {
|
|
|
45
46
|
@Suppress("DEPRECATION")
|
|
46
47
|
window.addFlags(
|
|
47
48
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
48
|
-
|
|
49
|
+
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
|
49
50
|
)
|
|
50
51
|
}
|
|
51
52
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## This file must *NOT* be checked into Version Control Systems,
|
|
2
|
+
# as it contains information specific to your local configuration.
|
|
3
|
+
#
|
|
4
|
+
# Location of the SDK. This is only used by Gradle.
|
|
5
|
+
# For customization when using a Version Control System, please read the
|
|
6
|
+
# header note.
|
|
7
|
+
#Fri Oct 23 16:38:30 IST 2026
|
|
8
|
+
sdk.dir=/Users/apple/Library/Android/sdk
|
package/index.d.ts
CHANGED
|
@@ -8,13 +8,13 @@ import type { Spec } from './specs/NativeAlarm';
|
|
|
8
8
|
export type {
|
|
9
9
|
AlarmRepeat,
|
|
10
10
|
AlarmConfig,
|
|
11
|
-
|
|
11
|
+
AlarmProp,
|
|
12
12
|
AlarmPermission,
|
|
13
13
|
AlarmEvent,
|
|
14
|
-
|
|
15
|
-
} from './specs/NativeAlarm';
|
|
14
|
+
} from './src/types';
|
|
16
15
|
|
|
17
16
|
export { AlarmService } from './AlarmService';
|
|
18
17
|
|
|
19
18
|
declare const Alarm: Spec;
|
|
19
|
+
|
|
20
20
|
export default Alarm;
|
package/index.ts
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
export type {
|
|
9
9
|
AlarmRepeat,
|
|
10
10
|
AlarmConfig,
|
|
11
|
-
|
|
11
|
+
AlarmProp,
|
|
12
12
|
AlarmPermission,
|
|
13
13
|
AlarmEvent,
|
|
14
|
-
|
|
15
|
-
} from './specs/NativeAlarm';
|
|
14
|
+
} from './src/types';
|
|
16
15
|
|
|
17
16
|
export { AlarmService } from './AlarmService';
|
|
18
17
|
|
|
19
|
-
import
|
|
20
|
-
|
|
18
|
+
import Alarm from './specs/NativeAlarm';
|
|
19
|
+
|
|
20
|
+
export default Alarm;
|
|
@@ -16,11 +16,25 @@ struct AlarmData: Codable {
|
|
|
16
16
|
var intervalMs: Double
|
|
17
17
|
var data: String
|
|
18
18
|
var enabled: Bool = true
|
|
19
|
-
|
|
19
|
+
var componentName: String = "AlarmRing"
|
|
20
|
+
var componentPropsJSON: String = "{}"
|
|
21
|
+
|
|
20
22
|
static func now() -> Double { Date().timeIntervalSince1970 * 1000 }
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
var date: Date { Date(timeIntervalSince1970: timestamp / 1000) }
|
|
23
25
|
|
|
26
|
+
var componentProps: [String: Any] {
|
|
27
|
+
guard
|
|
28
|
+
let jsonData = componentPropsJSON.data(using: .utf8),
|
|
29
|
+
let object = try? JSONSerialization.jsonObject(with: jsonData),
|
|
30
|
+
let props = object as? [String: Any]
|
|
31
|
+
else {
|
|
32
|
+
return [:]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return props
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
/// Next time this alarm should ring, in ms since epoch, or 0 if never.
|
|
25
39
|
func nextTrigger(from: Double = AlarmData.now()) -> Double {
|
|
26
40
|
if !enabled { return 0 }
|
|
@@ -37,7 +51,7 @@ struct AlarmData: Codable {
|
|
|
37
51
|
return timestamp > from ? timestamp : 0
|
|
38
52
|
}
|
|
39
53
|
}
|
|
40
|
-
|
|
54
|
+
|
|
41
55
|
private func nextMatching(from: Double, ok: (Int) -> Bool) -> Double {
|
|
42
56
|
let cal = Calendar.current
|
|
43
57
|
let base = cal.dateComponents([.hour, .minute], from: date)
|
|
@@ -55,9 +69,9 @@ struct AlarmData: Codable {
|
|
|
55
69
|
}
|
|
56
70
|
return 0
|
|
57
71
|
}
|
|
58
|
-
|
|
72
|
+
|
|
59
73
|
// MARK: - JS bridge shape (same keys as Android)
|
|
60
|
-
|
|
74
|
+
|
|
61
75
|
func toDictionary() -> [String: Any] {
|
|
62
76
|
[
|
|
63
77
|
"alarmId": id,
|
|
@@ -74,7 +88,7 @@ struct AlarmData: Codable {
|
|
|
74
88
|
"nextTriggerAt": nextTrigger(),
|
|
75
89
|
]
|
|
76
90
|
}
|
|
77
|
-
|
|
91
|
+
|
|
78
92
|
/// Parses the `AlarmConfig` object JS passes to `scheduleAlarm` / `updateAlarm`.
|
|
79
93
|
static func from(config m: [String: Any], id: String) -> AlarmData {
|
|
80
94
|
let r = m["repeat"] as? [String: Any]
|