react-native-alarmageddon 1.1.1 → 2.1.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.
Files changed (150) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +169 -187
  3. package/android/build/.transforms/33d69af0aa8d226a34981fd71aab63e2/results.bin +1 -0
  4. package/android/build/.transforms/33d69af0aa8d226a34981fd71aab63e2/transformed/classes/classes_dex/classes.dex +0 -0
  5. package/android/build/.transforms/4aeb440f8cdf777de34ab1099140d239/results.bin +1 -0
  6. package/android/build/.transforms/4aeb440f8cdf777de34ab1099140d239/transformed/classes/classes_dex/classes.dex +0 -0
  7. package/android/build/generated/source/buildConfig/debug/com/rnalarmmodule/BuildConfig.java +10 -0
  8. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +41 -0
  9. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  10. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  11. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  12. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  13. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  14. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +1 -0
  15. package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_alarm_default.wav.flat +0 -0
  16. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -0
  17. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  18. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
  19. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  20. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  21. package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/react-native-alarmageddon_debug.kotlin_module +0 -0
  22. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/rnalarmmodule/BuildConfig.class +0 -0
  23. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +3 -0
  24. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +68 -0
  25. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +41 -0
  26. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  27. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  28. package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/alarm_default.wav +0 -0
  29. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  30. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +2 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  98. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  99. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  100. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  101. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  102. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  103. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  104. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  105. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  106. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  107. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  108. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  109. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  110. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  111. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  112. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  113. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  114. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  115. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  116. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  117. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  118. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  119. package/android/build/outputs/logs/manifest-merger-debug-report.txt +71 -0
  120. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  121. package/android/build/tmp/kotlin-classes/debug/META-INF/react-native-alarmageddon_debug.kotlin_module +0 -0
  122. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmModule$Companion.class +0 -0
  123. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmModule.class +0 -0
  124. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmPackage.class +0 -0
  125. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmReceiver$Companion.class +0 -0
  126. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmReceiver.class +0 -0
  127. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/BootReceiver$Companion.class +0 -0
  128. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/BootReceiver.class +0 -0
  129. package/android/build.gradle +11 -21
  130. package/android/src/main/AndroidManifest.xml +16 -35
  131. package/android/src/main/java/com/rnalarmmodule/AlarmModule.kt +163 -387
  132. package/android/src/main/java/com/rnalarmmodule/AlarmPackage.kt +0 -1
  133. package/android/src/main/java/com/rnalarmmodule/AlarmReceiver.kt +300 -129
  134. package/android/src/main/java/com/rnalarmmodule/BootReceiver.kt +66 -124
  135. package/android/src/main/res/raw/alarm_default.wav +0 -0
  136. package/ios/{RNAlarmModule.m → AlarmModule.m} +9 -20
  137. package/ios/AlarmModule.swift +241 -36
  138. package/ios/RNAlarmModule-Bridging-Header.h +5 -1
  139. package/lib/index.d.ts +51 -85
  140. package/lib/index.d.ts.map +1 -1
  141. package/lib/index.js +60 -120
  142. package/lib/index.js.map +1 -1
  143. package/package.json +14 -23
  144. package/react-native-alarmageddon.podspec +10 -8
  145. package/react-native.config.js +3 -1
  146. package/src/index.ts +98 -201
  147. package/android/gradle.properties +0 -13
  148. package/android/src/main/java/com/rnalarmmodule/AlarmActivity.kt +0 -79
  149. package/android/src/main/java/com/rnalarmmodule/AlarmService.kt +0 -290
  150. package/android/src/main/res/raw/README.md +0 -36
@@ -1,156 +1,98 @@
1
1
  package com.rnalarmmodule
2
2
 
3
- import android.app.AlarmManager
4
- import android.app.PendingIntent
5
3
  import android.content.BroadcastReceiver
6
4
  import android.content.Context
7
5
  import android.content.Intent
6
+ import android.app.AlarmManager
7
+ import android.app.PendingIntent
8
8
  import android.os.Build
9
+ import org.json.JSONObject
10
+ import java.text.SimpleDateFormat
11
+ import java.util.Locale
9
12
  import android.util.Log
10
- import org.json.JSONArray
11
13
 
