Polish navigation, Quran flows, and sharing UX

This commit is contained in:
Dwindi Ramadhana
2026-03-18 00:07:10 +07:00
parent a049129a35
commit 2d09b5b356
59 changed files with 11835 additions and 3184 deletions

View File

@@ -0,0 +1,86 @@
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import '../local/hive_boxes.dart';
import '../local/models/app_settings.dart';
/// Runtime persistence for notification counters and cursors.
class NotificationRuntimeService {
NotificationRuntimeService._();
static final NotificationRuntimeService instance =
NotificationRuntimeService._();
static const _nonPrayerCountPrefix = 'non_prayer_push_count.';
static const _lastRemoteSyncKey = 'remote.last_sync_at';
static const _lastWeeklySummaryKey = 'summary.last_week_key';
Box get _box => Hive.box(HiveBoxes.notificationRuntime);
String _todayKey() => DateFormat('yyyy-MM-dd').format(DateTime.now());
int nonPrayerPushCountToday() {
return (_box.get('$_nonPrayerCountPrefix${_todayKey()}') as int?) ?? 0;
}
Future<void> incrementNonPrayerPushCount() async {
final key = '$_nonPrayerCountPrefix${_todayKey()}';
final next = ((_box.get(key) as int?) ?? 0) + 1;
await _box.put(key, next);
}
bool isWithinQuietHours(AppSettings settings, {DateTime? now}) {
final current = now ?? DateTime.now();
final startParts = _parseHourMinute(settings.quietHoursStart);
final endParts = _parseHourMinute(settings.quietHoursEnd);
if (startParts == null || endParts == null) return false;
final currentMinutes = current.hour * 60 + current.minute;
final startMinutes = startParts.$1 * 60 + startParts.$2;
final endMinutes = endParts.$1 * 60 + endParts.$2;
if (startMinutes == endMinutes) {
// Same value means quiet-hours disabled.
return false;
}
if (startMinutes < endMinutes) {
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
}
// Overnight interval (e.g. 22:00 -> 05:00).
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
}
bool canSendNonPrayerPush(AppSettings settings, {DateTime? now}) {
if (!settings.alertsEnabled) return false;
if (isWithinQuietHours(settings, now: now)) return false;
return nonPrayerPushCountToday() < settings.maxNonPrayerPushPerDay;
}
DateTime? lastRemoteSyncAt() {
final raw = (_box.get(_lastRemoteSyncKey) ?? '').toString();
if (raw.isEmpty) return null;
return DateTime.tryParse(raw);
}
Future<void> setLastRemoteSyncAt(DateTime value) async {
await _box.put(_lastRemoteSyncKey, value.toIso8601String());
}
String? lastWeeklySummaryWeekKey() {
final raw = (_box.get(_lastWeeklySummaryKey) ?? '').toString();
return raw.isEmpty ? null : raw;
}
Future<void> setLastWeeklySummaryWeekKey(String key) async {
await _box.put(_lastWeeklySummaryKey, key);
}
(int, int)? _parseHourMinute(String value) {
final match = RegExp(r'^(\d{1,2}):(\d{2})$').firstMatch(value.trim());
if (match == null) return null;
final hour = int.tryParse(match.group(1) ?? '');
final minute = int.tryParse(match.group(2) ?? '');
if (hour == null || minute == null) return null;
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
return (hour, minute);
}
}