react-native-alarmageddon 1.1.1 → 2.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.
Files changed (148) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +169 -189
  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/generated/source/buildConfig/debug/com/rnalarmmodule/BuildConfig.java +10 -0
  6. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +41 -0
  7. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  8. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  9. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  10. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  11. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  12. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +1 -0
  13. package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/raw_alarm_default.wav.flat +0 -0
  14. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -0
  15. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  16. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
  17. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  18. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  19. package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/react-native-alarmageddon_debug.kotlin_module +0 -0
  20. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/rnalarmmodule/BuildConfig.class +0 -0
  21. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +3 -0
  22. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +68 -0
  23. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +41 -0
  24. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  25. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  26. package/android/build/intermediates/packaged_res/debug/packageDebugResources/raw/alarm_default.wav +0 -0
  27. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  28. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +2 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  98. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  99. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  100. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  101. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  102. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  103. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  104. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  105. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  106. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  107. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  108. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  109. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  110. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  111. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  112. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  113. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  114. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  115. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  116. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  117. package/android/build/outputs/logs/manifest-merger-debug-report.txt +71 -0
  118. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  119. package/android/build/tmp/kotlin-classes/debug/META-INF/react-native-alarmageddon_debug.kotlin_module +0 -0
  120. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmModule$Companion.class +0 -0
  121. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmModule.class +0 -0
  122. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmPackage.class +0 -0
  123. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmReceiver$Companion.class +0 -0
  124. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/AlarmReceiver.class +0 -0
  125. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/BootReceiver$Companion.class +0 -0
  126. package/android/build/tmp/kotlin-classes/debug/com/rnalarmmodule/BootReceiver.class +0 -0
  127. package/android/build.gradle +11 -21
  128. package/android/src/main/AndroidManifest.xml +16 -35
  129. package/android/src/main/java/com/rnalarmmodule/AlarmModule.kt +154 -390
  130. package/android/src/main/java/com/rnalarmmodule/AlarmPackage.kt +0 -1
  131. package/android/src/main/java/com/rnalarmmodule/AlarmReceiver.kt +290 -129
  132. package/android/src/main/java/com/rnalarmmodule/BootReceiver.kt +66 -124
  133. package/android/src/main/res/raw/alarm_default.wav +0 -0
  134. package/ios/{RNAlarmModule.m → AlarmModule.m} +9 -20
  135. package/ios/AlarmModule.swift +190 -36
  136. package/ios/RNAlarmModule-Bridging-Header.h +5 -1
  137. package/lib/index.d.ts +47 -85
  138. package/lib/index.d.ts.map +1 -1
  139. package/lib/index.js +60 -120
  140. package/lib/index.js.map +1 -1
  141. package/package.json +14 -23
  142. package/react-native-alarmageddon.podspec +10 -8
  143. package/react-native.config.js +3 -1
  144. package/src/index.ts +94 -201
  145. package/android/gradle.properties +0 -13
  146. package/android/src/main/java/com/rnalarmmodule/AlarmActivity.kt +0 -79
  147. package/android/src/main/java/com/rnalarmmodule/AlarmService.kt +0 -290
  148. 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,233 @@
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
22
+ // MARK: - Permission Methods
23
+
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
+ }
23
34
  }
24
35
 
25
- override func stopObserving() {
26
- hasListeners = false
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
+
51
+ // Parse ISO date
52
+ let dateFormatter = ISO8601DateFormatter()
53
+ dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
54
+
55
+ var triggerDate: Date?
56
+ triggerDate = dateFormatter.date(from: datetimeISO)
57
+
58
+ // Try without fractional seconds if first parse fails
59
+ if triggerDate == nil {
60
+ dateFormatter.formatOptions = [.withInternetDateTime]
61
+ triggerDate = dateFormatter.date(from: datetimeISO)
62
+ }
63
+
64
+ // Try basic format
65
+ if triggerDate == nil {
66
+ let basicFormatter = DateFormatter()
67
+ basicFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
68
+ basicFormatter.locale = Locale(identifier: "en_US_POSIX")
69
+ triggerDate = basicFormatter.date(from: datetimeISO)
70
+ }
71
+
72
+ guard let date = triggerDate else {
73
+ reject("INVALID_DATE", "Could not parse datetimeISO: \(datetimeISO)", nil)
74
+ return
75
+ }
76
+
77
+ // Create notification content
78
+ let content = UNMutableNotificationContent()
79
+ content.title = title
80
+ content.body = body
81
+ content.sound = UNNotificationSound.default
82
+ content.categoryIdentifier = "ALARM_CATEGORY"
83
+ content.userInfo = ["alarmId": id]
84
+
85
+ // Create trigger
86
+ let calendar = Calendar.current
87
+ let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
88
+ let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
89
+
90
+ // Create request
91
+ let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
92
+
93
+ UNUserNotificationCenter.current().add(request) { error in
94
+ if let error = error {
95
+ reject("SCHEDULE_ERROR", error.localizedDescription, error)
96
+ return
97
+ }
98
+
99
+ // Save alarm to UserDefaults
100
+ self.saveAlarm(id: id, datetimeISO: datetimeISO, title: title, body: body)
101
+ resolve(nil)
102
+ }
27
103
  }
28
104
 
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)
105
+ @objc(cancelAlarm:resolver:rejecter:)
106
+ func cancelAlarm(_ id: String,
107
+ resolver resolve: @escaping RCTPromiseResolveBlock,
108
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
109
+ UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [id])
110
+ removeAlarm(id: id)
111
+ resolve(nil)
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
+ @objc(listAlarms:rejecter:)
115
+ func listAlarms(_ resolve: @escaping RCTPromiseResolveBlock,
116
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
117
+ let alarms = getAlarms()
118
+ resolve(alarms)
38
119
  }
