diff --git a/android/app/build.gradle b/android/app/build.gradle
index 10aeeb95c..52568176f 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -99,6 +99,9 @@ flutter {
}
dependencies {
+ implementation("androidx.work:work-runtime-ktx:2.9.0")
+ implementation 'com.google.android.gms:play-services-wearable:18.1.0'
+ implementation 'com.google.code.gson:gson:2.10.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 655a81847..76d4c9a29 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -53,7 +53,14 @@
android:enabled="true"
android:foregroundServiceType="systemExempted"
android:exported="true">
-
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmReceiver.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmReceiver.kt
index 72d97f95d..fd98aa8e8 100644
--- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmReceiver.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmReceiver.kt
@@ -8,6 +8,8 @@ import android.util.Log
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import com.google.android.gms.wearable.PutDataMapRequest
+import com.google.android.gms.wearable.Wearable
class AlarmReceiver : BroadcastReceiver() {
companion object {
@@ -15,6 +17,31 @@ class AlarmReceiver : BroadcastReceiver() {
private var lastTriggeredType = ""
private const val DUPLICATE_PREVENTION_WINDOW = 10000 // 10 seconds
}
+
+ private fun sendVerdictToWatch(context: Context, alarmId: String, willRing: Boolean, reason: String) {
+ Log.d("ActivityCheck", "Attempting to send verdict to watch for alarm: $alarmId")
+ val path = "/uac/pre_check_verdict"
+
+ val putDataMapRequest = PutDataMapRequest.create(path)
+ putDataMapRequest.dataMap.apply {
+ putString("alarmID", alarmId)
+ putBoolean("willRing", willRing)
+ putString("reason", reason)
+ putLong("timestamp", System.currentTimeMillis())
+ }
+
+ val putDataRequest = putDataMapRequest.asPutDataRequest().setUrgent()
+
+ val dataClient = Wearable.getDataClient(context)
+ dataClient.putDataItem(putDataRequest).apply {
+ addOnSuccessListener {
+ Log.d("ActivityCheck", "Successfully sent verdict to watch: ${it.uri}")
+ }
+ addOnFailureListener {
+ Log.e("ActivityCheck", "Failed to send verdict to watch", it)
+ }
+ }
+ }
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent == null) {
@@ -197,7 +224,10 @@ class AlarmReceiver : BroadcastReceiver() {
}
Log.d("AlarmReceiver", "Decision: shouldRing = $shouldRing")
-
+ val alarmId = intent.getStringExtra("alarmID")
+ if (alarmId != null) {
+ sendVerdictToWatch(context, alarmId, shouldRing, logMessage)
+ }
if (shouldRing) {
println("ANDROID STARTING APP")
context.startActivity(flutterIntent)
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmUtils.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmUtils.kt
index 67a11fa55..83b2802c7 100644
--- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmUtils.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmUtils.kt
@@ -14,6 +14,12 @@ import com.ccextractor.ultimate_alarm_clock.LogDatabaseHelper
import java.text.SimpleDateFormat
import java.util.*
import java.util.Locale
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import com.ccextractor.ultimate_alarm_clock.LocationCheckWorker
+import com.ccextractor.ultimate_alarm_clock.WeatherCheckWorker
+import java.util.concurrent.TimeUnit
object AlarmUtils {
@SuppressLint("ScheduleExactAlarm")
@@ -136,6 +142,84 @@ object AlarmUtils {
alarmID = alarmID
)
}
+
+ //* This checks the code for the smart-control features to send beforehand to watch
+ //! locaiton checker
+ if (isLocation == 1) {
+ val workManager = WorkManager.getInstance(context)
+
+ val precheckMinutes = 1L
+ val triggerAtMillis = System.currentTimeMillis() + intervalToAlarm
+ val precheckMillis = TimeUnit.MINUTES.toMillis(precheckMinutes)
+
+ val delay = triggerAtMillis - System.currentTimeMillis() - precheckMillis
+
+ var actualDelay = 0L
+ if (delay > 0) {
+ actualDelay = delay
+ Log.d("AlarmUtils", "Scheduling pre-check with a delay of ${actualDelay}ms.")
+ } else {
+ actualDelay = 0L
+ Log.d("AlarmUtils", "Pre-check window has passed. Scheduling worker to run immediately.")
+ }
+
+ if (intervalToAlarm > 0) {
+ val requestCode = if (isShared) MainActivity.REQUEST_CODE_SHARED_ALARM else MainActivity.REQUEST_CODE_LOCAL_ALARM
+
+ val data = Data.Builder()
+ .putString("ALARM_ID", alarmID)
+ .putString("LOCATION", location)
+ .putInt("LOCATION_CONDITION_TYPE", locationConditionType)
+ .putBoolean("IS_SHARED_ALARM", isShared)
+ .putInt("ALARM_REQUEST_CODE", requestCode)
+ .build()
+
+ val locationCheckRequest = OneTimeWorkRequest.Builder(LocationCheckWorker::class.java)
+ .setInitialDelay(actualDelay, TimeUnit.MILLISECONDS)
+ .setInputData(data)
+ .addTag(alarmID)
+ .build()
+
+ workManager.enqueue(locationCheckRequest)
+ Log.d("AlarmUtils", "✅ WORKER ENQUEUED for alarm ID: $alarmID")
+ }
+ }
+ //! weather checker
+ if (isWeather == 1) {
+ val workManager = WorkManager.getInstance(context)
+
+ val precheckMinutes = 1L
+ val triggerAtMillis = System.currentTimeMillis() + intervalToAlarm
+ val precheckMillis = TimeUnit.MINUTES.toMillis(precheckMinutes)
+ val delay = triggerAtMillis - System.currentTimeMillis() - precheckMillis
+
+ var actualDelay = 0L
+ if (delay > 0) {
+ actualDelay = delay
+ Log.d("AlarmUtils", "Scheduling pre-check with a delay of ${actualDelay}ms.")
+ } else {
+ actualDelay = 0L
+ Log.d("AlarmUtils", "Pre-check window has passed. Scheduling worker to run immediately.")
+ }
+
+ if (intervalToAlarm > 0) {
+ val requestCode = if (isShared) MainActivity.REQUEST_CODE_SHARED_ALARM else MainActivity.REQUEST_CODE_LOCAL_ALARM
+
+ val data = Data.Builder()
+ .putString("ALARM_ID", alarmID)
+ .putInt("ALARM_REQUEST_CODE", requestCode)
+ .putString("WEATHER_TYPES", weatherTypes)
+ .putInt("WEATHER_CONDITION_TYPE", weatherConditionType)
+ .build()
+ val weatherCheckRequest = OneTimeWorkRequest.Builder(WeatherCheckWorker::class.java)
+ .setInitialDelay(actualDelay, TimeUnit.MILLISECONDS)
+ .setInputData(data)
+ .addTag(alarmID)
+ .build()
+ workManager.enqueue(weatherCheckRequest)
+ Log.d("AlarmUtils", "✅ WeatherCheckWorker ENQUEUED for alarm ID: $alarmID")
+ }
+ }
}
fun cancelAlarmById(context: Context, alarmID: String, isShared: Boolean) {
@@ -229,6 +313,7 @@ object AlarmUtils {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
+ // val activityCheckTime = 1000.toLong()
val activityCheckTime = triggerAtMillis - (15 * 60 * 1000)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/GetLatestAlarm.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/GetLatestAlarm.kt
index 5e2f4b858..58aa69f0e 100644
--- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/GetLatestAlarm.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/GetLatestAlarm.kt
@@ -438,7 +438,13 @@ data class AlarmModel(
val location: String,
val alarmDate: String,
val alarmId: String,
- val ringOn: Int
+ val ringOn: Int,
+ val isEnabled: Int,
+ val isGuardian: Int,
+ val guardian: Int,
+ val guardianTimer: Int,
+ val isCall: Int,
+
) {
companion object {
@SuppressLint("Range")
@@ -451,6 +457,12 @@ data class AlarmModel(
val activityMonitor = cursor.getInt(cursor.getColumnIndex("activityMonitor"))
val isWeatherEnabled = cursor.getInt(cursor.getColumnIndex("isWeatherEnabled"))
val weatherTypes = cursor.getString(cursor.getColumnIndex("weatherTypes"))
+ val isEnabled = cursor.getInt(cursor.getColumnIndex("isEnabled"))
+ val isGuardian = cursor.getInt(cursor.getColumnIndex("isGuardian"))
+ val guardian = cursor.getInt(cursor.getColumnIndex("guardian"))
+ val guardianTimer = cursor.getInt(cursor.getColumnIndex("guardianTimer"))
+ val isCall = cursor.getInt(cursor.getColumnIndex("isCall"))
+
val weatherConditionTypeIndex = cursor.getColumnIndex("weatherConditionType")
@@ -504,7 +516,12 @@ data class AlarmModel(
location,
alarmDate,
alarmId,
- ringOn
+ ringOn,
+ isEnabled,
+ isGuardian,
+ guardian,
+ guardianTimer,
+ isCall,
)
}
}
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt
index 3d322e0ee..7a876ff5d 100644
--- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt
@@ -37,6 +37,13 @@ import java.util.Locale
import android.app.NotificationChannel
import android.graphics.Color
import androidx.core.app.NotificationCompat
+import com.google.gson.Gson
+import com.ccextractor.ultimate_alarm_clock.communication.UACDataLayerListenerService
+import com.google.android.gms.wearable.Wearable
+import com.google.android.gms.wearable.DataClient
+import com.google.android.gms.wearable.DataEventBuffer
+import com.google.android.gms.wearable.DataEvent
+import com.ccextractor.ultimate_alarm_clock.communication.PhoneSender
class MainActivity : FlutterActivity() {
@@ -44,6 +51,7 @@ class MainActivity : FlutterActivity() {
const val CHANNEL1 = "ulticlock"
const val CHANNEL2 = "timer"
const val CHANNEL3 = "system_ringtones"
+ const val CHANNEL4 = "watch_action_channel"
const val ACTION_START_FLUTTER_APP = "com.ccextractor.ultimate_alarm_clock"
const val EXTRA_KEY = "alarmRing"
const val ALARM_TYPE = "isAlarm"
@@ -94,10 +102,14 @@ class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
+
+ UACDataLayerListenerService.flutterEngine = flutterEngine
+
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
var methodChannel1 = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL1)
var methodChannel2 = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL2)
var methodChannel3 = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL3)
+ val methodChannel4 = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL4)
val intent = intent
@@ -141,6 +153,34 @@ class MainActivity : FlutterActivity() {
alarmConfig["isSharedAlarm"] = false
}
+ methodChannel4.setMethodCallHandler { call, result ->
+ when (call.method) {
+ "sendActionToWatch" -> {
+ val action = call.argument("action")
+ val id = call.argument("id") ?: ""
+ if (action != null) {
+ PhoneSender.sendActionToWatch(this, action, id)
+ result.success("Action '$action' sent to watch.")
+ } else {
+ result.error("INVALID_ARGUMENTS", "Missing 'action' or 'id'", null)
+ }
+ }
+ "sendAlarmToWatch" -> {
+ val alarmMap = call.arguments as? Map
+ val isarId = call.argument("isarId")
+ if (alarmMap != null && isarId != null) {
+ val alarmMapMutable = alarmMap.toMutableMap()
+ alarmMapMutable["isarid"] = isarId
+ PhoneSender.sendAlarmToWatch(context, alarmMapMutable)
+ result.success("Alarm sent to watch.")
+ } else {
+ result.error("INVALID_ARGUMENT", "Alarm data or alarmId missing.", null)
+ }
+ }
+ else -> result.notImplemented()
+ }
+ }
+
methodChannel3.setMethodCallHandler { call, result ->
when (call.method) {
"getSystemRingtones" -> {
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationCheckWorker.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationCheckWorker.kt
new file mode 100644
index 000000000..19344d414
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationCheckWorker.kt
@@ -0,0 +1,155 @@
+// checks the location condition 1 min before ringing the alarm
+package com.ccextractor.ultimate_alarm_clock
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+import kotlin.math.sqrt
+import com.google.android.gms.wearable.PutDataMapRequest
+import com.google.android.gms.wearable.Wearable
+
+class LocationCheckWorker(appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ override suspend fun doWork(): Result {
+ Log.d("LocationCheckWorker", "Worker started.")
+
+ val alarmID = inputData.getString("ALARM_ID") ?: return Result.failure()
+ val targetLocation = inputData.getString("LOCATION") ?: return Result.failure()
+ val locationConditionType = inputData.getInt("LOCATION_CONDITION_TYPE", 2)
+ val isSharedAlarm = inputData.getBoolean("IS_SHARED_ALARM", false)
+ val alarmRequestCode = inputData.getInt("ALARM_REQUEST_CODE", -1)
+
+ if (alarmRequestCode == -1) {
+ Log.e("LocationCheckWorker", "Invalid Request Code received.")
+ return Result.failure()
+ }
+
+ Log.d("LocationCheckWorker", "Processing alarm ID: $alarmID, Condition: $locationConditionType")
+
+ try {
+ val locationHelper = LocationHelper(applicationContext)
+ val currentLocationString = locationHelper.getCurrentLocation()
+ if (currentLocationString.isEmpty() || currentLocationString == "error" || currentLocationString == "timeout") {
+ Log.e("LocationCheckWorker", "Failed to get current location: $currentLocationString")
+ return Result.success()
+ }
+
+ val currentCoords = currentLocationString.split(",").map { it.toDouble() }
+ val targetCoords = targetLocation.split(",").map { it.toDouble() }
+ val currentLocation = Location(currentCoords[0], currentCoords[1])
+ val destinationLocation = Location(targetCoords[0], targetCoords[1])
+
+ val distance = calculateDistance(currentLocation, destinationLocation)
+ val isWithin500m = distance < 500.0
+
+ var shouldRing: Boolean? = null
+ var logMessage = ""
+
+ when (locationConditionType) {
+ 1 -> { // Ring when AT
+ shouldRing = isWithin500m
+ logMessage = if (shouldRing) "Pre-check: User is AT. Alarm WILL ring." else "Pre-check: User is NOT AT. Alarm will be SKIPPED."
+ }
+ 2 -> { // Cancel when AT
+ shouldRing = !isWithin500m
+ logMessage = if (!shouldRing) "Pre-check: User is AT. Alarm will be SKIPPED." else "Pre-check: User is NOT AT. Alarm WILL ring."
+ }
+ 3 -> { // Ring when AWAY
+ shouldRing = !isWithin500m
+ logMessage = if (shouldRing) "Pre-check: User is AWAY. Alarm WILL ring." else "Pre-check: User is NOT AWAY. Alarm will be SKIPPED."
+ }
+ 4 -> { // Cancel when AWAY
+ shouldRing = isWithin500m
+ logMessage = if (!shouldRing) "Pre-check: User is AWAY. Alarm will be SKIPPED." else "Pre-check: User is NOT AWAY. Alarm WILL ring."
+ }
+ }
+
+ Log.d("LocationCheckWorker", logMessage)
+
+ // Send the CORRECT verdict to the watch
+ if (shouldRing != null) {
+ sendVerdictToWatch(alarmID, shouldRing, logMessage)
+ val prefs = applicationContext.getSharedPreferences("AlarmVerdict", Context.MODE_PRIVATE)
+ with(prefs.edit()) {
+ putBoolean(alarmID, shouldRing)
+ apply()
+ }
+ }
+ return Result.success()
+ } catch (e: Exception) {
+ Log.e("LocationCheckWorker", "Error in LocationCheckWorker: ${e.message}", e)
+ return Result.failure()
+ }
+ }
+
+ private fun cancelAlarm(context: Context, requestCode: Int, isSharedAlarm: Boolean) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val intent = Intent(context, AlarmReceiver::class.java).apply {
+ putExtra("isSharedAlarm", isSharedAlarm)
+ }
+
+ val pendingIntent = PendingIntent.getBroadcast(
+ context,
+ requestCode,
+ intent,
+ PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ if (pendingIntent != null) {
+ alarmManager.cancel(pendingIntent)
+ pendingIntent.cancel()
+ Log.i("LocationCheckWorker", "Successfully cancelled PendingIntent with request code $requestCode")
+ } else {
+ Log.w("LocationCheckWorker", "Could not find PendingIntent with request code $requestCode to cancel.")
+ }
+ }
+
+ private fun sendVerdictToWatch(alarmId: String, willRing: Boolean, reason: String) {
+ Log.d("LocationCheckWorker", "Attempting to send verdict to watch for alarm: $alarmId")
+ val path = "/uac/pre_check_verdict"
+
+ val putDataMapRequest = PutDataMapRequest.create(path)
+ putDataMapRequest.dataMap.apply {
+ putString("alarmID", alarmId)
+ putBoolean("willRing", willRing)
+ putString("reason", reason)
+ putLong("timestamp", System.currentTimeMillis())
+ }
+
+ val putDataRequest = putDataMapRequest.asPutDataRequest().setUrgent()
+
+ val dataClient = Wearable.getDataClient(applicationContext)
+ dataClient.putDataItem(putDataRequest).apply {
+ addOnSuccessListener {
+ Log.d("LocationCheckWorker", "✅ Successfully sent verdict to watch: ${it.uri}")
+ }
+ addOnFailureListener {
+ Log.e("LocationCheckWorker", "❌ Failed to send verdict to watch", it)
+ }
+ }
+ }
+
+ data class Location(val latitude: Double, val longitude: Double)
+
+ private fun calculateDistance(current: Location, destination: Location): Double {
+ val earthRadius = 6371.0
+ val lat1 = Math.toRadians(current.latitude)
+ val lon1 = Math.toRadians(current.longitude)
+ val lat2 = Math.toRadians(destination.latitude)
+ val lon2 = Math.toRadians(destination.longitude)
+ val dlon = lon2 - lon1
+ val dlat = lat2 - lat1
+ val a = sin(dlat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(dlon / 2).pow(2)
+ val c = 2 * atan2(sqrt(a), sqrt(1 - a))
+ return earthRadius * c * 1000
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationFetcherService.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationFetcherService.kt
index 5dbed0226..8c80f6b1e 100644
--- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationFetcherService.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/LocationFetcherService.kt
@@ -57,6 +57,7 @@ class LocationFetcherService : Service() {
Log.d("LocationFetcherService", "Processing alarm - ID: $alarmID, isShared: $isSharedAlarm")
Log.d("LocationFetcherService", "LocationConditionType from intent: $locationConditionType")
Log.d("LocationFetcherService", "Target location: $targetLocation")
+ startForeground(notificationId, getNotification())
// Validate location data
if (targetLocation.isEmpty() || targetLocation == "0.0,0.0") {
@@ -66,8 +67,6 @@ class LocationFetcherService : Service() {
return START_NOT_STICKY
}
- startForeground(notificationId, getNotification())
-
try {
processLocationAlarm()
} catch (e: Exception) {
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherCheckWorker.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherCheckWorker.kt
new file mode 100644
index 000000000..c22500f1f
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherCheckWorker.kt
@@ -0,0 +1,104 @@
+// is called 1 min before the alarm to check if the weather condition is met using the WeatherHelper.kt
+
+package com.ccextractor.ultimate_alarm_clock
+
+import android.content.Context
+import android.util.Log
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.google.android.gms.wearable.PutDataMapRequest
+import com.google.android.gms.wearable.Wearable
+
+class WeatherCheckWorker(private val appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ override suspend fun doWork(): Result {
+ Log.d("WeatherCheckWorker", "Worker Started.")
+
+ val alarmID = inputData.getString("ALARM_ID") ?: return Result.failure()
+ val alarmRequestCode = inputData.getInt("ALARM_REQUEST_CODE", -1)
+ val weatherTypesJSON = inputData.getString("WEATHER_TYPES") ?: "[]"
+ val weatherConditionType = inputData.getInt("WEATHER_CONDITION_TYPE", 0)
+
+ Log.d("WeatherCheckWorker", "Processing alarm ID: $alarmID, Condition: $weatherConditionType")
+
+ try {
+ val targetWeatherTypes = getWeatherConditionsFromString(weatherTypesJSON)
+ val (shouldRing, reason) = checkWeatherCondition(targetWeatherTypes, weatherConditionType)
+
+ Log.d("WeatherCheckWorker", "Verdict for $alarmID: Should Ring = $shouldRing, Reason: $reason")
+
+ if (shouldRing == false) {
+ // Save verdict for the phone's AlarmReceiver
+ val prefs = appContext.getSharedPreferences("AlarmVerdict", Context.MODE_PRIVATE)
+ with(prefs.edit()) {
+ putBoolean(alarmID, false).apply()
+ }
+
+ sendVerdictToWatch(alarmID, false, reason)
+
+ val logdbHelper = LogDatabaseHelper(appContext)
+ logdbHelper.insertLog(reason, status = LogDatabaseHelper.Status.WARNING, type = LogDatabaseHelper.LogType.NORMAL, hasRung = 0)
+ }
+
+ return Result.success()
+ } catch (e: Exception) {
+ Log.e("WeatherCheckWorker", "Error: ${e.message}", e)
+ return Result.failure()
+ }
+ }
+
+ private suspend fun checkWeatherCondition(targetWeatherTypes: String, conditionType: Int): Pair {
+ val locationHelper = LocationHelper(appContext)
+ val currentLocationString = locationHelper.getCurrentLocation()
+ if (currentLocationString.isEmpty() || currentLocationString.contains("error")) {
+ return Pair(true, "Pre-check (Weather): Could not get location, ringing as safeguard.")
+ }
+
+ return try {
+ val coords = currentLocationString.split(",").map { it.toDouble() }
+ val latitude = coords[0]
+ val longitude = coords[1]
+ val weatherHelper = WeatherHelper(appContext)
+ val currentWeather = weatherHelper.fetchCurrentWeather(latitude, longitude)
+
+ if (currentWeather == null) {
+ return Pair(true, "Pre-check (Weather): Could not fetch weather, ringing as safeguard.")
+ }
+ val weatherMatches = targetWeatherTypes.contains(currentWeather)
+
+ when (conditionType) {
+ 1 -> Pair(weatherMatches, if (weatherMatches) "Weather matches." else "Weather doesn't match, skipping.")
+ 2 -> Pair(!weatherMatches, if (!weatherMatches) "Weather does not match." else "Weather matches, skipping.")
+ 3 -> Pair(!weatherMatches, if (!weatherMatches) "Weather is different." else "Weather is not different, skipping.")
+ 4 -> Pair(weatherMatches, if (weatherMatches) "Weather is not different." else "Weather is different, skipping.")
+ else -> Pair(true, "Unknown weather condition.")
+ }
+ } catch (e: Exception) {
+ Pair(true, "Error during weather check, ringing as safeguard.")
+ }
+ }
+
+ private fun getWeatherConditionsFromString(jsonString: String): String {
+ val weatherMap = mapOf(0 to "sunny", 1 to "cloudy", 2 to "rainy", 3 to "windy", 4 to "stormy")
+ return try {
+ if (jsonString.isEmpty() || jsonString == "[]") return ""
+ val indices = jsonString.removeSurrounding("[", "]").split(",").filter { it.trim().isNotEmpty() }.map { it.trim().toInt() }
+ indices.mapNotNull { weatherMap[it] }.joinToString(",")
+ } catch (e: Exception) { "" }
+ }
+
+private fun sendVerdictToWatch(alarmId: String, willRing: Boolean, reason: String) {
+ val path = "/uac/pre_check_verdict"
+ val putDataMapRequest = PutDataMapRequest.create(path)
+ putDataMapRequest.dataMap.apply {
+ putString("alarmID", alarmId)
+ putBoolean("willRing", willRing)
+ putString("reason", reason)
+ }
+ val putDataRequest = putDataMapRequest.asPutDataRequest().setUrgent()
+ Wearable.getDataClient(appContext).putDataItem(putDataRequest)
+ .addOnSuccessListener { Log.d("WeatherCheckWorker", "✅ Verdict sent to watch.") }
+ .addOnFailureListener { Log.e("WeatherCheckWorker", "❌ Failed to send verdict.", it) }
+}
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherHelper.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherHelper.kt
new file mode 100644
index 000000000..c28a63f36
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/Utilities/WeatherHelper.kt
@@ -0,0 +1,39 @@
+// actually fetches the weather data from the API for the companion app
+package com.ccextractor.ultimate_alarm_clock
+
+import android.content.Context
+import android.util.Log
+import com.android.volley.toolbox.Volley
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+class WeatherHelper(private val context: Context) {
+
+ suspend fun fetchCurrentWeather(latitude: Double, longitude: Double): String? {
+ val url = "https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude¤t=rain,snowfall,cloud_cover,wind_speed_10m"
+ Log.d("WeatherHelper", "Fetching weather from: $url")
+
+ return suspendCancellableCoroutine { continuation ->
+ val request = GsonRequest(url, WeatherModel::class.java,
+ { response ->
+ val currentWeather = when {
+ (response.current?.rain ?: 0.0) > 0.1 && (response.current?.windSpeed10m ?: 0.0) > 40.0 -> "stormy"
+ (response.current?.rain ?: 0.0) > 0.1 -> "rainy"
+ (response.current?.cloudCover ?: 0) > 60 -> "cloudy"
+ (response.current?.windSpeed10m ?: 0.0) > 20.0 -> "windy"
+ else -> "sunny"
+ }
+ Log.d("WeatherHelper", "API Success. Current weather is: $currentWeather")
+ if (continuation.isActive) continuation.resume(currentWeather)
+ },
+ { error ->
+ Log.e("WeatherHelper", "API Error: ${error.message}")
+ if (continuation.isActive) continuation.resume(null)
+ })
+
+ continuation.invokeOnCancellation { request.cancel() }
+
+ Volley.newRequestQueue(context).add(request)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmMapper.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmMapper.kt
new file mode 100644
index 000000000..3c1095f08
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmMapper.kt
@@ -0,0 +1,40 @@
+package com.ccextractor.ultimate_alarm_clock.communication
+
+import com.ccextractor.ultimate_alarm_clock.AlarmModel
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun FullAlarmDTO.toAlarmModel(): AlarmModel {
+ val formatter = SimpleDateFormat("HH:mm", Locale.getDefault())
+ val date = formatter.parse(time) ?: Date(0)
+ val calendar = Calendar.getInstance().apply { time = date }
+
+ val minutesSinceMidnight =
+ calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)
+
+ return AlarmModel(
+ id = id,
+ minutesSinceMidnight = minutesSinceMidnight,
+ alarmTime = time,
+ days = days.joinToString(","),
+ isOneTime = isOneTime,
+ isEnabled = isEnabled,
+ ringOn = isEnabled,
+ alarmId = uniqueSyncId,
+ isLocationEnabled = if (isLocationEnabled) 1 else 0,
+ locationConditionType = locationConditionType,
+ location = location,
+ isWeatherEnabled = if (isWeatherEnabled) 1 else 0,
+ weatherConditionType = weatherConditionType,
+ weatherTypes = weatherTypes.joinToString(","),
+ activityMonitor = if (isActivityEnabled) 1 else 0,
+ activityInterval = activityInterval,
+ activityConditionType = activityConditionType,
+ isGuardian = if (isGuardian) 1 else 0,
+ guardian = guardian.toIntOrNull() ?: 0,
+ guardianTimer = guardianTimer,
+ isCall = if (isCall) 1 else 0,
+
+ alarmDate = ""
+ )
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmToDb.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmToDb.kt
new file mode 100644
index 000000000..9ac3c8b9e
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/AlarmToDb.kt
@@ -0,0 +1,113 @@
+package com.ccextractor.ultimate_alarm_clock.communication
+
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import com.ccextractor.ultimate_alarm_clock.AlarmModel
+import com.google.gson.Gson
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+
+object ReceivedAlarmModelToDb {
+
+ private const val CHANNEL = "com.ccextractor.alarm_channel"
+ private const val TAG = "UAC_ReceivedAlarmModelToDb"
+
+ fun sendToFlutter(flutterEngine: FlutterEngine, alarm: AlarmModel, isNewAlarm: Boolean) {
+ val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
+ val currentDate = LocalDate.now()
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ val formattedDate = currentDate.format(formatter)
+ val parsedDays =
+ (alarm.days).split(",").mapNotNull {
+ it.trim().toIntOrNull()
+ }
+
+ val daysBooleanList =
+ List(7) { dayIndex ->
+ parsedDays.contains(dayIndex + 1)
+ }
+
+ val weatherTypesIntList = alarm.weatherTypes.split(",")
+ .mapNotNull { it.trim().toIntOrNull() }
+ ?: emptyList()
+
+ val alarmMap =
+ mapOf(
+ "isarId" to alarm.id,
+ "alarmTime" to alarm.alarmTime,
+ "alarmID" to alarm.alarmId,
+ "ownerId" to "watch",
+ "ownerName" to "Watch",
+ "lastEditedUserId" to "watch",
+ "mutexLock" to false,
+ "days" to daysBooleanList,
+ "intervalToAlarm" to 0,
+ "minutesSinceMidnight" to alarm.minutesSinceMidnight,
+ "isSharedAlarmEnabled" to false,
+
+ "isActivityEnabled" to (alarm.activityMonitor == 1),
+ "activityInterval" to alarm.activityInterval,
+ "activityConditionType" to alarm.activityConditionType,
+
+ "isLocationEnabled" to (alarm.isLocationEnabled == 1),
+ "locationConditionType" to alarm.locationConditionType,
+ "location" to if (alarm.location.isEmpty()) "0.0,0.0" else alarm.location,
+
+ "isWeatherEnabled" to (alarm.isWeatherEnabled == 1),
+ "weatherConditionType" to alarm.weatherConditionType,
+ "weatherTypes" to weatherTypesIntList,
+
+ "isGuardian" to (alarm.isGuardian == 1),
+ "guardianTimer" to alarm.guardianTimer,
+ "guardian" to alarm.guardian.toString(),
+ "isCall" to (alarm.isCall == 1),
+
+ "isMathsEnabled" to false,
+ "mathsDifficulty" to 0,
+ "numMathsQuestions" to 0,
+ "isShakeEnabled" to false,
+ "shakeTimes" to 0,
+ "isQrEnabled" to false,
+ "qrValue" to "",
+ "isPedometerEnabled" to false,
+ "numberOfSteps" to 0,
+ "mainAlarmTime" to alarm.alarmTime,
+ "label" to "Synced from Watch",
+ "isOneTime" to (alarm.isOneTime == 1),
+ "snoozeDuration" to 5,
+ "maxSnoozeCount" to 3,
+ "gradient" to 0,
+ "ringtoneName" to "Default",
+ "note" to "",
+ "deleteAfterGoesOff" to false,
+ "showMotivationalQuote" to false,
+ "volMax" to 1.0,
+ "volMin" to 0.5,
+ "activityMonitor" to alarm.activityMonitor,
+ "alarmDate" to formattedDate,
+ "profile" to "Default",
+ "isSunriseEnabled" to false,
+ "sunriseDuration" to 0,
+ "sunriseIntensity" to 0.5,
+ "sunriseColorScheme" to 0,
+ "sharedUserIds" to listOf(),
+ "ringOn" to (alarm.ringOn == 1),
+ "isEnabled" to (alarm.isEnabled == 1),
+ )
+
+ val finalAlarmMap = mapOf(
+ "alarmMap" to alarmMap,
+ "isNewAlarm" to isNewAlarm
+ )
+
+ // val json = Gson().toJson(alarmMap)
+ Log.d(TAG, "📤 Sending alarm to Flutter: $finalAlarmMap")
+
+ Handler(Looper.getMainLooper()).post {
+ methodChannel.invokeMethod("onWatchAlarmReceived", finalAlarmMap)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/FullAlarmDTO.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/FullAlarmDTO.kt
new file mode 100644
index 000000000..5e43672f9
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/FullAlarmDTO.kt
@@ -0,0 +1,24 @@
+package com.ccextractor.ultimate_alarm_clock.communication
+
+data class FullAlarmDTO(
+ val id: Int,
+ val time: String,
+ val days: List,
+ val uniqueSyncId: String,
+ val isEnabled: Int,
+ val isOneTime: Int,
+ val fromWatch: Boolean,
+ val isLocationEnabled: Boolean,
+ val locationConditionType: Int,
+ val location: String,
+ val isWeatherEnabled: Boolean,
+ val weatherConditionType: Int,
+ val weatherTypes: List,
+ val isActivityEnabled: Boolean,
+ val activityInterval: Int,
+ val activityConditionType: Int,
+ val isGuardian: Boolean,
+ val guardian: String,
+ val guardianTimer: Int,
+ val isCall: Boolean
+)
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/PhoneSender.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/PhoneSender.kt
new file mode 100644
index 000000000..d2858fed5
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/PhoneSender.kt
@@ -0,0 +1,95 @@
+package com.ccextractor.ultimate_alarm_clock.communication
+
+import android.content.Context
+import android.util.Log
+import com.google.android.gms.wearable.*
+import com.google.gson.Gson
+import com.ccextractor.ultimate_alarm_clock.AlarmModel
+// import com.ccextractor.ultimate_alarm_clock.communication.MinimalAlarmDTO
+
+object PhoneSender {
+ private const val TAG = "UAC_PhoneListSender"
+// private const val PATH_ALARM_LIST_SYNC = "/uac_phone_alarm_list_sync";
+// private const val PATH_ACTION = "/uac_alarm_sync/action"
+ private const val PATH_ACTION_PHONE_TO_WATCH = "/uac_phone_to_watch/action"
+ private val PATH_ALARM_PHONE_TO_WATCH = "/uac_phone_to_watch/alarm"
+
+private fun anyToBoolean(value: Any?): Boolean {
+ return when (value) {
+ is Boolean -> value
+ is Number -> value.toInt() == 1
+ else -> false
+ }
+}
+
+fun sendAlarmToWatch(context: Context, alarmDataFromFlutter: Map) {
+ val watchDataMap = mutableMapOf()
+
+ watchDataMap["uniqueSyncId"] = alarmDataFromFlutter["alarmID"]
+ watchDataMap["id"] = alarmDataFromFlutter["isarId"]
+ watchDataMap["time"] = alarmDataFromFlutter["alarmTime"]
+ watchDataMap["isOneTime"] = if (anyToBoolean(alarmDataFromFlutter["isOneTime"])) 1 else 0
+ watchDataMap["isEnabled"] = if (anyToBoolean(alarmDataFromFlutter["isEnabled"])) 1 else 0
+
+ val daysBoolList = alarmDataFromFlutter["days"] as? List ?: emptyList()
+ val daysIntList = daysBoolList.mapIndexedNotNull { index, isSet -> if (isSet) index + 1 else null }
+ watchDataMap["days"] = daysIntList
+
+ watchDataMap["isLocationEnabled"] = anyToBoolean(alarmDataFromFlutter["isLocationEnabled"])
+ watchDataMap["location"] = alarmDataFromFlutter["location"]
+ watchDataMap["locationConditionType"] = alarmDataFromFlutter["locationConditionType"]
+
+ watchDataMap["isWeatherEnabled"] = anyToBoolean(alarmDataFromFlutter["isWeatherEnabled"])
+ watchDataMap["weatherTypes"] = alarmDataFromFlutter["weatherTypes"]
+ watchDataMap["weatherConditionType"] = alarmDataFromFlutter["weatherConditionType"]
+
+ watchDataMap["isActivityEnabled"] = anyToBoolean(alarmDataFromFlutter["isActivityEnabled"])
+ watchDataMap["activityInterval"] = alarmDataFromFlutter["activityInterval"]
+ watchDataMap["activityConditionType"] = alarmDataFromFlutter["activityConditionType"]
+
+ watchDataMap["isGuardian"] = anyToBoolean(alarmDataFromFlutter["isGuardian"])
+ watchDataMap["guardian"] = alarmDataFromFlutter["guardian"]
+ watchDataMap["guardianTimer"] = alarmDataFromFlutter["guardianTimer"]
+ watchDataMap["isCall"] = anyToBoolean(alarmDataFromFlutter["isCall"])
+
+ val alarmJson = Gson().toJson(watchDataMap)
+ Log.d(TAG, "Sending transformed alarm to watch: $alarmJson")
+
+ val putDataMapRequest = PutDataMapRequest.create(PATH_ALARM_PHONE_TO_WATCH)
+ putDataMapRequest.dataMap.putString("alarm_json", alarmJson)
+ putDataMapRequest.dataMap.putLong("timestamp", System.currentTimeMillis())
+
+ val request = putDataMapRequest.asPutDataRequest().setUrgent()
+
+ Wearable.getDataClient(context).putDataItem(request)
+ .addOnSuccessListener {
+ Log.d(TAG, "Alarm sent to watch successfully")
+ }
+ .addOnFailureListener {
+ Log.e(TAG, "Failed to send alarm to watch", it)
+ }
+}
+
+ fun sendActionToWatch(context: Context, action: String, alarmId: String) {
+ Log.d(TAG, "Attempting to send action '$action' for alarm ID $alarmId to watch")
+
+ try {
+ val dataMap = PutDataMapRequest.create(PATH_ACTION_PHONE_TO_WATCH)
+ dataMap.dataMap.putString("action", action)
+ dataMap.dataMap.putString("alarm_id", alarmId)
+ dataMap.dataMap.putLong("timestamp", System.currentTimeMillis())
+
+ val request = dataMap.asPutDataRequest().setUrgent()
+
+ Wearable.getDataClient(context).putDataItem(request)
+ .addOnSuccessListener {
+ Log.d(TAG, "✅ Successfully sent action '$action' to watch with PATH -> $PATH_ACTION_PHONE_TO_WATCH")
+ }
+ .addOnFailureListener { e ->
+ Log.e(TAG, "❌ Failed to send action to watch.", e)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "❌ Exception while sending action to watch.", e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/UACDataLayerListenerService.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/UACDataLayerListenerService.kt
new file mode 100644
index 000000000..1ba9909b0
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/communicatoin/UACDataLayerListenerService.kt
@@ -0,0 +1,77 @@
+package com.ccextractor.ultimate_alarm_clock.communication
+
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import com.google.android.gms.wearable.*
+import com.google.gson.Gson
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
+
+class UACDataLayerListenerService : WearableListenerService() {
+ private val TAG = "UAC_DataLayerService"
+ private val CHANNEL_NAME = "com.ccextractor.uac/alarm_actions"
+ private val PATH_ACTION_WATCH_TO_PHONE = "/uac_watch_to_phone/action"
+ private val PATH_ALARM_WATCH_TO_PHONE = "/uac_watch_to_phone/alarm"
+
+ private val mainThreadHandler = Handler(Looper.getMainLooper())
+
+ companion object {
+ var flutterEngine: FlutterEngine? = null
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ }
+
+ override fun onDataChanged(dataEvents: DataEventBuffer) {
+ Log.d(TAG, "📡 Phone receivers triggered")
+
+ for (event in dataEvents) {
+ if (event.type != DataEvent.TYPE_CHANGED) continue
+
+ val item = event.dataItem
+ val path = item.uri.path ?: continue
+ val dataMap = DataMapItem.fromDataItem(item).dataMap
+
+ Log.d(TAG, "➡ Path: $path | data: $dataMap")
+
+ when {
+ // ! alarm received is send to the flutter side
+ path == PATH_ALARM_WATCH_TO_PHONE -> {
+ val json = dataMap.getString("alarm_json")
+ val isNewAlarm = dataMap.getBoolean("isNewAlarm", true)
+ val dto = Gson().fromJson(json, FullAlarmDTO::class.java)
+ val fullAlarm = dto.toAlarmModel()
+ Log.d(TAG, "Background Alarm Received: isNewAlarm=$isNewAlarm, fullAlarm=$fullAlarm")
+
+ mainThreadHandler.post {
+ flutterEngine?.let {
+ ReceivedAlarmModelToDb.sendToFlutter(it, fullAlarm, isNewAlarm)
+ }
+ ?: Log.w(TAG, "⚠️ flutterEngine not available for alarm sync.")
+ }
+ }
+
+ path == PATH_ACTION_WATCH_TO_PHONE -> {
+ val action = dataMap.getString("action")
+ val alarmId = dataMap.getString("uniqueSyncId")
+ val timestamp = dataMap.getLong("timestamp")
+
+ Log.d(TAG, "Received Action: '$action' for alarmId: $alarmId at $timestamp")
+
+ mainThreadHandler.post {
+ flutterEngine?.dartExecutor?.binaryMessenger?.let { messenger ->
+ MethodChannel(messenger, CHANNEL_NAME)
+ .invokeMethod(
+ "handleReceivedAction",
+ mapOf("action" to action, "alarmId" to alarmId)
+ )
+ }
+ ?: Log.w(TAG, "⚠️ flutterEngine not available for action sync.")
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/app/communication/communication.dart b/lib/app/communication/communication.dart
new file mode 100644
index 000000000..46602e4f9
--- /dev/null
+++ b/lib/app/communication/communication.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/services.dart';
+import 'package:get/get.dart';
+import 'package:ultimate_alarm_clock/app/data/models/alarm_model.dart';
+import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart';
+import 'package:ultimate_alarm_clock/app/modules/home/controllers/home_controller.dart';
+
+class AlarmSyncHandler {
+ static const MethodChannel _channel =
+ MethodChannel("com.ccextractor.alarm_channel");
+
+ static void initListener() {
+ _channel.setMethodCallHandler((call) async {
+ if (call.method == "onWatchAlarmReceived") {
+ try {
+ final Map payload = call.arguments;
+ final Map alarmMap = Map.from(payload['alarmMap']);
+ final bool isNewAlarm = payload['isNewAlarm'];
+ final AlarmModel alarm = AlarmModel.fromMap(alarmMap);
+
+ if (isNewAlarm) {
+ print("AlarmSyncHandler: Calling static create handler...");
+ await AddOrUpdateAlarmController.handleWatchCreate(alarm);
+ } else {
+ print("AlarmSyncHandler: Calling static update handler...");
+ await AddOrUpdateAlarmController.handleWatchUpdate(alarm);
+ }
+ } catch (e, st) {
+ print("AlarmSyncHandler: Failed to process incoming alarm: $e\n$st");
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/lib/app/communication/native_action_handler.dart b/lib/app/communication/native_action_handler.dart
new file mode 100644
index 000000000..b4fe5237b
--- /dev/null
+++ b/lib/app/communication/native_action_handler.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/services.dart';
+import 'package:get/get.dart';
+import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart';
+import 'package:ultimate_alarm_clock/app/modules/alarmRing/controllers/alarm_ring_controller.dart';
+import 'package:ultimate_alarm_clock/app/modules/home/controllers/home_controller.dart';
+
+class NativeActionHandler extends GetxService {
+ static const _channel = MethodChannel('com.ccextractor.uac/alarm_actions');
+ Future init() async {
+ _channel.setMethodCallHandler(_handleNativeActions);
+ return this;
+ }
+
+ Future _handleNativeActions(MethodCall call) async {
+ if (call.method == 'handleReceivedAction') {
+ final action = call.arguments['action'] as String?;
+ final uniqueSyncId = call.arguments['alarmId'] as String?;
+
+ if (uniqueSyncId == null) {
+ print("NativeActionHandler: Received action '$action' but alarmId was null. Aborting.");
+ return;
+ }
+
+ print("NativeActionHandler: Received '$action' action for $uniqueSyncId");
+
+ switch (action) {
+ case 'delete alarm':
+ await AddOrUpdateAlarmController.handleWatchDelete(uniqueSyncId);
+ // Refresh the UI if the HomeController is active
+ if (Get.isRegistered()) {
+ final HomeController homeController = Get.find();
+ await Future.delayed(const Duration(milliseconds: 200));
+ await homeController.refreshUpcomingAlarms();
+ }
+ break;
+
+ case 'snooze':
+ if (Get.isRegistered()) {
+ Get.find().startSnooze();
+ }
+ break;
+
+ case 'dismiss':
+ if (Get.isRegistered()) {
+ await Get.find().dismissAlarm();
+ }
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/app/data/providers/isar_provider.dart b/lib/app/data/providers/isar_provider.dart
index d48b1f7e3..88533f71f 100644
--- a/lib/app/data/providers/isar_provider.dart
+++ b/lib/app/data/providers/isar_provider.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
@@ -11,6 +12,7 @@ import 'package:ultimate_alarm_clock/app/data/models/saved_emails.dart';
import 'package:ultimate_alarm_clock/app/data/models/timer_model.dart';
import 'package:ultimate_alarm_clock/app/data/providers/firestore_provider.dart';
import 'package:ultimate_alarm_clock/app/data/providers/get_storage_provider.dart';
+import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart';
import 'package:ultimate_alarm_clock/app/utils/utils.dart';
import 'package:sqflite/sqflite.dart';
@@ -519,6 +521,12 @@ class IsarDb {
where: 'alarmID = ?',
whereArgs: [alarmRecord.alarmID],
);
+ try {
+ await AddOrUpdateAlarmController().syncAlarmToWatch(alarmRecord);
+ debugPrint("Successfully sent toggled alarm state to the watch: $alarmRecord");
+ } catch (e) {
+ debugPrint("Failed to send toggled alarm state to the watch: $e");
+ }
}
}
@@ -631,6 +639,27 @@ class IsarDb {
);
}
}
+
+ // need this for companion app // UniqueId = alarmID on phone.
+ static Future deleteAlarmByUniqueId(String alarmID) async {
+ final isarProvider = IsarDb();
+ final db = await isarProvider.db;
+
+ // Find the alarm in the database using the unique alarmID
+ final alarmToDelete = await db.alarmModels
+ .where()
+ .filter()
+ .alarmIDEqualTo(alarmID)
+ .findFirst();
+
+ // If an alarm is found, use its isarId to delete it
+ if (alarmToDelete != null) {
+ print("Found alarm with alarmID $alarmID. Deleting it now.");
+ await IsarDb.deleteAlarm(alarmToDelete.isarId);
+ } else {
+ print("Could not find alarm with alarmID $alarmID to delete.");
+ }
+ }
// Timer Functions
diff --git a/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart b/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart
index 3d68c84b6..dd4238568 100644
--- a/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart
+++ b/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart
@@ -1,3 +1,4 @@
+import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
@@ -7,6 +8,7 @@ import 'package:collection/collection.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:fl_location/fl_location.dart';
+import 'package:isar/isar.dart';
import 'package:latlong2/latlong.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:path_provider/path_provider.dart';
@@ -17,6 +19,7 @@ import 'package:ultimate_alarm_clock/app/data/models/user_model.dart';
import 'package:ultimate_alarm_clock/app/data/providers/firestore_provider.dart';
import 'package:ultimate_alarm_clock/app/data/providers/get_storage_provider.dart';
import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart' as isar;
+import 'package:ultimate_alarm_clock/app/data/providers/isar_provider.dart';
import 'package:ultimate_alarm_clock/app/data/providers/push_notifications.dart';
import 'package:ultimate_alarm_clock/app/data/providers/secure_storage_provider.dart';
import 'package:ultimate_alarm_clock/app/data/models/ringtone_model.dart';
@@ -36,6 +39,16 @@ class AddOrUpdateAlarmController extends GetxController {
final labelController = TextEditingController();
ThemeController themeController = Get.find();
SettingsController settingsController = Get.find();
+
+ static const MethodChannel watchSyncChannel = MethodChannel('watch_action_channel');
+ Future syncAlarmToWatch(AlarmModel alarm) async {
+ try {
+ await watchSyncChannel.invokeMethod('sendAlarmToWatch', {...alarm.toSQFliteMap(), 'isarId': alarm.isarId});
+ debugPrint('Successfully requested sync for alarm: ${alarm.toSQFliteMap()}');
+ } on PlatformException catch (e) {
+ debugPrint("Failed to sync alarm to watch: ${e.message}");
+ }
+ }
final Rx userModel = Rx(null);
var alarmID = const Uuid().v4();
@@ -447,7 +460,7 @@ class AddOrUpdateAlarmController extends GetxController {
return true;
}
- createAlarm(AlarmModel alarmData) async {
+ Future createAlarm(AlarmModel alarmData) async {
if (isSharedAlarmEnabled.value == true) {
alarmRecord.value =
await FirestoreDb.addAlarm(userModel.value, alarmData);
@@ -460,6 +473,9 @@ class AddOrUpdateAlarmController extends GetxController {
alarmRecord: alarmData,
);
});
+ debugPrint("Created alarm with ID: ${alarmData.isarId}");
+
+ await syncAlarmToWatch(alarmData);
}
showQRDialog() {
@@ -661,6 +677,63 @@ class AddOrUpdateAlarmController extends GetxController {
detectedQrValue.value = retake ? '' : qrValue.value;
}
+ static Future handleWatchCreate(AlarmModel alarm) async {
+ await isar.IsarDb.addAlarm(alarm);
+ print("AddOrUpdateAlarmController: Static handler created new alarm.");
+ }
+
+ static Future handleWatchUpdate(AlarmModel alarmFromWatch) async {
+ final db = await isar.IsarDb().db;
+ final homeController = Get.find();
+
+ final existingAlarm = await db.alarmModels
+ .where()
+ .filter()
+ .alarmIDEqualTo(alarmFromWatch.alarmID)
+ .findFirst();
+
+ if (existingAlarm != null) {
+ print("AddOrUpdateAlarmController: Found existing alarm. Proceeding with update.");
+ alarmFromWatch.isarId = existingAlarm.isarId;
+
+ try {
+ await homeController.alarmChannel.invokeMethod('cancelAlarmById', {
+ 'alarmID': existingAlarm.alarmID,
+ 'isSharedAlarm': false,
+ });
+ print('Canceled existing local alarm before update: ${existingAlarm.alarmID}');
+ } catch (e) {
+ print('Error canceling existing alarm during watch sync: $e');
+ }
+
+ await isar.IsarDb.updateAlarm(alarmFromWatch);
+ print("AddOrUpdateAlarmController: Static handler updated existing alarm in DB.");
+ await homeController.refreshUpcomingAlarms();
+ } else {
+ print("AddOrUpdateAlarmController: Could not find alarm to update. Creating it instead.");
+ await isar.IsarDb.addAlarm(alarmFromWatch);
+ await homeController.refreshUpcomingAlarms();
+ }
+ }
+
+ static Future handleWatchDelete(String uniqueSyncId) async {
+ final homeController = Get.find();
+
+ // CANCEL the scheduled native alarm first
+ try {
+ debugPrint('handleWatchDelete called');
+ await homeController.alarmChannel.invokeMethod('cancelAlarmById', {
+ 'alarmID': uniqueSyncId,
+ 'isSharedAlarm': false,
+ });
+ print('Canceled native alarm via watch sync: $uniqueSyncId');
+ } catch (e) {
+ print('Error canceling native alarm during watch delete sync: $e');
+ }
+ await isar.IsarDb.deleteAlarmByUniqueId(uniqueSyncId);
+ print("AddOrUpdateAlarmController: Static handler deleted alarm.");
+ }
+
updateAlarm(AlarmModel alarmData) async {
// Adding the ID's so it can update depending on the db
if (isSharedAlarmEnabled.value == true) {
@@ -759,10 +832,11 @@ class AddOrUpdateAlarmController extends GetxController {
alarmRecord.value = await isar.IsarDb.addAlarm(alarmData);
+ await syncAlarmToWatch(alarmRecord.value);
+
debugPrint('✅ Created new normal alarm in local database: ${alarmRecord.value.alarmID}');
-
- } else if (await isar.IsarDb.doesAlarmExist(alarmRecord.value.alarmID) == true) {
-
+ } else if (await isar.IsarDb.doesAlarmExist(alarmRecord.value.alarmID) ==
+ true) {
debugPrint('📝 Updating existing normal alarm: ${alarmRecord.value.alarmID}');
alarmData.isarId = alarmRecord.value.isarId;
@@ -780,8 +854,7 @@ class AddOrUpdateAlarmController extends GetxController {
await isar.IsarDb.updateAlarm(alarmData);
-
-
+ await syncAlarmToWatch(alarmData);
homeController.forceRefreshAfterAlarmUpdate(alarmData.alarmID, false);
} else {
diff --git a/lib/app/modules/alarmRing/controllers/alarm_ring_controller.dart b/lib/app/modules/alarmRing/controllers/alarm_ring_controller.dart
index 017e12df9..7e3f0301b 100644
--- a/lib/app/modules/alarmRing/controllers/alarm_ring_controller.dart
+++ b/lib/app/modules/alarmRing/controllers/alarm_ring_controller.dart
@@ -30,6 +30,7 @@ import 'package:screen_brightness/screen_brightness.dart';
import '../../home/controllers/home_controller.dart';
class AlarmControlController extends GetxController {
+ static const MethodChannel watchSyncChannel = MethodChannel('watch_action_channel');
MethodChannel alarmChannel = MethodChannel('ulticlock');
RxString note = ''.obs;
Timer? vibrationTimer;
@@ -174,6 +175,62 @@ class AlarmControlController extends GetxController {
});
}
+ Future sendToWatch(String action) async {
+ try {
+ await watchSyncChannel.invokeMethod('sendActionToWatch', {
+ 'action': action,
+ 'id': currentlyRingingAlarm.value.alarmID,
+ });
+
+ print('✅ Sent $action action to watch');
+ } catch (e) {
+ print('Failed to send $action to watch: $e');
+ }
+ }
+
+ Future dismissAlarm() async {
+ Utils.hapticFeedback();
+ await sendToWatch('dismiss');
+
+ debugPrint('🔔 Dismissing alarm via controller method');
+
+ if (isPreviewMode.value) {
+ debugPrint('🔔 Preview mode - simple navigation back');
+ Get.offAllNamed('/bottom-navigation-bar');
+ return;
+ }
+
+ if (currentlyRingingAlarm.value.isGuardian) {
+ guardianTimer.cancel();
+ debugPrint('🔔 Guardian timer canceled');
+ }
+
+ if (currentlyRingingAlarm.value.isSharedAlarmEnabled) {
+ rememberDismissedAlarm();
+ debugPrint('🔔 Blocked shared alarm: ${currentlyRingingAlarm.value.alarmTime}, ID: ${currentlyRingingAlarm.value.firestoreId}');
+ }
+
+ await homeController.clearLastScheduledAlarm();
+ debugPrint('🔔 Cleared all scheduled alarms');
+
+ homeController.refreshTimer = true;
+ debugPrint('🔔 Set refresh flag for alarm scheduling');
+
+ if (Utils.isChallengeEnabled(currentlyRingingAlarm.value)) {
+ debugPrint('🔔 Navigating to challenge screen');
+ Get.toNamed(
+ '/alarm-challenge',
+ arguments: currentlyRingingAlarm.value,
+ );
+ } else {
+ debugPrint('🔔 Navigating to home screen');
+ Get.offAllNamed(
+ '/bottom-navigation-bar',
+ arguments: currentlyRingingAlarm.value,
+ );
+ }
+ }
+
Future _fadeInAlarmVolume() async {
await FlutterVolumeController.setVolume(
currentlyRingingAlarm.value.volMin / 10.0,
diff --git a/lib/app/modules/alarmRing/views/alarm_ring_view.dart b/lib/app/modules/alarmRing/views/alarm_ring_view.dart
index d08b2b059..a93a98e50 100644
--- a/lib/app/modules/alarmRing/views/alarm_ring_view.dart
+++ b/lib/app/modules/alarmRing/views/alarm_ring_view.dart
@@ -18,6 +18,7 @@ class AlarmControlView extends GetView {
AlarmControlView({Key? key}) : super(key: key);
ThemeController themeController = Get.find();
+ static const MethodChannel watchSyncChannel = MethodChannel('watch_action_channel');
Obx getAddSnoozeButtons(
BuildContext context, int snoozeMinutes, String title) {
@@ -175,9 +176,10 @@ class AlarmControlView extends GetView {
fontWeight: FontWeight.w600,
),
),
- onPressed: () {
+ onPressed: () async {
Utils.hapticFeedback();
controller.startSnooze();
+ await controller.sendToWatch('snooze');
},
),
),
@@ -218,51 +220,8 @@ class AlarmControlView extends GetView {
),
),
onPressed: () async {
- Utils.hapticFeedback();
- debugPrint('🔔 Dismiss button pressed');
-
- // Handle preview mode differently
- if (controller.isPreviewMode.value) {
- debugPrint('🔔 Preview mode - simple navigation back');
- Get.offAllNamed('/bottom-navigation-bar');
- return;
- }
-
- if (controller.currentlyRingingAlarm.value.isGuardian) {
- controller.guardianTimer.cancel();
- debugPrint('🔔 Guardian timer canceled');
- }
-
-
- if (controller.currentlyRingingAlarm.value.isSharedAlarmEnabled) {
- controller.rememberDismissedAlarm();
- debugPrint('🔔 Blocked shared alarm: ${controller.currentlyRingingAlarm.value.alarmTime}, ID: ${controller.currentlyRingingAlarm.value.firestoreId}');
- }
-
-
- await controller.homeController.clearLastScheduledAlarm();
- debugPrint('🔔 Cleared all scheduled alarms');
-
-
- controller.homeController.refreshTimer = true;
- debugPrint('🔔 Set refresh flag for alarm scheduling');
-
-
- if (Utils.isChallengeEnabled(
- controller.currentlyRingingAlarm.value,
- )) {
- debugPrint('🔔 Navigating to challenge screen');
- Get.toNamed(
- '/alarm-challenge',
- arguments: controller.currentlyRingingAlarm.value,
- );
- } else {
- debugPrint('🔔 Navigating to home screen');
- Get.offAllNamed(
- '/bottom-navigation-bar',
- arguments: controller.currentlyRingingAlarm.value,
- );
- }
+ await controller.dismissAlarm();
+ // removed the controller logic from view :)
},
child: Text(
Utils.isChallengeEnabled(
diff --git a/lib/app/modules/home/bindings/home_binding.dart b/lib/app/modules/home/bindings/home_binding.dart
index 76ec7ba28..599b748f2 100644
--- a/lib/app/modules/home/bindings/home_binding.dart
+++ b/lib/app/modules/home/bindings/home_binding.dart
@@ -1,4 +1,5 @@
import 'package:get/get.dart';
+import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart';
import 'package:ultimate_alarm_clock/app/modules/bottomNavigationBar/controllers/bottom_navigation_bar_controller.dart';
import '../controllers/home_controller.dart';
@@ -10,6 +11,7 @@ class HomeBinding extends Bindings {
HomeController(),
);
+ Get.lazyPut(()=>AddOrUpdateAlarmController());
Get.lazyPut(
() => BottomNavigationBarController(),
diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart
index 5b291a270..359e32c83 100644
--- a/lib/app/modules/home/controllers/home_controller.dart
+++ b/lib/app/modules/home/controllers/home_controller.dart
@@ -34,6 +34,7 @@ class Pair {
class HomeController extends GetxController {
MethodChannel alarmChannel = const MethodChannel('ulticlock');
+ static const MethodChannel watchSyncChannel = MethodChannel('watch_action_channel');
Stream? firestoreStreamAlarms;
Stream? sharedAlarmsStream;
@@ -418,6 +419,7 @@ class HomeController extends GetxController {
@override
void onInit() async {
super.onInit();
+ refreshUpcomingAlarms();
// Clear all alarm tracking on init to ensure clean startup
recentlyDismissedAlarmIds.clear();
@@ -1330,6 +1332,13 @@ class HomeController extends GetxController {
}
await FirestoreDb.deleteAlarm(user, alarm.firestoreId!);
+
+ if (alarmToDelete != null) {
+ await watchSyncChannel.invokeMethod('sendActionToWatch', {
+ 'action': 'delete alarm',
+ 'id': alarmToDelete.alarmID,
+ });
+ }
} else {
alarmToDelete = await IsarDb.getAlarm(alarm.isarId);
@@ -1345,6 +1354,12 @@ class HomeController extends GetxController {
}
await IsarDb.deleteAlarm(alarm.isarId);
+ if (alarmToDelete != null) {
+ await watchSyncChannel.invokeMethod('sendActionToWatch', {
+ 'action': 'delete alarm',
+ 'id': alarmToDelete.alarmID,
+ });
+ }
}
if (Get.isSnackbarOpen) {
diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart
index 2f2cf85ca..d8b446fa9 100644
--- a/lib/app/modules/home/views/home_view.dart
+++ b/lib/app/modules/home/views/home_view.dart
@@ -115,7 +115,7 @@ class HomeView extends GetView {
.withOpacity(
0.75,
),
- fontSize: 14 *
+ fontSize: 13 *
controller
.scalingFactor
.value,
diff --git a/lib/app/modules/splashScreen/controllers/splash_screen_controller.dart b/lib/app/modules/splashScreen/controllers/splash_screen_controller.dart
index b2e7e10e8..c2549759f 100644
--- a/lib/app/modules/splashScreen/controllers/splash_screen_controller.dart
+++ b/lib/app/modules/splashScreen/controllers/splash_screen_controller.dart
@@ -15,6 +15,7 @@ import '../../home/controllers/home_controller.dart';
class SplashScreenController extends GetxController {
MethodChannel alarmChannel = const MethodChannel('ulticlock');
MethodChannel timerChannel = const MethodChannel('timer');
+ static const MethodChannel watchChannel = MethodChannel('com.ccextractor.uac/alarm_actions');
bool shouldAlarmRing = true;
bool shouldNavigate = true;
@@ -30,6 +31,19 @@ class SplashScreenController extends GetxController {
return latestAlarm;
}
+ // void initWatchActionListener() {
+ // watchChannel.setMethodCallHandler((call) async {
+ // if (call.method == "handleReceivedAction") {
+ // final Map args = call.arguments as Map;
+ // final String action = args["action"];
+ // final int alarmId = args["id"];
+ // if (action == "delete") {
+ // debugPrint("Alarm deleted from watch: $alarmId");
+ // }
+ // }
+ // });
+ // }
+
getNextAlarm() async {
UserModel? _userModel = await SecureStorageProvider().retrieveUserModel();
AlarmModel _alarmRecord = homeController.genFakeAlarmModel();
diff --git a/lib/main.dart b/lib/main.dart
index d995478b3..586878b75 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -6,11 +6,14 @@ import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:ultimate_alarm_clock/app/data/providers/get_storage_provider.dart';
import 'package:ultimate_alarm_clock/app/data/providers/push_notifications.dart';
+import 'package:ultimate_alarm_clock/app/modules/debug/controllers/debug_controller.dart';
import 'package:ultimate_alarm_clock/app/modules/settings/controllers/theme_controller.dart';
import 'package:sqflite/sqflite.dart';
import 'package:ultimate_alarm_clock/app/utils/language.dart';
import 'package:ultimate_alarm_clock/app/utils/constants.dart';
import 'package:ultimate_alarm_clock/app/utils/custom_error_screen.dart';
+import 'package:ultimate_alarm_clock/app/communication/communication.dart';
+import 'package:ultimate_alarm_clock/app/communication/native_action_handler.dart';
import 'firebase_options.dart';
import 'app/routes/app_pages.dart';
@@ -18,6 +21,7 @@ Locale? loc;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+ AlarmSyncHandler.initListener();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
@@ -30,11 +34,13 @@ void main() async {
await Get.putAsync(() => GetStorageProvider().init());
+ await Get.putAsync(() => NativeActionHandler().init());
final storage = Get.find();
loc = await storage.readLocale();
final ThemeController themeController = Get.put(ThemeController());
+ Get.put(ThemeController());
AudioPlayer.global.setAudioContext(
const AudioContext(