1+ import 'dart:async' ;
12import 'package:flutter/material.dart' ;
2- import 'alarm_model.dart' ;
3+
34import '../../core/notifications/notification_service.dart' ;
5+ import '../../shared/storage/alarm_storage.dart' ;
6+ import 'alarm_model.dart' ;
47
58class AlarmScreen extends StatefulWidget {
69 const AlarmScreen ({super .key});
@@ -10,9 +13,52 @@ class AlarmScreen extends StatefulWidget {
1013}
1114
1215class _AlarmScreenState extends State <AlarmScreen > {
13- final List <AlarmModel > _alarms = [];
16+ final List <AlarmModel > alarms = [];
17+ Timer ? _ticker;
18+
19+ @override
20+ void initState () {
21+ super .initState ();
22+ _loadAlarms ();
23+ _ticker = Timer .periodic (const Duration (minutes: 1 ), (_) {
24+ if (mounted) setState (() {});
25+ });
26+ }
27+
28+ @override
29+ void dispose () {
30+ _ticker? .cancel ();
31+ super .dispose ();
32+ }
33+
34+ Future <void > _loadAlarms () async {
35+ final stored = await AlarmStorage .load ();
36+ if (! mounted) return ;
37+ alarms.addAll (stored);
38+ setState (() {});
39+ }
40+
41+ Future <void > _persist () async {
42+ await AlarmStorage .save (alarms);
43+ }
44+
45+ Duration _remainingTime (AlarmModel alarm) {
46+ return alarm.scheduledAt.difference (DateTime .now ());
47+ }
1448
1549 Future <void > _addAlarm () async {
50+ final allowed = await NotificationService .requestExactAlarmPermission ();
51+ if (! allowed) {
52+ if (! mounted) return ;
53+ ScaffoldMessenger .of (context).showSnackBar (
54+ const SnackBar (
55+ content: Text ('Enable exact alarm permission in system settings' ),
56+ ),
57+ );
58+ return ;
59+ }
60+
61+ if (! mounted) return ;
1662 final picked = await showTimePicker (
1763 context: context,
1864 initialTime: TimeOfDay .now (),
@@ -29,12 +75,11 @@ class _AlarmScreenState extends State<AlarmScreen> {
2975 picked.minute,
3076 );
3177
32- // If selected time already passed, schedule for tomorrow
3378 if (scheduled.isBefore (now)) {
3479 scheduled = scheduled.add (const Duration (days: 1 ));
3580 }
3681
37- final id = scheduled.millisecondsSinceEpoch ~ / 1000 ;
82+ final int id = scheduled.millisecondsSinceEpoch ~ / 1000 ;
3883
3984 await NotificationService .scheduleAlarm (
4085 id: id,
@@ -43,20 +88,38 @@ class _AlarmScreenState extends State<AlarmScreen> {
4388 body: 'It\' s time!' ,
4489 );
4590
46- setState (() {
47- _alarms.add (AlarmModel (time: picked, notificationId: id));
91+ alarms.add (
92+ AlarmModel (time: picked, id: id, scheduledAt: scheduled, active: true ),
93+ );
4894
49- _alarms.sort ((a, b) {
50- final aMin = a.time.hour * 60 + a.time.minute;
51- final bMin = b.time.hour * 60 + b.time.minute;
52- return aMin.compareTo (bMin);
53- });
54- });
95+ setState (() {});
96+ await _persist ();
97+ }
98+
99+ Future <void > _toggleAlarm (int index, bool value) async {
100+ final alarm = alarms[index];
101+ alarm.active = value;
102+
103+ if (value) {
104+ await NotificationService .scheduleAlarm (
105+ id: alarm.id,
106+ dateTime: alarm.scheduledAt,
107+ title: '⏰ Alarm' ,
108+ body: 'It\' s time!' ,
109+ );
110+ } else {
111+ await NotificationService .cancel (alarm.id);
112+ }
113+
114+ setState (() {});
115+ await _persist ();
55116 }
56117
57118 Future <void > _deleteAlarm (int index) async {
58- await NotificationService .cancel (_alarms[index].notificationId);
59- setState (() => _alarms.removeAt (index));
119+ await NotificationService .cancel (alarms[index].id);
120+ alarms.removeAt (index);
121+ setState (() {});
122+ await _persist ();
60123 }
61124
62125 @override
@@ -67,16 +130,17 @@ class _AlarmScreenState extends State<AlarmScreen> {
67130 icon: const Icon (Icons .add_alarm),
68131 label: const Text ('Add Alarm' ),
69132 ),
70- body: _alarms .isEmpty
133+ body: alarms .isEmpty
71134 ? const Center (child: Text ('No alarms set' ))
72135 : ListView .builder (
73136 padding: const EdgeInsets .all (16 ),
74- itemCount: _alarms .length,
137+ itemCount: alarms .length,
75138 itemBuilder: (context, index) {
76- final alarm = _alarms[index];
139+ final alarm = alarms[index];
140+ final remaining = _remainingTime (alarm);
77141
78142 return Dismissible (
79- key: ValueKey (alarm.notificationId ),
143+ key: ValueKey (alarm.id ),
80144 direction: DismissDirection .endToStart,
81145 onDismissed: (_) => _deleteAlarm (index),
82146 background: Container (
@@ -87,7 +151,7 @@ class _AlarmScreenState extends State<AlarmScreen> {
87151 ),
88152 child: Card (
89153 shape: RoundedRectangleBorder (
90- borderRadius: BorderRadius .circular (15 ),
154+ borderRadius: BorderRadius .circular (16 ),
91155 ),
92156 child: ListTile (
93157 title: Text (
@@ -97,7 +161,16 @@ class _AlarmScreenState extends State<AlarmScreen> {
97161 fontWeight: FontWeight .bold,
98162 ),
99163 ),
100- trailing: const Icon (Icons .notifications_active),
164+ subtitle: alarm.active && remaining.inMinutes > 0
165+ ? Text (
166+ '${remaining .inHours }h ${remaining .inMinutes % 60 }m remaining' ,
167+ style: const TextStyle (color: Colors .grey),
168+ )
169+ : const Text ('Inactive' ),
170+ trailing: Switch (
171+ value: alarm.active,
172+ onChanged: (v) => _toggleAlarm (index, v),
173+ ),
101174 ),
102175 ),
103176 );
0 commit comments