39
120
 
40
- @objc func listAlarms(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
41
- // iOS stub - return empty array
42
- resolve([])
121
+ // MARK: - Snooze Methods
122
+
123
+ @objc(snoozeAlarm:minutes:resolver:rejecter:)
124
+ func snoozeAlarm(_ id: String,
125
+ minutes: Int,
126
+ resolver resolve: @escaping RCTPromiseResolveBlock,
127
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
128
+ // Create a new alarm in X minutes
129
+ let content = UNMutableNotificationContent()
130
+ content.title = "Snoozed Alarm"
131
+ content.body = id
132
+ content.sound = UNNotificationSound.default
133
+ content.categoryIdentifier = "ALARM_CATEGORY"
134
+
135
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(minutes * 60), repeats: false)
136
+ let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
137
+
138
+ UNUserNotificationCenter.current().add(request) { error in
139
+ if let error = error {
140
+ reject("SNOOZE_ERROR", error.localizedDescription, error)
141
+ return
142
+ }
143
+ resolve(nil)
144
+ }
43
145
  }
44
146
 
45
- @objc func requestPermissions(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
46
- // iOS stub - would request notification permissions
47
- resolve(["granted": true, "exactAlarmGranted": true])
147
+ // MARK: - Current Alarm Methods
148
+
149
+ @objc(stopCurrentAlarm:resolver:rejecter:)
150
+ func stopCurrentAlarm(_ id: String,
151
+ resolver resolve: @escaping RCTPromiseResolveBlock,
152
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
153
+ // iOS doesn't have persistent alarm sounds like Android
154
+ // Remove the delivered notification
155
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [id])
156
+ resolve(nil)
48
157
  }
49
158
 
50
- @objc func checkExactAlarmPermission(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
51
- // iOS doesn't have exact alarm permission concept
52
- resolve(true)
159
+ @objc(snoozeCurrentAlarm:minutes:resolver:rejecter:)
160
+ func snoozeCurrentAlarm(_ id: String,
161
+ minutes: Int,
162
+ resolver resolve: @escaping RCTPromiseResolveBlock,
163
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
164
+ // Remove current notification
165
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [id])
166
+
167
+ // Get alarm details
168
+ if let alarm = getAlarmById(id: id) {
169
+ let title = alarm["title"] as? String ?? "Alarm"
170
+ let body = (alarm["body"] as? String ?? "") + " (Snoozed)"
171
+
172
+ let content = UNMutableNotificationContent()
173
+ content.title = title
174
+ content.body = body
175
+ content.sound = UNNotificationSound.default
176
+ content.categoryIdentifier = "ALARM_CATEGORY"
177
+ content.userInfo = ["alarmId": id]
178
+
179
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(minutes * 60), repeats: false)
180
+ let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
181
+
182
+ UNUserNotificationCenter.current().add(request) { error in
183
+ if let error = error {
184
+ reject("SNOOZE_ERROR", error.localizedDescription, error)
185
+ return
186
+ }
187
+ resolve(nil)
188
+ }
189
+ } else {
190
+ // Alarm not found, just snooze with generic content
191
+ snoozeAlarm(id, minutes: minutes, resolver: resolve, rejecter: reject)
192
+ }
53
193
  }
54
194
 
55
- @objc func openExactAlarmSettings(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
56
- // iOS stub - no equivalent
195
+ @objc(getCurrentAlarmPlaying:rejecter:)
196
+ func getCurrentAlarmPlaying(_ resolve: @escaping RCTPromiseResolveBlock,
197
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
198
+ // iOS doesn't have a concept of "currently playing" alarm like Android
199
+ // Return nil as there's no persistent alarm sound
57
200
  resolve(nil)
58
201
  }
59
202
 
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)
203
+ // MARK: - Storage Helpers
204
+
205
+ private func saveAlarm(id: String, datetimeISO: String, title: String, body: String) {
206
+ var alarms = getAlarmsDict()
207
+ alarms[id] = [
208
+ "id": id,
209
+ "datetimeISO": datetimeISO,
210
+ "title": title,
211
+ "body": body
212
+ ]
213
+ UserDefaults.standard.set(alarms, forKey: AlarmModule.PREFS_KEY)
63
214
  }
64
215
 
65
- @objc func stopCurrentAlarm(_ alarmId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
66
- // iOS stub
67
- resolve(nil)
216
+ private func removeAlarm(id: String) {
217
+ var alarms = getAlarmsDict()
218
+ alarms.removeValue(forKey: id)
219
+ UserDefaults.standard.set(alarms, forKey: AlarmModule.PREFS_KEY)
68
220
  }
69
221
 
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)
222
+ private func getAlarmsDict() -> [String: [String: String]] {
223
+ return UserDefaults.standard.dictionary(forKey: AlarmModule.PREFS_KEY) as? [String: [String: String]] ?? [:]
73
224
  }
74
225
 
75
- @objc func getCurrentAlarmPlaying(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
76
- // iOS stub - no alarm playing
77
- resolve(nil)
226
+ private func getAlarms() -> [[String: String]] {
227
+ return Array(getAlarmsDict().values)
228
+ }
229
+
230
+ private func getAlarmById(id: String) -> [String: String]? {
231
+ return getAlarmsDict()[id]
78
232
  }
79
233
  }
@@ -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>