Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.type.watch" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
Expand All @@ -14,6 +16,16 @@
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher_round">

<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>


<receiver android:name=".AlarmBroadcastReceiver" android:exported="true" />
<receiver android:name=".AlarmDismissReceiver" android:exported="true"/>
<receiver android:name=".AlarmSnoozeReceiver" android:exported="true"/>
Expand Down
34 changes: 34 additions & 0 deletions android/app/src/main/kotlin/com/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import com.ccextractor.uac_companion.communication.parseAlarm
import com.ccextractor.uac_companion.communication.UACDataLayerListenerService

class MainActivity : FlutterActivity() {

private val RESTORE_ACTION = "RESTORE_ALARMS_AFTER_BOOT"

private val CHANNEL = "uac_alarm_channel"
private val NATIVE_TO_FLUTTER = "uac_kotlin_to_flutter"
Expand Down Expand Up @@ -86,8 +88,40 @@ class MainActivity : FlutterActivity() {
else -> result.notImplemented()
}
}

}

private fun handleRestoreAfterBoot(intent: Intent?) {
if (intent?.action == RESTORE_ACTION) {
Log.d("MainActivity", "Restoring alarms after boot")

val engine = flutterEngine
if (engine == null) {
Log.w("MainActivity", "FlutterEngine not ready yet, skipping restore")
return
}

MethodChannel(
engine.dartExecutor.binaryMessenger,
CHANNEL
).invokeMethod("restoreAlarms", null)
}
}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleRestoreAfterBoot(intent)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleRestoreAfterBoot(intent)
}



