Skip to content

Commit bd6453a

Browse files
committed
Remakeing application - 2026-01-10 [3]
1 parent 8a3e2e1 commit bd6453a

10 files changed

Lines changed: 389 additions & 68 deletions

File tree

android/.kotlin/sessions/kotlin-compiler-8468629118357355569.salive

Whitespace-only changes.

lib/core/notifications/notification_service.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class NotificationService {
8282
),
8383
),
8484
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
85+
8586
);
8687
}
8788

lib/features/Alarm/alarm_model.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ class AlarmModel {
2222
};
2323

2424
factory AlarmModel.fromJson(Map<String, dynamic> json) {
25+
final scheduled = DateTime.parse(json['scheduledAt']);
26+
2527
return AlarmModel(
26-
time: TimeOfDay(hour: json['hour'], minute: json['minute']),
27-
id: json['id'],
28-
scheduledAt: DateTime.parse(json['scheduledAt']),
29-
active: json['active'],
28+
time: TimeOfDay(hour: json['hour'] ?? 0, minute: json['minute'] ?? 0),
29+
id:
30+
json['id'] ??
31+
scheduled.millisecondsSinceEpoch ~/ 1000, // backward-safe
32+
scheduledAt: scheduled,
33+
active: json['active'] ?? false,
3034
);
3135
}
3236
}

lib/features/Alarm/alarm_page.dart

Lines changed: 156 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
3+
import 'package:audioplayers/audioplayers.dart';
34

45
import '../../core/notifications/notification_service.dart';
56
import '../../shared/storage/alarm_storage.dart';
67
import 'alarm_model.dart';
8+
import 'alarm_tile.dart';
9+
import '../../shared/widgets/app_model.dart';
710

