diff --git a/lib/main.dart b/lib/main.dart index 84cc402..8767fc3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,8 @@ import 'package:timezone/timezone.dart' as tz; import 'package:http/http.dart' as http; import 'dart:convert'; +import 'dart:io'; + void main() async { WidgetsFlutterBinding.ensureInitialized(); tz.initializeTimeZones(); @@ -160,6 +162,8 @@ class _HomeScreenState extends State { } } + Timer? _alarmTimer; + Future _scheduleAlarm() async { final sunsetText = _sunsetController.text.trim(); if (sunsetText.isEmpty) return; @@ -175,7 +179,6 @@ class _HomeScreenState extends State { if (alarmTime.isBefore(now)) { alarmTime = alarmTime.add(const Duration(days: 1)); } - alarmTime = alarmTime.subtract(Duration(minutes: _minutesBefore)); if (alarmTime.isBefore(now)) { @@ -183,33 +186,50 @@ class _HomeScreenState extends State { alarmTime = alarmTime.add(const Duration(days: 1)); } - final tzAlarm = tz.TZDateTime.from(alarmTime, tz.local); - - const androidDetails = AndroidNotificationDetails( - 'sunset_alarm', - 'Sunset Alarm', - channelDescription: 'Alarm X minutes before sunset', - importance: Importance.max, - priority: Priority.high, - fullScreenIntent: true, - category: AndroidNotificationCategory.alarm, - ); - const iosDetails = DarwinNotificationDetails( - presentAlert: true, - presentBadge: true, - presentSound: true, - ); - - await _notifications.zonedSchedule( - _alarmId, - 'Sunset in ${_minutesBefore} min', - 'The sun sets at $sunsetText. Time to get ready!', - tzAlarm, - NotificationDetails(android: androidDetails, iOS: iosDetails), - androidScheduleMode: AndroidScheduleMode.alarmClock, - uiLocalNotificationDateInterpretation: - UILocalNotificationDateInterpretation.absoluteTime, - ); + if (Platform.isLinux) { + // Linux: use a Timer (app must stay open) + _alarmTimer?.cancel(); + final delay = alarmTime.difference(now); + _alarmTimer = Timer(delay, () { + const linuxDetails = LinuxNotificationDetails( + urgency: LinuxNotificationUrgency.critical, + resident: true, + timeout: LinuxNotificationTimeout.expiresNever(), + ); + _notifications.show( + _alarmId, + 'Sunset in ${_minutesBefore} min', + 'The sun sets at $sunsetText. Time to get ready!', + const NotificationDetails(linux: linuxDetails), + ); + }); + _showSnack( + 'Alarm set for ${alarmTime.hour.toString().padLeft(2, '0')}:${alarmTime.minute.toString().padLeft(2, '0')} (app must stay open)', + ); + } else { + // Android: use zonedSchedule (works in background) + final tzAlarm = tz.TZDateTime.from(alarmTime, tz.local); + const androidDetails = AndroidNotificationDetails( + 'sunset_alarm', + 'Sunset Alarm', + channelDescription: 'Alarm X minutes before sunset', + importance: Importance.max, + priority: Priority.high, + fullScreenIntent: true, + category: AndroidNotificationCategory.alarm, + ); + await _notifications.zonedSchedule( + _alarmId, + 'Sunset in ${_minutesBefore} min', + 'The sun sets at $sunsetText. Time to get ready!', + tzAlarm, + const NotificationDetails(android: androidDetails), + androidScheduleMode: AndroidScheduleMode.alarmClock, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + ); + _showSnack('Alarm scheduled.'); + } } Future _toggleAlarm(bool value) async { @@ -217,9 +237,11 @@ class _HomeScreenState extends State { await _savePrefs(); if (value) { await _scheduleAlarm(); - _showSnack('Alarm scheduled.'); } else { - await _notifications.cancel(_alarmId); + _alarmTimer?.cancel(); + if (!Platform.isLinux) { + await _notifications.cancel(_alarmId); + } _showSnack('Alarm cancelled.'); } } @@ -353,24 +375,40 @@ class _HomeScreenState extends State { }, ), ), - ], - ), - const SizedBox(height: 24), - // --- Toggle --- - SwitchListTile( - title: const Text('Enable Sunset Alarm'), - subtitle: Text( - _alarmEnabled - ? 'Will alert ${_minutesBefore} min before sunset' - : 'Alarm is off', - ), - value: _alarmEnabled, - onChanged: _toggleAlarm, + const SizedBox(height: 24), + + // --- Toggle --- + SwitchListTile( + title: const Text('Enable Sunset Alarm'), + subtitle: Text( + _alarmEnabled + ? 'Will alert ${_minutesBefore} min before sunset. ($_alarmDisplayTime)' + : 'Alarm is off', + ), + value: _alarmEnabled, + onChanged: _toggleAlarm, + ), + ], ), ], ), ), ); } + + String get _alarmDisplayTime { + final sunsetText = _sunsetController.text.trim(); + if (sunsetText.isEmpty) return '—'; + final parts = sunsetText.split(':'); + if (parts.length < 2) return '—'; + final h = int.tryParse(parts[0]); + final m = int.tryParse(parts[1]); + if (h == null || m == null) return '—'; + final alarmMinutes = h * 60 + m - _minutesBefore; + final displayMin = alarmMinutes % (24 * 60); + final dh = (displayMin ~/ 60) % 24; + final dm = displayMin % 60; + return '${dh.toString().padLeft(2, '0')}:${dm.toString().padLeft(2, '0')}'; + } }