private fun checkAndRequestPermissions() {
val permissionsToRequest = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ccextractor.uac_companion

import com.ccextractor.uac_companion.Utils.Preferences

import android.app.*
import android.content.*
import android.os.Build
Expand All @@ -9,14 +11,16 @@ import kotlin.math.abs

class AlarmSnoozeReceiver : BroadcastReceiver() {
final val TAG = "AlarmSnoozeReceiver"
//!need fixes alarm snoozes but with warning - W/Ringtone: Neither local nor remote playback available that makes the alarm to ring after 5 min but the alrm do not ring
// Handles snooze action for alarms - reschedules alarm after configured snooze duration
override fun onReceive(context: Context, intent: Intent) {
// val alarmId = intent.getIntExtra("alarmId", -1)
val alarmId = intent.getIntExtra("alarmId", -1)
// ... later in code:
putExtra("alarmId", alarmId) // Cleaner than intent.getIntExtra("alarmId", -1)
val uniqueSyncId = intent.getStringExtra("uniqueSyncId") ?: ""
val hour = intent.getIntExtra("hour", -1)
val minute = intent.getIntExtra("minute", -1)
// val isSnoozed = intent.getBooleanExtra("isSnoozed", false)
val fromPhone = intent.getBooleanExtra("fromPhone", false) ?: false
// val isSnoozed = intent.getBooleanExtra("isSnoozed", false)
val fromPhone = intent.getBooleanExtra("fromPhone", false)

if (!fromPhone) {
WatchAlarmSender.sendActionToPhone(context, "snooze", uniqueSyncId)
Expand All @@ -26,20 +30,27 @@ class AlarmSnoozeReceiver : BroadcastReceiver() {

// Stop current sound/vibration/notification
AlarmServiceHolder.ringtone?.stop()
AlarmServiceHolder.ringtone = null // added
AlarmServiceHolder.vibrator?.cancel()
AlarmServiceHolder.vibrator = null // added
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(NOTIFICATION_ID)

// Reschedule the alarm 5 minute ahead
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// val triggerAtMillis = System.currentTimeMillis() + 300_000
val triggerAtMillis = System.currentTimeMillis() + 300_000

//Preferences.kt added and AlarmSnoozeReceiver updated.
val snoozeMinutes = Preferences.getSnoozeDurationMinutes(context)
val triggerAtMillis = System.currentTimeMillis() + snoozeMinutes * 60 * 1000


val snoozeIntent = Intent(context, AlarmBroadcastReceiver::class.java).apply {
// putExtra("alarmId", alarmId)
putExtra("alarmId", intent.getIntExtra("alarmId", -1)) // Preserve original alarmId
putExtra("uniqueSyncId", uniqueSyncId)
putExtra("hour", hour)
putExtra("minute", minute)
putExtra("days", intent.getStringExtra("days") ?: "") // ← ADD THIS (optional)
putExtra("isSnoozed", true)
action = "com.ccextractor.uac_companion.ALARM_TRIGGERED_$uniqueSyncId"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ccextractor.uac_companion

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log

class BootReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("BootReceiver", "BOOT_COMPLETED received")

val restoreIntent = Intent(context, MainActivity::class.java).apply {
action = "RESTORE_ALARMS_AFTER_BOOT"
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

context.startActivity(restoreIntent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ccextractor.uac_companion.Utils

import android.content.Context

object Preferences {

private const val PREF_NAME = "uac_companion_prefs"
private const val KEY_SNOOZE_DURATION_MINUTES = "snooze_duration_minutes"
private const val DEFAULT_SNOOZE_MINUTES = 5

fun getSnoozeDurationMinutes(context: Context): Int {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
return prefs.getInt(KEY_SNOOZE_DURATION_MINUTES, DEFAULT_SNOOZE_MINUTES)
}

fun setSnoozeDurationMinutes(context: Context, minutes: Int) {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putInt(KEY_SNOOZE_DURATION_MINUTES, minutes).apply()
}
}
37 changes: 33 additions & 4 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,23 @@ class HomeController extends GetxController with WidgetsBindingObserver {

//! if native receives alarm from phone then reload the UI
const MethodChannel('uac_alarm_channel').setMethodCallHandler((call) async {
if (call.method == 'alarmsChanged') {
debugPrint(
'Flutter (Watch): Received alarmsChanged event. Reloading alarms.');
await loadAlarms();
switch (call.method) {
case 'alarmsChanged':
debugPrint(
'Flutter (Watch): Received alarmsChanged event. Reloading alarms.');
await loadAlarms();
break;

case 'restoreAlarms':
debugPrint('[BOOT] Restoring alarms after reboot');
await restoreAlarms();
break;

default:
debugPrint('Unknown method: ${call.method}');
}
});

}

//* Needed for the WidgetsBindingObserver to listen to app lifecycle changes
Expand Down Expand Up @@ -113,4 +124,22 @@ class HomeController extends GetxController with WidgetsBindingObserver {
debugPrint('HomeController -> Alarm delete/cancel failed: $e');
}
}

Future<void> restoreAlarms() async {
// Fetch enabled alarms from local DB
final enabledAlarms =
(await alarmService.getAlarms()).where((a) => a.isEnabled).toList();

debugPrint('[BOOT] Found ${enabledAlarms.length} enabled alarms');

// Reschedule alarms using existing native logic
for (final alarm in enabledAlarms) {
try {
await platform.invokeMethod('scheduleAlarm');
} catch (e) {
debugPrint('[BOOT] Failed to reschedule alarm ${alarm.uniqueSyncId}: $e');
}
}
}

}
53 changes: 48 additions & 5 deletions lib/app/modules/smart_control/controllers/location_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:uac_companion/app/modules/smart_control/controllers/smart_controls_controller.dart';
import 'package:uac_companion/app/routes/app_routes.dart';
import 'package:fl_location/fl_location.dart';


class LocationController extends GetxController {
static LocationController get to => Get.find();
Expand All @@ -15,10 +17,51 @@ class LocationController extends GetxController {
{"label": "Cancel Away from Location", "type": 4},
];

//! need to change the defaultLatLng as user's current locaiton
static final LatLng defaultLatLng = LatLng(28.6139, 77.2090);
final MapController mapController = MapController();
var pickerLatLng = defaultLatLng.obs;
final LatLng fallbackLatLng = const LatLng(28.6139, 77.2090);
late Rx<LatLng> currentLatLng = fallbackLatLng.obs;

late Rx<LatLng> pickerLatLng = fallbackLatLng.obs;


@override
void onInit() {
super.onInit();
_fetchCurrentLocation();
}

Future<void> _fetchCurrentLocation() async {
try {
if (!await FlLocation.isLocationServicesEnabled) {
return;
}

var permission = await FlLocation.checkLocationPermission();

if (permission == LocationPermission.deniedForever) {
return;
}

if (permission == LocationPermission.denied) {
permission = await FlLocation.requestLocationPermission();
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
return;
}
}

final location = await FlLocation.getLocation(
accuracy: LocationAccuracy.best,
timeLimit: const Duration(seconds: 10),
);

final latLng = LatLng(location.latitude, location.longitude);
currentLatLng.value = latLng;
pickerLatLng.value = latLng;

} catch (e) {
// Use fallback location
}
}

void onPickerScreenReady() {
if (mapController.camera.center != pickerLatLng.value) {
Expand All @@ -27,7 +70,7 @@ class LocationController extends GetxController {
}

Future<void> onSelectCondition(int index) async {
pickerLatLng.value = defaultLatLng;
pickerLatLng.value = currentLatLng.value;

final result = await Get.toNamed(AppRoutes.locationPicker);

Expand Down