180 lines
5.1 KiB
Dart
180 lines
5.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:timezone/timezone.dart' as tz;
|
|
|
|
/// Single source of truth for time-zone display prefs (v1: local-only).
|
|
/// Mirrors LocaleProvider: ChangeNotifier + SharedPreferences + notifyListeners.
|
|
/// Backend sync (AppUser.multitime) is a later phase; for now prefs stay on-device
|
|
/// so the settings card in the screenshot works standalone + multilingual.
|
|
class TimezoneProvider with ChangeNotifier {
|
|
static const _zoneKey = 'app_timezone_id';
|
|
static const _patternKey = 'app_date_pattern';
|
|
static const _is24hKey = 'app_time_24h';
|
|
static const _weekStartKey = 'app_week_start';
|
|
|
|
/// Curated quick-pick list matching the settings mock.
|
|
static const commonZoneIds = [
|
|
'Asia/Kolkata',
|
|
'Asia/Dubai',
|
|
'Asia/Singapore',
|
|
'Asia/Kuala_Lumpur',
|
|
'Asia/Hong_Kong',
|
|
'Asia/Shanghai',
|
|
'Asia/Taipei',
|
|
'Asia/Tokyo',
|
|
'Asia/Seoul',
|
|
'Asia/Bangkok',
|
|
'Asia/Jakarta',
|
|
'Asia/Manila',
|
|
'Asia/Karachi',
|
|
'Europe/London',
|
|
'Europe/Paris',
|
|
'Europe/Berlin',
|
|
'America/New_York',
|
|
'America/Chicago',
|
|
'America/Denver',
|
|
'America/Los_Angeles',
|
|
'America/Toronto',
|
|
'Australia/Sydney',
|
|
'UTC',
|
|
];
|
|
|
|
static const datePatterns = ['dd-MM-yyyy', 'MM-dd-yyyy', 'yyyy-MM-dd'];
|
|
static const weekDays = [
|
|
'Monday',
|
|
'Tuesday',
|
|
'Wednesday',
|
|
'Thursday',
|
|
'Friday',
|
|
'Saturday',
|
|
'Sunday',
|
|
];
|
|
|
|
String _zoneId = 'Asia/Kolkata';
|
|
String get zoneId => _zoneId;
|
|
|
|
String _datePattern = 'dd-MM-yyyy';
|
|
String get datePattern => _datePattern;
|
|
|
|
bool _is24h = true;
|
|
bool get is24h => _is24h;
|
|
|
|
String _weekStart = 'Monday';
|
|
String get weekStart => _weekStart;
|
|
|
|
bool _ready = false;
|
|
bool get ready => _ready;
|
|
|
|
/// Short city label: 'Asia/Kolkata' -> 'Kolkata', 'UTC' -> 'UTC'.
|
|
static String cityOf(String id) {
|
|
if (id == 'UTC') return 'UTC';
|
|
final parts = id.split('/');
|
|
final raw = parts.isNotEmpty ? parts.last : id;
|
|
return raw.replaceAll('_', ' ');
|
|
}
|
|
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final z = prefs.getString(_zoneKey);
|
|
if (z != null && z.isNotEmpty) _zoneId = z;
|
|
final p = prefs.getString(_patternKey);
|
|
if (p != null && datePatterns.contains(p)) _datePattern = p;
|
|
final h = prefs.getBool(_is24hKey);
|
|
if (h != null) _is24h = h;
|
|
final w = prefs.getString(_weekStartKey);
|
|
if (w != null && weekDays.contains(w)) _weekStart = w;
|
|
_ready = true;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setZone(String id) async {
|
|
_zoneId = id;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_zoneKey, id);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setDatePattern(String p) async {
|
|
if (!datePatterns.contains(p)) return;
|
|
_datePattern = p;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_patternKey, p);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setIs24h(bool v) async {
|
|
_is24h = v;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_is24hKey, v);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setWeekStart(String d) async {
|
|
if (!weekDays.contains(d)) return;
|
|
_weekStart = d;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_weekStartKey, d);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Explicit save (button in the mock). Values are already persisted on
|
|
/// change; this re-persists + notifies so callers can show a snackbar.
|
|
Future<void> save() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_zoneKey, _zoneId);
|
|
await prefs.setString(_patternKey, _datePattern);
|
|
await prefs.setBool(_is24hKey, _is24h);
|
|
await prefs.setString(_weekStartKey, _weekStart);
|
|
notifyListeners();
|
|
}
|
|
|
|
tz.Location get location {
|
|
try {
|
|
return tz.getLocation(_zoneId);
|
|
} catch (_) {
|
|
return tz.getLocation('UTC');
|
|
}
|
|
}
|
|
|
|
tz.TZDateTime nowInZone() {
|
|
try {
|
|
return tz.TZDateTime.now(location);
|
|
} catch (_) {
|
|
return tz.TZDateTime.now(tz.getLocation('UTC'));
|
|
}
|
|
}
|
|
|
|
/// e.g. 'GMT+5:30', 'GMT-4:00', 'GMT'. DST-aware via timezone DB.
|
|
String offsetLabel([tz.TZDateTime? at]) {
|
|
try {
|
|
final dt = at ?? nowInZone();
|
|
final offset = dt.timeZoneOffset;
|
|
if (offset == Duration.zero) return 'GMT';
|
|
final sign = offset.isNegative ? '-' : '+';
|
|
final abs = offset.abs();
|
|
final h = abs.inHours.toString();
|
|
final m = (abs.inMinutes % 60).toString().padLeft(2, '0');
|
|
return 'GMT$sign$h:$m';
|
|
} catch (_) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/// 'Kolkata (GMT+5:30)' style label for dropdown rows.
|
|
String displayLabel(String id) {
|
|
try {
|
|
final loc = tz.getLocation(id);
|
|
final now = tz.TZDateTime.now(loc);
|
|
final offset = now.timeZoneOffset;
|
|
final sign = offset.isNegative ? '-' : '+';
|
|
final abs = offset.abs();
|
|
final off = offset == Duration.zero
|
|
? 'GMT'
|
|
: 'GMT$sign${abs.inHours}:${(abs.inMinutes % 60).toString().padLeft(2, '0')}';
|
|
return '${cityOf(id)} ($off)';
|
|
} catch (_) {
|
|
return cityOf(id);
|
|
}
|
|
}
|
|
}
|