Fix notification delivery and settings flows

This commit is contained in:
Dwindi Ramadhana
2026-05-31 20:40:20 +07:00
parent 5195ba19ad
commit 2bd8e3666a
12 changed files with 1419 additions and 435 deletions

View File

@@ -10,6 +10,19 @@ import '../../../data/services/notification_analytics_service.dart';
import '../../../data/services/notification_inbox_service.dart';
import '../../../data/services/notification_service.dart';
int _todayUpcomingAlarmCount(List<NotificationPendingAlert> alarms) {
final now = DateTime.now();
final todayStart = DateTime(now.year, now.month, now.day);
final todayEnd = todayStart.add(const Duration(days: 1));
return alarms.where((alarm) {
final when = alarm.scheduledAt;
if (when == null) return false;
return !when.isBefore(now) &&
!when.isBefore(todayStart) &&
when.isBefore(todayEnd);
}).length;
}
class NotificationCenterScreen extends StatefulWidget {
const NotificationCenterScreen({super.key});
@@ -56,6 +69,14 @@ class _NotificationCenterScreenState extends State<NotificationCenterScreen>
);
}
Future<void> _clearNonCritical() async {
await NotificationInboxService.instance.clearNonCritical();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Notifikasi non-kritis dibersihkan.')),
);
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
@@ -91,10 +112,19 @@ class _NotificationCenterScreenState extends State<NotificationCenterScreen>
builder: (context, _, __) {
final unread =
NotificationInboxService.instance.unreadCount();
if (unread <= 0) return const SizedBox.shrink();
return TextButton(
onPressed: _markAllRead,
child: const Text('Tandai semua'),
return Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: _clearNonCritical,
child: const Text('Bersihkan'),
),
if (unread > 0)
TextButton(
onPressed: _markAllRead,
child: const Text('Tandai semua'),
),
],
);
},
);
@@ -113,7 +143,9 @@ class _NotificationCenterScreenState extends State<NotificationCenterScreen>
FutureBuilder<List<NotificationPendingAlert>>(
future: _alarmsFuture,
builder: (context, snapshot) {
final count = snapshot.data?.length ?? 0;
final count = _todayUpcomingAlarmCount(
snapshot.data ?? const <NotificationPendingAlert>[],
);
return Tab(text: count > 0 ? 'Alarm ($count)' : 'Alarm');
},
),
@@ -170,13 +202,21 @@ class _AlarmTabState extends State<_AlarmTab> {
final alarms = snapshot.data ?? const <NotificationPendingAlert>[];
final now = DateTime.now();
final upcoming = alarms
.where((alarm) =>
alarm.scheduledAt == null || !alarm.scheduledAt!.isBefore(now))
.toList();
final todayStart = DateTime(now.year, now.month, now.day);
final todayEnd = todayStart.add(const Duration(days: 1));
final upcoming = alarms.where((alarm) {
final when = alarm.scheduledAt;
if (when == null) return false;
return !when.isBefore(now) &&
!when.isBefore(todayStart) &&
when.isBefore(todayEnd);
}).toList();
final passed = alarms
.where((alarm) =>
alarm.scheduledAt != null && alarm.scheduledAt!.isBefore(now))
alarm.scheduledAt != null &&
alarm.scheduledAt!.isBefore(now) &&
!alarm.scheduledAt!.isBefore(todayStart) &&
alarm.scheduledAt!.isBefore(todayEnd))
.toList();
final visible = _alarmFilter == 'past' ? passed : upcoming;
@@ -254,7 +294,7 @@ class _AlarmTabState extends State<_AlarmTab> {
),
child: Text(
_alarmFilter == 'upcoming'
? 'Tidak ada alarm akan datang.'
? 'Tidak ada alarm tersisa untuk hari ini.'
: 'Belum ada alarm sudah lewat.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
@@ -368,9 +408,7 @@ class _AlarmTabState extends State<_AlarmTab> {
runSpacing: 8,
children: [
_FilterChip(
label: upcomingCount > 0
? 'Akan Datang ($upcomingCount)'
: 'Akan Datang',
label: upcomingCount > 0 ? 'Hari Ini ($upcomingCount)' : 'Hari Ini',
selected: _alarmFilter == 'upcoming',
isDark: isDark,
onTap: () => setState(() => _alarmFilter = 'upcoming'),
@@ -627,6 +665,16 @@ class _InboxTabState extends State<_InboxTab> {
label: _inboxLabel(item.type),
color: accent,
),
if ((item.meta['isGrouped'] == true) &&
(item.meta['groupCount'] is num))
Padding(
padding: const EdgeInsets.only(left: 8),
child: _TypeBadge(
label:
'${(item.meta['groupCount'] as num).toInt()} item',
color: AppColors.sage,
),
),
const SizedBox(width: 8),
Text(
_formatInboxTime(item.createdAt),
@@ -694,6 +742,12 @@ class _InboxTabState extends State<_InboxTab> {
isDark: isDark,
onTap: () => setState(() => _filter = 'system'),
),
_FilterChip(
label: 'Kritis',
selected: _filter == 'critical',
isDark: isDark,
onTap: () => setState(() => _filter = 'critical'),
),
],
);
}