12
14
  class BootReceiver : BroadcastReceiver() {
13
15
 
14
16
  companion object {
15
17
  private const val TAG = "BootReceiver"
16
- private const val PREFS_NAME = "rn_alarm_module_alarms"
17
- private const val ALARMS_KEY = "alarms"
18
+ private const val PREFS = "rn_alarm_module_alarms"
19
+ private const val DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"
18
20
  }
19
21
 
20
- override fun onReceive(context: Context, intent: Intent) {
21
- val action = intent.action
22
- Log.d(TAG, "onReceive: action=$action")
23
-
24
- when (action) {
22
+ override fun onReceive(context: Context, intent: Intent?) {
23
+ if (intent == null) return
24
+ when (intent.action) {
25
25
  Intent.ACTION_BOOT_COMPLETED,
26
- Intent.ACTION_LOCKED_BOOT_COMPLETED,
27
26
  Intent.ACTION_TIME_CHANGED,
28
- Intent.ACTION_TIMEZONE_CHANGED,
29
- Intent.ACTION_MY_PACKAGE_REPLACED -> {
30
- rescheduleAllAlarms(context)
31
- }
32
- else -> {
33
- Log.w(TAG, "Unhandled action: $action")
27
+ Intent.ACTION_TIMEZONE_CHANGED -> {
28
+ Log.d(TAG, "Rescheduling alarms after: ${intent.action}")
29
+ rescheduleAlarms(context)
34
30
  }
35
31
  }
36
32
  }
37
33
 
38
- private fun rescheduleAllAlarms(context: Context) {
39
- Log.d(TAG, "Rescheduling all alarms...")
40
-
41
- try {
42
- val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
43
- val alarmsJson = prefs.getString(ALARMS_KEY, "[]") ?: "[]"
44
- val alarms = JSONArray(alarmsJson)
34
+ private fun rescheduleAlarms(context: Context) {
35
+ val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
36
+ val all = prefs.all
37
+ if (all.isEmpty()) {
38
+ Log.d(TAG, "No alarms to reschedule.")
39
+ return
40
+ }
45
41
 
46
- val currentTime = System.currentTimeMillis()
47
- val validAlarms = JSONArray()
48
- var rescheduledCount = 0
42
+ val sdf = SimpleDateFormat(DATE_PATTERN, Locale.getDefault())
43
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
44
+ val now = System.currentTimeMillis()
49
45
 
50
- for (i in 0 until alarms.length()) {
51
- val alarm = alarms.getJSONObject(i)
52
- val id = alarm.getString("id")
53
- val triggerTime = alarm.getLong("triggerTime")
54
- val title = alarm.optString("title", "Alarm")
55
- val body = alarm.optString("body", "")
56
- val soundUri = alarm.optString("soundUri", "")
57
- val vibrate = alarm.optBoolean("vibrate", true)
58
- val snoozeMinutes = alarm.optInt("snoozeMinutes", 5)
59
- val autoStopSeconds = alarm.optInt("autoStopSeconds", 60)
46
+ var rescheduledCount = 0
47
+ var skippedCount = 0
60
48
 
61
- // Only reschedule alarms that are in the future
62
- if (triggerTime > currentTime) {
63
- scheduleAlarm(
64
- context,
65
- id,
66
- triggerTime,
67
- title,
68
- body,
69
- soundUri,
70
- vibrate,
71
- snoozeMinutes,
72
- autoStopSeconds
73
- )
74
- validAlarms.put(alarm)
75
- rescheduledCount++
76
- Log.d(TAG, "Rescheduled alarm: id=$id, triggerTime=$triggerTime")
77
- } else {
78
- Log.d(TAG, "Skipping past alarm: id=$id, triggerTime=$triggerTime")
79
- }
49
+ for ((id, rawJson) in all) {
50
+ val jsonStr = rawJson as? String ?: continue
51
+ val obj = try {
52
+ JSONObject(jsonStr)
53
+ } catch (_: Exception) {
54
+ continue
55
+ }
56
+ val datetimeISO = obj.optString("datetimeISO", "")
57
+ val title = obj.optString("title", "Alarm")
58
+ val body = obj.optString("body", "")
59
+
60
+ val date = try {
61
+ sdf.parse(datetimeISO)
62
+ } catch (_: Exception) {
63
+ null
64
+ } ?: continue
65
+
66
+ val triggerAt = date.time
67
+ if (triggerAt <= now) {
68
+ // Past alarms: skip (could optionally fire immediately if desired)
69
+ skippedCount++
70
+ continue
80
71
  }
81
72
 
82
- // Update stored alarms to only include valid (future) ones
83
- prefs.edit().putString(ALARMS_KEY, validAlarms.toString()).apply()
84
-
85
- Log.d(TAG, "Rescheduled $rescheduledCount alarms")
86
- } catch (e: Exception) {
87
- Log.e(TAG, "Failed to reschedule alarms: ${e.message}", e)
88
- }
89
- }
90
-
91
- private fun scheduleAlarm(
92
- context: Context,
93
- id: String,
94
- triggerTimeMillis: Long,
95
- title: String,
96
- body: String,
97
- soundUri: String,
98
- vibrate: Boolean,
99
- snoozeMinutes: Int,
100
- autoStopSeconds: Int
101
- ) {
102
- val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
103
-
104
- val intent = Intent(context, AlarmReceiver::class.java).apply {
105
- action = AlarmReceiver.ACTION_TRIGGER
106
- putExtra("alarmId", id)
107
- putExtra("title", title)
108
- putExtra("body", body)
109
- putExtra("soundUri", soundUri)
110
- putExtra("vibrate", vibrate)
111
- putExtra("snoozeMinutes", snoozeMinutes)
112
- putExtra("autoStopSeconds", autoStopSeconds)
113
- }
73
+ val alarmIntent = Intent(context, AlarmReceiver::class.java).apply {
74
+ putExtra("id", id)
75
+ putExtra("title", title)
76
+ putExtra("body", body)
77
+ }
114
78
 
115
- val pendingIntent = PendingIntent.getBroadcast(
116
- context,
117
- id.hashCode(),
118
- intent,
119
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
120
- )
79
+ val pendingIntent = PendingIntent.getBroadcast(
80
+ context,
81
+ id.hashCode(),
82
+ alarmIntent,
83
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
84
+ )
121
85
 
122
- try {
123
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
124
- if (alarmManager.canScheduleExactAlarms()) {
125
- alarmManager.setAlarmClock(
126
- AlarmManager.AlarmClockInfo(triggerTimeMillis, pendingIntent),
127
- pendingIntent
128
- )
129
- } else {
130
- // Fallback to inexact alarm if exact permission not granted
131
- alarmManager.setAndAllowWhileIdle(
132
- AlarmManager.RTC_WAKEUP,
133
- triggerTimeMillis,
134
- pendingIntent
135
- )
136
- Log.w(TAG, "Exact alarm permission not granted, using inexact alarm for: $id")
137
- }
138
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
139
- alarmManager.setAlarmClock(
140
- AlarmManager.AlarmClockInfo(triggerTimeMillis, pendingIntent),
141
- pendingIntent
142
- )
86
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
87
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAt, pendingIntent)
143
88
  } else {
144
- alarmManager.setExact(
145
- AlarmManager.RTC_WAKEUP,
146
- triggerTimeMillis,
147
- pendingIntent
148
- )
89
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAt, pendingIntent)
149
90
  }
150
- } catch (e: SecurityException) {
151
- Log.e(TAG, "Security exception scheduling alarm $id: ${e.message}", e)
152
- } catch (e: Exception) {
153
- Log.e(TAG, "Failed to schedule alarm $id: ${e.message}", e)
91
+
92
+ rescheduledCount++
93
+ Log.d(TAG, "Rescheduled alarm id=$id at=$datetimeISO")
154
94
  }
95
+
96
+ Log.d(TAG, "Rescheduling complete: $rescheduledCount rescheduled, $skippedCount skipped (past)")
155
97
  }
156
98
  }
@@ -3,46 +3,35 @@
3
3
 
4
4
  @interface RCT_EXTERN_MODULE(AlarmModule, RCTEventEmitter)
5
5
 
6
+ RCT_EXTERN_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve
7
+ rejecter:(RCTPromiseRejectBlock)reject)
8
+
6
9
  RCT_EXTERN_METHOD(scheduleAlarm:(NSDictionary *)alarm
7
10
  resolver:(RCTPromiseResolveBlock)resolve
8
11
  rejecter:(RCTPromiseRejectBlock)reject)
9
12
 
10
- RCT_EXTERN_METHOD(cancelAlarm:(NSString *)alarmId
13
+ RCT_EXTERN_METHOD(cancelAlarm:(NSString *)id
11
14
  resolver:(RCTPromiseResolveBlock)resolve
12
15
  rejecter:(RCTPromiseRejectBlock)reject)
13
16
 
14
17
  RCT_EXTERN_METHOD(listAlarms:(RCTPromiseResolveBlock)resolve
15
18
  rejecter:(RCTPromiseRejectBlock)reject)
16
19
 
17
- RCT_EXTERN_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve
18
- rejecter:(RCTPromiseRejectBlock)reject)
19
-
20
- RCT_EXTERN_METHOD(checkExactAlarmPermission:(RCTPromiseResolveBlock)resolve
21
- rejecter:(RCTPromiseRejectBlock)reject)
22
-
23
- RCT_EXTERN_METHOD(openExactAlarmSettings:(RCTPromiseResolveBlock)resolve
24
- rejecter:(RCTPromiseRejectBlock)reject)
25
-
26
- RCT_EXTERN_METHOD(snoozeAlarm:(NSString *)alarmId
27
- minutes:(nonnull NSNumber *)minutes
20
+ RCT_EXTERN_METHOD(snoozeAlarm:(NSString *)id
21
+ minutes:(int)minutes
28
22
  resolver:(RCTPromiseResolveBlock)resolve
29
23
  rejecter:(RCTPromiseRejectBlock)reject)
30
24
 
31
- RCT_EXTERN_METHOD(stopCurrentAlarm:(NSString *)alarmId
25
+ RCT_EXTERN_METHOD(stopCurrentAlarm:(NSString *)id
32
26
  resolver:(RCTPromiseResolveBlock)resolve
33
27
  rejecter:(RCTPromiseRejectBlock)reject)
34
28
 
35
- RCT_EXTERN_METHOD(snoozeCurrentAlarm:(NSString *)alarmId
36
- minutes:(nonnull NSNumber *)minutes
29
+ RCT_EXTERN_METHOD(snoozeCurrentAlarm:(NSString *)id
30
+ minutes:(int)minutes
37
31
  resolver:(RCTPromiseResolveBlock)resolve
38
32
  rejecter:(RCTPromiseRejectBlock)reject)
39
33
 
40
34
  RCT_EXTERN_METHOD(getCurrentAlarmPlaying:(RCTPromiseResolveBlock)resolve
41
35
  rejecter:(RCTPromiseRejectBlock)reject)
42
36
 
43
- + (BOOL)requiresMainQueueSetup
44
- {
45
- return NO;
46
- }
47
-
48
37
  @end
@@ -1,79 +1,284 @@
1
1
  import Foundation
2
2
  import React
3
+ import UserNotifications
3
4
 
4
5
  @objc(AlarmModule)
5
6
  class AlarmModule: RCTEventEmitter {
6
7
 
7
- private var hasListeners = false
8
+ private static let PREFS_KEY = "rn_alarm_module_alarms"
8
9
 
9
10
  override init() {
10
11
  super.init()
11
12
  }
12
13
 
13
14
  @objc override static func requiresMainQueueSetup() -> Bool {
14
- return false
15
+ return true
15
16
  }
16
17
 
17
18
  override func supportedEvents() -> [String]! {
18
19
  return ["activeAlarmId"]
19
20
  }
20
21
 
21
- override func startObserving() {
22
- hasListeners = true
23
- }
22
+ // MARK: - Permission Methods
24
23
 
25
- override func stopObserving() {
26
- hasListeners = false
24
+ @objc(requestPermissions:rejecter:)
25
+ func requestPermissions(_ resolve: @escaping RCTPromiseResolveBlock,
26
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
27
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
28
+ if let error = error {
29
+ reject("PERMISSION_ERROR", error.localizedDescription, error)
30
+ return
31
+ }
32
+ resolve(["granted": granted])
33
+ }
27
34
  }
28
35
 
29
- @objc func scheduleAlarm(_ alarm: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
30
- // iOS stub - alarms are not yet implemented
31
- // iOS uses different mechanisms (UNUserNotificationCenter) which require different handling
32
- reject("NOT_IMPLEMENTED", "Alarm scheduling is not yet implemented for iOS. Consider using local notifications.", nil)
36
+ // MARK: - Alarm Scheduling
37
+
38
+ @objc(scheduleAlarm:resolver:rejecter:)
39
+ func scheduleAlarm(_ alarm: NSDictionary,
40
+ resolver resolve: @escaping RCTPromiseResolveBlock,
41
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
42
+ guard let id = alarm["id"] as? String,
43
+ let datetimeISO = alarm["datetimeISO"] as? String else {
44
+ reject("INVALID_PARAMS", "id and datetimeISO are required", nil)
45
+ return
46
+ }
47
+
48
+ let title = alarm["title"] as? String ?? "Alarm"
49
+ let body = alarm["body"] as? String ?? ""
50
+ let snoozeEnabled = alarm["snoozeEnabled"] as? Bool ?? true
51
+ let snoozeInterval = alarm["snoozeInterval"] as? Int ?? 5
52
+
53
+ // Parse ISO date
54
+ let dateFormatter = ISO8601DateFormatter()
55
+ dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
56
+
57
+ var triggerDate: Date?
58
+ triggerDate = dateFormatter.date(from: datetimeISO)
59
+
60
+ // Try without fractional seconds if first parse fails
61
+ if triggerDate == nil {
62
+ dateFormatter.formatOptions = [.withInternetDateTime]
63
+ triggerDate = dateFormatter.date(from: datetimeISO)
64
+ }
65
+
66
+ // Try basic format
67
+ if triggerDate == nil {
68
+ let basicFormatter = DateFormatter()
69
+ basicFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
70
+ basicFormatter.locale = Locale(identifier: "en_US_POSIX")
71
+ triggerDate = basicFormatter.date(from: datetimeISO)
72
+ }
73
+
74
+ guard let date = triggerDate else {
75
+ reject("INVALID_DATE", "Could not parse datetimeISO: \(datetimeISO)", nil)
76
+ return
77
+ }
78
+
79
+ // Create notification content
80
+ let content = UNMutableNotificationContent()
81
+ content.title = title
82
+ content.body = body
83
+ content.sound = UNNotificationSound.default
84
+ content.categoryIdentifier = snoozeEnabled ? "ALARM_CATEGORY_WITH_SNOOZE" : "ALARM_CATEGORY"
85
+ content.userInfo = [
86
+ "alarmId": id,
87
+ "snoozeEnabled": snoozeEnabled,
88
+ "snoozeInterval": snoozeInterval
89
+ ]
90
+
91
+ // Register notification category with appropriate actions
92
+ registerNotificationCategory(snoozeEnabled: snoozeEnabled)
93
+
94
+ // Create trigger
95
+ let calendar = Calendar.current
96
+ let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
97
+ let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
98
+
99
+ // Create request
100
+ let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
101
+
102
+ UNUserNotificationCenter.current().add(request) { error in
103
+ if let error = error {
104
+ reject("SCHEDULE_ERROR", error.localizedDescription, error)
105
+ return
106
+ }
107
+
108
+ // Save alarm to UserDefaults
109
+ self.saveAlarm(id: id, datetimeISO: datetimeISO, title: title, body: body, snoozeEnabled: snoozeEnabled, snoozeInterval: snoozeInterval)
110
+ resolve(nil)
111
+ }
33
112
  }
34
113
 
35
- @objc func cancelAlarm(_ alarmId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
36
- // iOS stub
37
- reject("NOT_IMPLEMENTED", "Alarm cancellation is not yet implemented for iOS.", nil)
114
+ private func registerNotificationCategory(snoozeEnabled: Bool) {
115
+ let stopAction = UNNotificationAction(
116
+ identifier: "STOP_ACTION",
117
+ title: "Stop",
118
+ options: [.destructive, .foreground]
119
+ )
120
+
121
+ if snoozeEnabled {
122
+ let snoozeAction = UNNotificationAction(
123
+ identifier: "SNOOZE_ACTION",
124
+ title: "Snooze",
125
+ options: []
126
+ )
127
+
128
+ let categoryWithSnooze = UNNotificationCategory(
129
+ identifier: "ALARM_CATEGORY_WITH_SNOOZE",
130
+ actions: [stopAction, snoozeAction],
131
+ intentIdentifiers: [],
132
+ options: [.customDismissAction]
133
+ )
134
+ UNUserNotificationCenter.current().setNotificationCategories([categoryWithSnooze])
135
+ } else {
136
+ let categoryWithoutSnooze = UNNotificationCategory(
137
+ identifier: "ALARM_CATEGORY",
138
+ actions: [stopAction],
139
+ intentIdentifiers: [],
140
+ options: [.customDismissAction]
141
+ )
142
+ UNUserNotificationCenter.current().setNotificationCategories([categoryWithoutSnooze])
143
+ }
38
144
  }
39
145
 
40
- @objc func listAlarms(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
41
- // iOS stub - return empty array
42
- resolve([])
146
+ @objc(cancelAlarm:resolver:rejecter:)
147
+ func cancelAlarm(_ id: String,
148
+ resolver resolve: @escaping RCTPromiseResolveBlock,
149
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
150
+ UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [id])
151
+ removeAlarm(id: id)
152
+ resolve(nil)
43
153
  }
44
154
 
45
- @objc func requestPermissions(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
46
- // iOS stub - would request notification permissions
47
- resolve(["granted": true, "exactAlarmGranted": true])
155
+ @objc(listAlarms:rejecter:)
156
+ func listAlarms(_ resolve: @escaping RCTPromiseResolveBlock,
157
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
158
+ let alarms = getAlarms()
159
+ resolve(alarms)
48
160
  }
49
161
 
50
- @objc func checkExactAlarmPermission(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
51
- // iOS doesn't have exact alarm permission concept
52
- resolve(true)
162
+ // MARK: - Snooze Methods
163
+
164
+ @objc(snoozeAlarm:minutes:resolver:rejecter:)
165
+ func snoozeAlarm(_ id: String,
166
+ minutes: Int,
167
+ resolver resolve: @escaping RCTPromiseResolveBlock,
168
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
169
+ // Create a new alarm in X minutes
170
+ let content = UNMutableNotificationContent()
171
+ content.title = "Snoozed Alarm"
172
+ content.body = id
173
+ content.sound = UNNotificationSound.default
174
+ content.categoryIdentifier = "ALARM_CATEGORY"
175
+
176
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(minutes * 60), repeats: false)
177
+ let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
178
+
179
+ UNUserNotificationCenter.current().add(request) { error in
180
+ if let error = error {
181
+ reject("SNOOZE_ERROR", error.localizedDescription, error)
182
+ return
183
+ }
184
+ resolve(nil)
185
+ }
53
186
  }
54
187
 
55
- @objc func openExactAlarmSettings(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
56
- // iOS stub - no equivalent
188
+ // MARK: - Current Alarm Methods
189
+
190
+ @objc(stopCurrentAlarm:resolver:rejecter:)
191
+ func stopCurrentAlarm(_ id: String,
192
+ resolver resolve: @escaping RCTPromiseResolveBlock,
193
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
194
+ // iOS doesn't have persistent alarm sounds like Android
195
+ // Remove the delivered notification
196
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [id])
57
197
  resolve(nil)
58
198
  }
59
199
 
60
- @objc func snoozeAlarm(_ alarmId: String, minutes: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
61
- // iOS stub
62
- reject("NOT_IMPLEMENTED", "Alarm snooze is not yet implemented for iOS.", nil)
200
+ @objc(snoozeCurrentAlarm:minutes:resolver:rejecter:)
201
+ func snoozeCurrentAlarm(_ id: String,
202
+ minutes: Int,
203
+ resolver resolve: @escaping RCTPromiseResolveBlock,
204
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
205
+ // Remove current notification
206
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [id])
207
+
208
+ // Get alarm details
209
+ if let alarm = getAlarmById(id: id) {
210
+ let title = alarm["title"] ?? "Alarm"
211
+ let body = (alarm["body"] ?? "") + " (Snoozed)"
212
+ let snoozeEnabled = (alarm["snoozeEnabled"] as NSString?)?.boolValue ?? true
213
+ let snoozeInterval = Int(alarm["snoozeInterval"] ?? "5") ?? 5
214
+
215
+ let content = UNMutableNotificationContent()
216
+ content.title = title
217
+ content.body = body
218
+ content.sound = UNNotificationSound.default
219
+ content.categoryIdentifier = snoozeEnabled ? "ALARM_CATEGORY_WITH_SNOOZE" : "ALARM_CATEGORY"
220
+ content.userInfo = [
221
+ "alarmId": id,
222
+ "snoozeEnabled": snoozeEnabled,
223
+ "snoozeInterval": snoozeInterval
224
+ ]
225
+
226
+ registerNotificationCategory(snoozeEnabled: snoozeEnabled)
227
+
228
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(minutes * 60), repeats: false)
229
+ let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
230
+
231
+ UNUserNotificationCenter.current().add(request) { error in
232
+ if let error = error {
233
+ reject("SNOOZE_ERROR", error.localizedDescription, error)
234
+ return
235
+ }
236
+ resolve(nil)
237
+ }
238
+ } else {
239
+ // Alarm not found, just snooze with generic content
240
+ snoozeAlarm(id, minutes: minutes, resolver: resolve, rejecter: reject)
241
+ }
63
242
  }
64
243
 
65
- @objc func stopCurrentAlarm(_ alarmId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
66
- // iOS stub
244
+ @objc(getCurrentAlarmPlaying:rejecter:)
245
+ func getCurrentAlarmPlaying(_ resolve: @escaping RCTPromiseResolveBlock,
246
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
247
+ // iOS doesn't have a concept of "currently playing" alarm like Android
248
+ // Return nil as there's no persistent alarm sound
67
249
  resolve(nil)
68
250
  }
69
251
 
70
- @objc func snoozeCurrentAlarm(_ alarmId: String, minutes: NSNumber, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
71
- // iOS stub
72
- reject("NOT_IMPLEMENTED", "Alarm snooze is not yet implemented for iOS.", nil)
252
+ // MARK: - Storage Helpers
253
+
254
+ private func saveAlarm(id: String, datetimeISO: String, title: String, body: String, snoozeEnabled: Bool = true, snoozeInterval: Int = 5) {
255
+ var alarms = getAlarmsDict()
256
+ alarms[id] = [
257
+ "id": id,
258
+ "datetimeISO": datetimeISO,
259
+ "title": title,
260
+ "body": body,
261
+ "snoozeEnabled": String(snoozeEnabled),
262
+ "snoozeInterval": String(snoozeInterval)
263
+ ]
264
+ UserDefaults.standard.set(alarms, forKey: AlarmModule.PREFS_KEY)
73
265
  }
74
266
 
75
- @objc func getCurrentAlarmPlaying(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
76
- // iOS stub - no alarm playing
77
- resolve(nil)
267
+ private func removeAlarm(id: String) {
268
+ var alarms = getAlarmsDict()
269
+ alarms.removeValue(forKey: id)
270
+ UserDefaults.standard.set(alarms, forKey: AlarmModule.PREFS_KEY)
271
+ }
272
+
273
+ private func getAlarmsDict() -> [String: [String: String]] {
274
+ return UserDefaults.standard.dictionary(forKey: AlarmModule.PREFS_KEY) as? [String: [String: String]] ?? [:]
275
+ }
276
+
277
+ private func getAlarms() -> [[String: String]] {
278
+ return Array(getAlarmsDict().values)
279
+ }
280
+
281
+ private func getAlarmById(id: String) -> [String: String]? {
282
+ return getAlarmsDict()[id]
78
283
  }
79
284
  }
@@ -1,3 +1,7 @@
1
+ //
2
+ // Use this file to import your target's public headers that you would like to expose to Swift.
3
+ //
4
+
1
5
  #import <React/RCTBridgeModule.h>
2
6
  #import <React/RCTEventEmitter.h>
3
- #import <React/RCTUtils.h>
7
+ #import <React/RCTViewManager.h>