811
class AlarmScreen extends StatefulWidget {
912
const AlarmScreen({super.key});
@@ -14,60 +17,69 @@ class AlarmScreen extends StatefulWidget {
1417

1518
class _AlarmScreenState extends State<AlarmScreen> {
1619
final List<AlarmModel> alarms = [];
20+
1721
Timer? _ticker;
22+
bool _dialogVisible = false;
23+
24+
// Initialize the audio player
25+
final AudioPlayer _audioPlayer = AudioPlayer();
1826

1927
@override
2028
void initState() {
2129
super.initState();
2230
_loadAlarms();
23-
_ticker = Timer.periodic(const Duration(minutes: 1), (_) {
24-
if (mounted) setState(() {});
25-
});
31+
// Update UI every 30 seconds to check alarm times and refresh "remaining time"
32+
_ticker = Timer.periodic(const Duration(seconds: 30), (_) => _tick());
2633
}
2734

2835
@override
2936
void dispose() {
3037
_ticker?.cancel();
38+
_audioPlayer.dispose(); // Dispose of the player to release resources
3139
super.dispose();
3240
}
3341

3442
Future<void> _loadAlarms() async {
3543
final stored = await AlarmStorage.load();
36-
if (!mounted) return;
3744
alarms.addAll(stored);
38-
setState(() {});
45+
_tick(); // Check immediately upon load
3946
}
4047

4148
Future<void> _persist() async {
4249
await AlarmStorage.save(alarms);
4350
}
4451

45-
Duration _remainingTime(AlarmModel alarm) {
46-
return alarm.scheduledAt.difference(DateTime.now());
52+
void _tick() {
53+
final now = DateTime.now();
54+
bool changed = false;
55+
56+
for (final alarm in alarms) {
57+
if (alarm.active && now.isAfter(alarm.scheduledAt)) {
58+
alarm.active = false;
59+
changed = true;
60+
61+
if (mounted && !_dialogVisible) {
62+
_showAlarmDialog(alarm);
63+
}
64+
}
65+
}
66+
67+
if (changed) _persist();
68+
if (mounted) setState(() {});
4769
}
4870

4971
Future<void> _addAlarm() async {
5072
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-
}
73+
if (!allowed || !mounted) return;
6074

61-
if (!mounted) return;
6275
final picked = await showTimePicker(
6376
context: context,
6477
initialTime: TimeOfDay.now(),
6578
);
66-
6779
if (picked == null) return;
6880

6981
final now = DateTime.now();
70-
DateTime scheduled = DateTime(
82+
var scheduled = DateTime(
7183
now.year,
7284
now.month,
7385
now.day,
@@ -79,7 +91,7 @@ class _AlarmScreenState extends State<AlarmScreen> {
7991
scheduled = scheduled.add(const Duration(days: 1));
8092
}
8193

82-
final int id = scheduled.millisecondsSinceEpoch ~/ 1000;
94+
final id = scheduled.millisecondsSinceEpoch ~/ 1000;
8395

8496
await NotificationService.scheduleAlarm(
8597
id: id,
@@ -96,32 +108,145 @@ class _AlarmScreenState extends State<AlarmScreen> {
96108
await _persist();
97109
}
98110

99-
Future<void> _toggleAlarm(int index, bool value) async {
100-
final alarm = alarms[index];
101-
alarm.active = value;
111+
Future<void> _editAlarm(int index) async {
112+
final oldAlarm = alarms[index];
113+
114+
// 1. Show Time Picker initialized with the EXISTING time
115+
final picked = await showTimePicker(
116+
context: context,
117+
initialTime: oldAlarm.time,
118+
);
119+
120+
// If user cancelled, do nothing
121+
if (picked == null) return;
122+
123+
// 2. Calculate new schedule
124+
final now = DateTime.now();
125+
var scheduled = DateTime(
126+
now.year,
127+
now.month,
128+
now.day,
129+
picked.hour,
130+
picked.minute,
131+
);
132+
133+
if (scheduled.isBefore(now)) {
134+
scheduled = scheduled.add(const Duration(days: 1));
135+
}
136+
137+
// 3. Create new ID based on new time
138+
final newId = scheduled.millisecondsSinceEpoch ~/ 1000;
139+
140+
// 4. Cancel the OLD notification
141+
await NotificationService.cancel(oldAlarm.id);
142+
143+
// 5. Schedule the NEW notification
144+
await NotificationService.scheduleAlarm(
145+
id: newId,
146+
dateTime: scheduled,
147+
title: '⏰ Alarm',
148+
body: 'It\'s time!',
149+
);
150+
151+
// 6. Update the list
152+
setState(() {
153+
alarms[index] = AlarmModel(
154+
time: picked,
155+
id: newId,
156+
scheduledAt: scheduled,
157+
active: true,
158+
);
159+
});
160+
161+
await _persist();
162+
}
163+
164+
Future<void> _toggle(int index, bool value) async {
165+
var alarm = alarms[index];
102166

103167
if (value) {
168+
var scheduled = alarm.scheduledAt;
169+
170+
// Reschedule for tomorrow if the time has already passed today
171+
if (scheduled.isBefore(DateTime.now())) {
172+
scheduled = scheduled.add(const Duration(days: 1));
173+
alarm = alarms[index] = AlarmModel(
174+
time: alarm.time,
175+
id: scheduled.millisecondsSinceEpoch ~/ 1000,
176+
scheduledAt: scheduled,
177+
active: true,
178+
);
179+
}
180+
104181
await NotificationService.scheduleAlarm(
105182
id: alarm.id,
106183
dateTime: alarm.scheduledAt,
107184
title: '⏰ Alarm',
108185
body: 'It\'s time!',
109186
);
187+
188+
alarm.active = true;
110189
} else {
111190
await NotificationService.cancel(alarm.id);
191+
alarm.active = false;
112192
}
113193

114194
setState(() {});
115195
await _persist();
116196
}
117197

118-
Future<void> _deleteAlarm(int index) async {
198+
Future<void> _delete(int index) async {
119199
await NotificationService.cancel(alarms[index].id);
120200
alarms.removeAt(index);
121201
setState(() {});
122202
await _persist();
123203
}
124204

205+
Future<void> _showAlarmDialog(AlarmModel alarm) async {
206+
_dialogVisible = true;
207+
208+
// 1. Play the sound in a loop
209+
try {
210+
await _audioPlayer.setReleaseMode(ReleaseMode.loop);
211+
await _audioPlayer.play(AssetSource('sounds/alarm_clock_1.mp3'));
212+
} catch (e) {
213+
debugPrint("Error playing sound: $e");
214+
}
215+
216+
if (!mounted) return;
217+
218+
// 2. Show the modal
219+
await AppModal.show(
220+
context: context,
221+
title: '⏰ Alarm',
222+
content: Text('It\'s ${alarm.time.format(context)}'),
223+
dismissible: false, // Forces user to interact
224+
actions: [
225+
TextButton(
226+
onPressed: () {
227+
// Stop sound
228+
_audioPlayer.stop();
229+
230+
Navigator.pop(context);
231+
_dialogVisible = false;
232+
},
233+
child: const Text('Dismiss'),
234+
),
235+
TextButton(
236+
onPressed: () {
237+
// Stop sound
238+
_audioPlayer.stop();
239+
240+
// TODO: Implement actual snooze logic (e.g., reschedule for +5 mins)
241+
Navigator.pop(context);
242+
_dialogVisible = false;
243+
},
244+
child: const Text('Snooze (5 min)'),
245+
),
246+
],
247+
);
248+
}
249+
125250
@override
126251
Widget build(BuildContext context) {
127252
return Scaffold(
@@ -135,44 +260,13 @@ class _AlarmScreenState extends State<AlarmScreen> {
135260
: ListView.builder(
136261
padding: const EdgeInsets.all(16),
137262
itemCount: alarms.length,
138-
itemBuilder: (context, index) {
139-
final alarm = alarms[index];
140-
final remaining = _remainingTime(alarm);
141-
142-
return Dismissible(
143-
key: ValueKey(alarm.id),
144-
direction: DismissDirection.endToStart,
145-
onDismissed: (_) => _deleteAlarm(index),
146-
background: Container(
147-
alignment: Alignment.centerRight,
148-
padding: const EdgeInsets.only(right: 20),
149-
color: Colors.red,
150-
child: const Icon(Icons.delete, color: Colors.white),
151-
),
152-
child: Card(
153-
shape: RoundedRectangleBorder(
154-
borderRadius: BorderRadius.circular(16),
155-
),
156-
child: ListTile(
157-
title: Text(
158-
alarm.time.format(context),
159-
style: const TextStyle(
160-
fontSize: 28,
161-
fontWeight: FontWeight.bold,
162-
),
163-
),
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-
),
174-
),
175-
),
263+
itemBuilder: (_, i) {
264+
return AlarmTile(
265+
key: ValueKey(alarms[i].id),
266+
alarm: alarms[i],
267+
onDelete: () => _delete(i),
268+
onEdit: () => _editAlarm(i), // Pass the edit function here
269+
onToggle: (val) => _toggle(i, val),
176270
);
177271
},
178272
),

0 commit comments

Comments
 (0)