react-native-alarmageddon 1.0.0

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.
@@ -0,0 +1,485 @@
1
+ package com.rnalarmmodule
2
+
3
+ import android.app.AlarmManager
4
+ import android.app.PendingIntent
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.SharedPreferences
8
+ import android.os.Build
9
+ import android.provider.Settings
10
+ import android.util.Log
11
+ import com.facebook.react.bridge.*
12
+ import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
13
+ import org.json.JSONArray
14
+ import org.json.JSONObject
15
+ import java.lang.ref.WeakReference
16
+ import java.text.SimpleDateFormat
17
+ import java.util.*
18
+
19
+ class AlarmModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
20
+
21
+ companion object {
22
+ private const val TAG = "AlarmModule"
23
+ private const val PREFS_NAME = "rn_alarm_module_alarms"
24
+ private const val ALARMS_KEY = "alarms"
25
+ private const val DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"
26
+ private const val ACTIVE_ALARM_KEY = "active_alarm_id"
27
+
28
+ // Weak reference to avoid memory leaks
29
+ private var reactContextRef: WeakReference<ReactApplicationContext>? = null
30
+
31
+ fun getReactContext(): ReactApplicationContext? = reactContextRef?.get()
32
+
33
+ fun emitActiveAlarmId(id: String?) {
34
+ val reactContext = getReactContext()
35
+ if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
36
+ try {
37
+ reactContext
38
+ .getJSModule(RCTDeviceEventEmitter::class.java)
39
+ .emit("activeAlarmId", id)
40
+ } catch (e: Exception) {
41
+ Log.e(TAG, "Failed to emit activeAlarmId event: ${e.message}")
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ private val prefs: SharedPreferences = reactContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
48
+ private val dateFormat = SimpleDateFormat(DATE_PATTERN, Locale.getDefault())
49
+
50
+ init {
51
+ reactContextRef = WeakReference(reactContext)
52
+ dateFormat.timeZone = TimeZone.getDefault()
53
+ }
54
+
55
+ override fun getName(): String = "AlarmModule"
56
+
57
+ override fun getConstants(): Map<String, Any> {
58
+ return mapOf(
59
+ "EXACT_ALARM_PERMISSION_REQUIRED" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
60
+ )
61
+ }
62
+
63
+ @ReactMethod
64
+ fun addListener(eventName: String) {
65
+ // Required for NativeEventEmitter
66
+ }
67
+
68
+ @ReactMethod
69
+ fun removeListeners(count: Int) {
70
+ // Required for NativeEventEmitter
71
+ }
72
+
73
+ @ReactMethod
74
+ fun scheduleAlarm(alarmParams: ReadableMap, promise: Promise) {
75
+ try {
76
+ val id = alarmParams.getString("id") ?: run {
77
+ promise.reject("INVALID_PARAMS", "Alarm ID is required")
78
+ return
79
+ }
80
+ val datetimeISO = alarmParams.getString("datetimeISO") ?: run {
81
+ promise.reject("INVALID_PARAMS", "datetimeISO is required")
82
+ return
83
+ }
84
+ val title = alarmParams.getString("title") ?: "Alarm"
85
+ val body = alarmParams.getString("body") ?: ""
86
+ val soundUri = if (alarmParams.hasKey("soundUri")) alarmParams.getString("soundUri") else null
87
+ val vibrate = if (alarmParams.hasKey("vibrate")) alarmParams.getBoolean("vibrate") else true
88
+ val snoozeMinutes = if (alarmParams.hasKey("snoozeMinutes")) alarmParams.getInt("snoozeMinutes") else 5
89
+ val autoStopSeconds = if (alarmParams.hasKey("autoStopSeconds")) alarmParams.getInt("autoStopSeconds") else 60
90
+
91
+ val triggerTime = parseDateTime(datetimeISO)
92
+ if (triggerTime == null) {
93
+ promise.reject("INVALID_DATE", "Could not parse datetimeISO: $datetimeISO")
94
+ return
95
+ }
96
+
97
+ // Store alarm in SharedPreferences
98
+ val alarm = JSONObject().apply {
99
+ put("id", id)
100
+ put("datetimeISO", datetimeISO)
101
+ put("triggerTime", triggerTime)
102
+ put("title", title)
103
+ put("body", body)
104
+ put("soundUri", soundUri ?: "")
105
+ put("vibrate", vibrate)
106
+ put("snoozeMinutes", snoozeMinutes)
107
+ put("autoStopSeconds", autoStopSeconds)
108
+ }
109
+ saveAlarm(alarm)
110
+
111
+ // Schedule the alarm
112
+ scheduleExactAlarm(id, triggerTime, title, body, soundUri, vibrate, snoozeMinutes, autoStopSeconds)
113
+
114
+ Log.d(TAG, "Alarm scheduled: id=$id, triggerTime=$triggerTime")
115
+ promise.resolve(null)
116
+ } catch (e: Exception) {
117
+ Log.e(TAG, "Failed to schedule alarm: ${e.message}", e)
118
+ promise.reject("SCHEDULE_ERROR", "Failed to schedule alarm: ${e.message}", e)
119
+ }
120
+ }
121
+
122
+ @ReactMethod
123
+ fun cancelAlarm(id: String, promise: Promise) {
124
+ try {
125
+ cancelScheduledAlarm(id)
126
+ removeAlarm(id)
127
+ Log.d(TAG, "Alarm cancelled: id=$id")
128
+ promise.resolve(null)
129
+ } catch (e: Exception) {
130
+ Log.e(TAG, "Failed to cancel alarm: ${e.message}", e)
131
+ promise.reject("CANCEL_ERROR", "Failed to cancel alarm: ${e.message}", e)
132
+ }
133
+ }
134
+
135
+ @ReactMethod
136
+ fun listAlarms(promise: Promise) {
137
+ try {
138
+ val alarms = getStoredAlarms()
139
+ val result = Arguments.createArray()
140
+ for (i in 0 until alarms.length()) {
141
+ val alarm = alarms.getJSONObject(i)
142
+ val map = Arguments.createMap().apply {
143
+ putString("id", alarm.getString("id"))
144
+ putString("datetimeISO", alarm.getString("datetimeISO"))
145
+ putString("title", alarm.optString("title", "Alarm"))
146
+ putString("body", alarm.optString("body", ""))
147
+ putBoolean("vibrate", alarm.optBoolean("vibrate", true))
148
+ putInt("snoozeMinutes", alarm.optInt("snoozeMinutes", 5))
149
+ putInt("autoStopSeconds", alarm.optInt("autoStopSeconds", 60))
150
+ }
151
+ result.pushMap(map)
152
+ }
153
+ promise.resolve(result)
154
+ } catch (e: Exception) {
155
+ Log.e(TAG, "Failed to list alarms: ${e.message}", e)
156
+ promise.reject("LIST_ERROR", "Failed to list alarms: ${e.message}", e)
157
+ }
158
+ }
159
+
160
+ @ReactMethod
161
+ fun requestPermissions(promise: Promise) {
162
+ try {
163
+ val result = Arguments.createMap()
164
+
165
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
166
+ val alarmManager = reactApplicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
167
+ val canScheduleExact = alarmManager.canScheduleExactAlarms()
168
+ result.putBoolean("granted", canScheduleExact)
169
+ result.putBoolean("exactAlarmGranted", canScheduleExact)
170
+
171
+ if (!canScheduleExact) {
172
+ // Open settings to grant exact alarm permission
173
+ try {
174
+ val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
175
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
176
+ }
177
+ reactApplicationContext.startActivity(intent)
178
+ } catch (e: Exception) {
179
+ Log.e(TAG, "Failed to open exact alarm settings: ${e.message}")
180
+ }
181
+ }
182
+ } else {
183
+ result.putBoolean("granted", true)
184
+ result.putBoolean("exactAlarmGranted", true)
185
+ }
186
+
187
+ promise.resolve(result)
188
+ } catch (e: Exception) {
189
+ Log.e(TAG, "Failed to request permissions: ${e.message}", e)
190
+ promise.reject("PERMISSION_ERROR", "Failed to request permissions: ${e.message}", e)
191
+ }
192
+ }
193
+
194
+ @ReactMethod
195
+ fun checkExactAlarmPermission(promise: Promise) {
196
+ try {
197
+ val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
198
+ val alarmManager = reactApplicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
199
+ alarmManager.canScheduleExactAlarms()
200
+ } else {
201
+ true
202
+ }
203
+ promise.resolve(result)
204
+ } catch (e: Exception) {
205
+ Log.e(TAG, "Failed to check exact alarm permission: ${e.message}", e)
206
+ promise.reject("PERMISSION_ERROR", "Failed to check permission: ${e.message}", e)
207
+ }
208
+ }
209
+
210
+ @ReactMethod
211
+ fun openExactAlarmSettings(promise: Promise) {
212
+ try {
213
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
214
+ val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
215
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
216
+ }
217
+ reactApplicationContext.startActivity(intent)
218
+ }
219
+ promise.resolve(null)
220
+ } catch (e: Exception) {
221
+ Log.e(TAG, "Failed to open exact alarm settings: ${e.message}", e)
222
+ promise.reject("SETTINGS_ERROR", "Failed to open settings: ${e.message}", e)
223
+ }
224
+ }
225
+
226
+ @ReactMethod
227
+ fun snoozeAlarm(id: String, minutes: Int, promise: Promise) {
228
+ try {
229
+ val alarm = getAlarmById(id)
230
+ if (alarm == null) {
231
+ promise.reject("NOT_FOUND", "Alarm not found: $id")
232
+ return
233
+ }
234
+
235
+ val snoozeTime = System.currentTimeMillis() + (minutes * 60 * 1000L)
236
+ val newDatetimeISO = dateFormat.format(Date(snoozeTime))
237
+
238
+ // Update alarm with new time
239
+ alarm.put("datetimeISO", newDatetimeISO)
240
+ alarm.put("triggerTime", snoozeTime)
241
+ updateAlarm(alarm)
242
+
243
+ // Reschedule
244
+ scheduleExactAlarm(
245
+ id,
246
+ snoozeTime,
247
+ alarm.optString("title", "Alarm"),
248
+ alarm.optString("body", ""),
249
+ alarm.optString("soundUri", null),
250
+ alarm.optBoolean("vibrate", true),
251
+ alarm.optInt("snoozeMinutes", 5),
252
+ alarm.optInt("autoStopSeconds", 60)
253
+ )
254
+
255
+ Log.d(TAG, "Alarm snoozed: id=$id, new trigger=$snoozeTime")
256
+ promise.resolve(null)
257
+ } catch (e: Exception) {
258
+ Log.e(TAG, "Failed to snooze alarm: ${e.message}", e)
259
+ promise.reject("SNOOZE_ERROR", "Failed to snooze alarm: ${e.message}", e)
260
+ }
261
+ }
262
+
263
+ @ReactMethod
264
+ fun stopCurrentAlarm(id: String, promise: Promise) {
265
+ try {
266
+ // Stop the alarm service
267
+ val stopIntent = Intent(reactApplicationContext, AlarmService::class.java).apply {
268
+ action = AlarmService.ACTION_STOP
269
+ putExtra("alarmId", id)
270
+ }
271
+ reactApplicationContext.stopService(stopIntent)
272
+
273
+ // Clear active alarm state
274
+ prefs.edit().remove(ACTIVE_ALARM_KEY).apply()
275
+ emitActiveAlarmId(null)
276
+
277
+ Log.d(TAG, "Alarm stopped: id=$id")
278
+ promise.resolve(null)
279
+ } catch (e: Exception) {
280
+ Log.e(TAG, "Failed to stop alarm: ${e.message}", e)
281
+ promise.reject("STOP_ERROR", "Failed to stop alarm: ${e.message}", e)
282
+ }
283
+ }
284
+
285
+ @ReactMethod
286
+ fun snoozeCurrentAlarm(id: String, minutes: Int, promise: Promise) {
287
+ try {
288
+ // Stop the current alarm
289
+ val stopIntent = Intent(reactApplicationContext, AlarmService::class.java).apply {
290
+ action = AlarmService.ACTION_STOP
291
+ putExtra("alarmId", id)
292
+ }
293
+ reactApplicationContext.stopService(stopIntent)
294
+
295
+ // Clear active alarm state
296
+ prefs.edit().remove(ACTIVE_ALARM_KEY).apply()
297
+ emitActiveAlarmId(null)
298
+
299
+ // Schedule snooze
300
+ snoozeAlarm(id, minutes, promise)
301
+ } catch (e: Exception) {
302
+ Log.e(TAG, "Failed to snooze current alarm: ${e.message}", e)
303
+ promise.reject("SNOOZE_ERROR", "Failed to snooze current alarm: ${e.message}", e)
304
+ }
305
+ }
306
+
307
+ @ReactMethod
308
+ fun getCurrentAlarmPlaying(promise: Promise) {
309
+ try {
310
+ val activeAlarmId = prefs.getString(ACTIVE_ALARM_KEY, null)
311
+ if (activeAlarmId != null) {
312
+ val result = Arguments.createMap().apply {
313
+ putString("activeAlarmId", activeAlarmId)
314
+ }
315
+ promise.resolve(result)
316
+ } else {
317
+ promise.resolve(null)
318
+ }
319
+ } catch (e: Exception) {
320
+ Log.e(TAG, "Failed to get current alarm: ${e.message}", e)
321
+ promise.reject("GET_ERROR", "Failed to get current alarm: ${e.message}", e)
322
+ }
323
+ }
324
+
325
+ private fun parseDateTime(datetimeISO: String): Long? {
326
+ return try {
327
+ // Try parsing with timezone info
328
+ val formats = listOf(
329
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.getDefault()),
330
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.getDefault()),
331
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()),
332
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()),
333
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault()),
334
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()),
335
+ SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
336
+ )
337
+
338
+ for (format in formats) {
339
+ try {
340
+ val date = format.parse(datetimeISO)
341
+ if (date != null) {
342
+ return date.time
343
+ }
344
+ } catch (e: Exception) {
345
+ // Try next format
346
+ }
347
+ }
348
+ null
349
+ } catch (e: Exception) {
350
+ Log.e(TAG, "Failed to parse date: $datetimeISO", e)
351
+ null
352
+ }
353
+ }
354
+
355
+ private fun scheduleExactAlarm(
356
+ id: String,
357
+ triggerTimeMillis: Long,
358
+ title: String,
359
+ body: String,
360
+ soundUri: String?,
361
+ vibrate: Boolean,
362
+ snoozeMinutes: Int,
363
+ autoStopSeconds: Int
364
+ ) {
365
+ val alarmManager = reactApplicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
366
+
367
+ val intent = Intent(reactApplicationContext, AlarmReceiver::class.java).apply {
368
+ action = "com.rnalarmmodule.ALARM_TRIGGER"
369
+ putExtra("alarmId", id)
370
+ putExtra("title", title)
371
+ putExtra("body", body)
372
+ putExtra("soundUri", soundUri ?: "")
373
+ putExtra("vibrate", vibrate)
374
+ putExtra("snoozeMinutes", snoozeMinutes)
375
+ putExtra("autoStopSeconds", autoStopSeconds)
376
+ }
377
+
378
+ val pendingIntent = PendingIntent.getBroadcast(
379
+ reactApplicationContext,
380
+ id.hashCode(),
381
+ intent,
382
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
383
+ )
384
+
385
+ try {
386
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
387
+ if (alarmManager.canScheduleExactAlarms()) {
388
+ alarmManager.setAlarmClock(
389
+ AlarmManager.AlarmClockInfo(triggerTimeMillis, pendingIntent),
390
+ pendingIntent
391
+ )
392
+ } else {
393
+ // Fallback to inexact alarm if exact permission not granted
394
+ alarmManager.setAndAllowWhileIdle(
395
+ AlarmManager.RTC_WAKEUP,
396
+ triggerTimeMillis,
397
+ pendingIntent
398
+ )
399
+ Log.w(TAG, "Exact alarm permission not granted, using inexact alarm")
400
+ }
401
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
402
+ alarmManager.setAlarmClock(
403
+ AlarmManager.AlarmClockInfo(triggerTimeMillis, pendingIntent),
404
+ pendingIntent
405
+ )
406
+ } else {
407
+ alarmManager.setExact(
408
+ AlarmManager.RTC_WAKEUP,
409
+ triggerTimeMillis,
410
+ pendingIntent
411
+ )
412
+ }
413
+ } catch (e: SecurityException) {
414
+ Log.e(TAG, "Security exception scheduling alarm: ${e.message}", e)
415
+ throw e
416
+ }
417
+ }
418
+
419
+ private fun cancelScheduledAlarm(id: String) {
420
+ val alarmManager = reactApplicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
421
+
422
+ val intent = Intent(reactApplicationContext, AlarmReceiver::class.java).apply {
423
+ action = "com.rnalarmmodule.ALARM_TRIGGER"
424
+ }
425
+
426
+ val pendingIntent = PendingIntent.getBroadcast(
427
+ reactApplicationContext,
428
+ id.hashCode(),
429
+ intent,
430
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
431
+ )
432
+
433
+ alarmManager.cancel(pendingIntent)
434
+ pendingIntent.cancel()
435
+ }
436
+
437
+ private fun getStoredAlarms(): JSONArray {
438
+ val alarmsJson = prefs.getString(ALARMS_KEY, "[]") ?: "[]"
439
+ return JSONArray(alarmsJson)
440
+ }
441
+
442
+ private fun saveAlarm(alarm: JSONObject) {
443
+ val alarms = getStoredAlarms()
444
+ val id = alarm.getString("id")
445
+
446
+ // Remove existing alarm with same ID
447
+ val filteredAlarms = JSONArray()
448
+ for (i in 0 until alarms.length()) {
449
+ val existingAlarm = alarms.getJSONObject(i)
450
+ if (existingAlarm.getString("id") != id) {
451
+ filteredAlarms.put(existingAlarm)
452
+ }
453
+ }
454
+
455
+ filteredAlarms.put(alarm)
456
+ prefs.edit().putString(ALARMS_KEY, filteredAlarms.toString()).apply()
457
+ }
458
+
459
+ private fun updateAlarm(alarm: JSONObject) {
460
+ saveAlarm(alarm)
461
+ }
462
+
463
+ private fun removeAlarm(id: String) {
464
+ val alarms = getStoredAlarms()
465
+ val filteredAlarms = JSONArray()
466
+ for (i in 0 until alarms.length()) {
467
+ val existingAlarm = alarms.getJSONObject(i)
468
+ if (existingAlarm.getString("id") != id) {
469
+ filteredAlarms.put(existingAlarm)
470
+ }
471
+ }
472
+ prefs.edit().putString(ALARMS_KEY, filteredAlarms.toString()).apply()
473
+ }
474
+
475
+ private fun getAlarmById(id: String): JSONObject? {
476
+ val alarms = getStoredAlarms()
477
+ for (i in 0 until alarms.length()) {
478
+ val alarm = alarms.getJSONObject(i)
479
+ if (alarm.getString("id") == id) {
480
+ return alarm
481
+ }
482
+ }
483
+ return null
484
+ }
485
+ }
@@ -0,0 +1,17 @@
1
+ package com.rnalarmmodule
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class AlarmPackage : ReactPackage {
9
+
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(AlarmModule(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return emptyList()
16
+ }
17
+ }
@@ -0,0 +1,183 @@
1
+ package com.rnalarmmodule
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.os.Build
7
+ import android.os.PowerManager
8
+ import android.util.Log
9
+
10
+ class AlarmReceiver : BroadcastReceiver() {
11
+
12
+ companion object {
13
+ private const val TAG = "AlarmReceiver"
14
+ const val ACTION_TRIGGER = "com.rnalarmmodule.ALARM_TRIGGER"
15
+ const val ACTION_STOP = "com.rnalarmmodule.ALARM_STOP"
16
+ const val ACTION_SNOOZE = "com.rnalarmmodule.ALARM_SNOOZE"
17
+ }
18
+
19
+ override fun onReceive(context: Context, intent: Intent) {
20
+ Log.d(TAG, "onReceive: action=${intent.action}")
21
+
22
+ // Acquire wake lock to ensure device stays awake
23
+ val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
24
+ val wakeLock = powerManager.newWakeLock(
25
+ PowerManager.PARTIAL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
26
+ "AlarmModule:AlarmWakeLock"
27
+ )
28
+ wakeLock.acquire(60 * 1000L) // 60 seconds max
29
+
30
+ try {
31
+ when (intent.action) {
32
+ ACTION_TRIGGER -> handleAlarmTrigger(context, intent)
33
+ ACTION_STOP -> handleAlarmStop(context, intent)
34
+ ACTION_SNOOZE -> handleAlarmSnooze(context, intent)
35
+ else -> {
36
+ Log.w(TAG, "Unknown action: ${intent.action}")
37
+ }
38
+ }
39
+ } catch (e: Exception) {
40
+ Log.e(TAG, "Error handling alarm: ${e.message}", e)
41
+ } finally {
42
+ if (wakeLock.isHeld) {
43
+ wakeLock.release()
44
+ }
45
+ }
46
+ }
47
+
48
+ private fun handleAlarmTrigger(context: Context, intent: Intent) {
49
+ val alarmId = intent.getStringExtra("alarmId") ?: return
50
+ val title = intent.getStringExtra("title") ?: "Alarm"
51
+ val body = intent.getStringExtra("body") ?: ""
52
+ val soundUri = intent.getStringExtra("soundUri") ?: ""
53
+ val vibrate = intent.getBooleanExtra("vibrate", true)
54
+ val snoozeMinutes = intent.getIntExtra("snoozeMinutes", 5)
55
+ val autoStopSeconds = intent.getIntExtra("autoStopSeconds", 60)
56
+
57
+ Log.d(TAG, "Alarm triggered: id=$alarmId, title=$title")
58
+
59
+ // Store active alarm ID in SharedPreferences
60
+ val prefs = context.getSharedPreferences("rn_alarm_module_alarms", Context.MODE_PRIVATE)
61
+ prefs.edit().putString("active_alarm_id", alarmId).apply()
62
+
63
+ // Start the alarm service
64
+ val serviceIntent = Intent(context, AlarmService::class.java).apply {
65
+ action = AlarmService.ACTION_START
66
+ putExtra("alarmId", alarmId)
67
+ putExtra("title", title)
68
+ putExtra("body", body)
69
+ putExtra("soundUri", soundUri)
70
+ putExtra("vibrate", vibrate)
71
+ putExtra("snoozeMinutes", snoozeMinutes)
72
+ putExtra("autoStopSeconds", autoStopSeconds)
73
+ }
74
+
75
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
76
+ context.startForegroundService(serviceIntent)
77
+ } else {
78
+ context.startService(serviceIntent)
79
+ }
80
+
81
+ // Try to emit event to React Native
82
+ AlarmModule.emitActiveAlarmId(alarmId)
83
+
84
+ // Launch full-screen activity to wake up screen
85
+ launchAlarmActivity(context, alarmId, title, body, snoozeMinutes)
86
+ }
87
+
88
+ private fun handleAlarmStop(context: Context, intent: Intent) {
89
+ val alarmId = intent.getStringExtra("alarmId") ?: return
90
+ Log.d(TAG, "Stopping alarm: id=$alarmId")
91
+
92
+ // Stop the alarm service
93
+ val serviceIntent = Intent(context, AlarmService::class.java).apply {
94
+ action = AlarmService.ACTION_STOP
95
+ putExtra("alarmId", alarmId)
96
+ }
97
+ context.stopService(serviceIntent)
98
+
99
+ // Clear active alarm state
100
+ val prefs = context.getSharedPreferences("rn_alarm_module_alarms", Context.MODE_PRIVATE)
101
+ prefs.edit().remove("active_alarm_id").apply()
102
+
103
+ // Emit null to indicate alarm stopped
104
+ AlarmModule.emitActiveAlarmId(null)
105
+ }
106
+
107
+ private fun handleAlarmSnooze(context: Context, intent: Intent) {
108
+ val alarmId = intent.getStringExtra("alarmId") ?: return
109
+ val snoozeMinutes = intent.getIntExtra("snoozeMinutes", 5)
110
+ Log.d(TAG, "Snoozing alarm: id=$alarmId, minutes=$snoozeMinutes")
111
+
112
+ // Stop current alarm
113
+ handleAlarmStop(context, intent)
114
+
115
+ // Reschedule the alarm
116
+ val snoozeTime = System.currentTimeMillis() + (snoozeMinutes * 60 * 1000L)
117
+
118
+ val triggerIntent = Intent(context, AlarmReceiver::class.java).apply {
119
+ action = ACTION_TRIGGER
120
+ putExtra("alarmId", alarmId)
121
+ putExtra("title", intent.getStringExtra("title") ?: "Alarm")
122
+ putExtra("body", intent.getStringExtra("body") ?: "")
123
+ putExtra("soundUri", intent.getStringExtra("soundUri") ?: "")
124
+ putExtra("vibrate", intent.getBooleanExtra("vibrate", true))
125
+ putExtra("snoozeMinutes", snoozeMinutes)
126
+ putExtra("autoStopSeconds", intent.getIntExtra("autoStopSeconds", 60))
127
+ }
128
+
129
+ val pendingIntent = android.app.PendingIntent.getBroadcast(
130
+ context,
131
+ alarmId.hashCode(),
132
+ triggerIntent,
133
+ android.app.PendingIntent.FLAG_UPDATE_CURRENT or android.app.PendingIntent.FLAG_IMMUTABLE
134
+ )
135
+
136
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as android.app.AlarmManager
137
+
138
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
139
+ if (alarmManager.canScheduleExactAlarms()) {
140
+ alarmManager.setAlarmClock(
141
+ android.app.AlarmManager.AlarmClockInfo(snoozeTime, pendingIntent),
142
+ pendingIntent
143
+ )
144
+ } else {
145
+ alarmManager.setAndAllowWhileIdle(
146
+ android.app.AlarmManager.RTC_WAKEUP,
147
+ snoozeTime,
148
+ pendingIntent
149
+ )
150
+ }
151
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
152
+ alarmManager.setAlarmClock(
153
+ android.app.AlarmManager.AlarmClockInfo(snoozeTime, pendingIntent),
154
+ pendingIntent
155
+ )
156
+ } else {
157
+ alarmManager.setExact(
158
+ android.app.AlarmManager.RTC_WAKEUP,
159
+ snoozeTime,
160
+ pendingIntent
161
+ )
162
+ }
163
+
164
+ Log.d(TAG, "Alarm snoozed and rescheduled for: $snoozeTime")
165
+ }
166
+
167
+ private fun launchAlarmActivity(context: Context, alarmId: String, title: String, body: String, snoozeMinutes: Int) {
168
+ try {
169
+ val activityIntent = Intent(context, AlarmActivity::class.java).apply {
170
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or
171
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or
172
+ Intent.FLAG_ACTIVITY_SINGLE_TOP
173
+ putExtra("alarmId", alarmId)
174
+ putExtra("title", title)
175
+ putExtra("body", body)
176
+ putExtra("snoozeMinutes", snoozeMinutes)
177
+ }
178
+ context.startActivity(activityIntent)
179
+ } catch (e: Exception) {
180
+ Log.e(TAG, "Failed to launch alarm activity: ${e.message}", e)
181
+ }
182
+ }
183
+ }