Notification system audit: fix 6 defects, close 5 gaps, add rich notifications (v1.1.0)
Defects fixed: - D1: Fix notification ID range collision (report reminders 700k→2M+) - D2: Streak risk now checks both dzikir pagi & petang - D3: _cancelPrayerPending no longer kills non-prayer notifications - D4: Push notifications carry deeplink in payload for proper routing - D5: Add reconfigureTimeZoneIfNeeded() for TZ change detection - D6: Defer launch notification routing until widget tree is ready Gaps closed: - G1: Add streak risk + weekly summary toggles to settings UI - G2: Verified boot reschedule already in place (flutter_local_notifications v21) - G3: Remove unused mirrorAdzanToInbox field and legacy cleanup calls - G4: Add notif_push_opened analytics tracking - G5: Add notif_settings_changed analytics tracking Enhancements: - O1: Rich notification with Sudah Sholat action button on report reminders - O2: Permission check on app resume via WidgetsBindingObserver (30s throttle) - O2b: Fix stretched notification icon (white crescent moon vector drawable) - O3: Expired inbox cleanup in background sync - O4: Haptic feedback on notification bell quick actions Bump version 1.0.8+9 → 1.1.0+10
This commit is contained in:
@@ -4,8 +4,13 @@ import 'dart:ui' show ViewFocusEvent, ViewFocusState;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
import '../core/providers/theme_provider.dart';
|
||||
import '../data/local/hive_boxes.dart';
|
||||
import '../data/local/models/app_settings.dart';
|
||||
import '../data/services/notification_event_producer_service.dart';
|
||||
import '../data/services/notification_service.dart';
|
||||
import '../features/dashboard/data/prayer_times_provider.dart';
|
||||
import 'router.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
@@ -20,6 +25,7 @@ class App extends ConsumerStatefulWidget {
|
||||
|
||||
class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
|
||||
Timer? _midnightResyncTimer;
|
||||
DateTime? _lastPermissionCheckAt;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -29,6 +35,7 @@ class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
|
||||
HardwareKeyboard.instance.syncKeyboardState();
|
||||
});
|
||||
_scheduleMidnightResync();
|
||||
NotificationService.instance.consumePendingLaunchRoute();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -49,6 +56,7 @@ class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
|
||||
ref.invalidate(prayerTimesProvider);
|
||||
unawaited(ref.read(prayerTimesProvider.future));
|
||||
_scheduleMidnightResync();
|
||||
_checkNotificationPermissionOnResume();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +81,32 @@ class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkNotificationPermissionOnResume() async {
|
||||
final now = DateTime.now();
|
||||
if (_lastPermissionCheckAt != null &&
|
||||
now.difference(_lastPermissionCheckAt!).inSeconds < 30) {
|
||||
return; // Throttle: max once per 30 seconds.
|
||||
}
|
||||
_lastPermissionCheckAt = now;
|
||||
|
||||
try {
|
||||
final settings = Hive.box<AppSettings>(HiveBoxes.settings)
|
||||
.get('default') ??
|
||||
AppSettings();
|
||||
if (!settings.adhanEnabled.values.any((v) => v)) return;
|
||||
|
||||
final permissionStatus =
|
||||
await NotificationService.instance.getPermissionStatus();
|
||||
await NotificationEventProducerService.instance
|
||||
.emitPermissionWarningsIfNeeded(
|
||||
settings: settings,
|
||||
permissionStatus: permissionStatus,
|
||||
);
|
||||
} catch (_) {
|
||||
// Non-blocking: permission check on resume is best-effort.
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeMode = ref.watch(themeProvider);
|
||||
|
||||
Reference in New Issue
Block a user