fixing version issue
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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 (local-only v1).
|
||||
/// Mirrors core LocaleProvider: ChangeNotifier + SharedPreferences keys
|
||||
/// under the `app_*` convention + notifyListeners.
|
||||
/// Backend sync (AppUser.multitime) is a later phase.
|
||||
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 (same as sibling app).
|
||||
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> initialize() 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 (card button). Values persist on change already;
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,4 +174,17 @@
|
||||
,"ohSnap": "Oh Snap!"
|
||||
,"alertTitle": "Alert"
|
||||
,"successTitle": "Success"
|
||||
,"timeZoneSettings": "Time Zone Settings"
|
||||
,"timeZoneSubtitle": "Set your local time, date & week preferences"
|
||||
,"livePreview": "LIVE PREVIEW"
|
||||
,"currentTimeZone": "CURRENT TIME ZONE"
|
||||
,"commonTimeZones": "COMMON TIME ZONES"
|
||||
,"dateFormat": "DATE FORMAT"
|
||||
,"timeFormat": "TIME FORMAT"
|
||||
,"hour12": "12 Hour"
|
||||
,"hour24": "24 Hour"
|
||||
,"firstDayOfWeek": "FIRST DAY OF WEEK"
|
||||
,"storedInUtcNote": "Stored in UTC - converted to your time zone on display"
|
||||
,"savePreferences": "Save Preferences"
|
||||
,"preferencesSaved": "Preferences saved"
|
||||
}
|
||||
|
||||
@@ -173,4 +173,17 @@
|
||||
,"ohSnap": "ओह स्नैप!"
|
||||
,"alertTitle": "चेतावनी"
|
||||
,"successTitle": "सफलता"
|
||||
,"timeZoneSettings": "समय क्षेत्र सेटिंग"
|
||||
,"timeZoneSubtitle": "अपना स्थानीय समय, तिथि और सप्ताह वरीयताएँ सेट करें"
|
||||
,"livePreview": "लाइव पूर्वावलोकन"
|
||||
,"currentTimeZone": "वर्तमान समय क्षेत्र"
|
||||
,"commonTimeZones": "सामान्य समय क्षेत्र"
|
||||
,"dateFormat": "तिथि प्रारूप"
|
||||
,"timeFormat": "समय प्रारूप"
|
||||
,"hour12": "12 घंटे"
|
||||
,"hour24": "24 घंटे"
|
||||
,"firstDayOfWeek": "सप्ताह का पहला दिन"
|
||||
,"storedInUtcNote": "UTC में संग्रहीत - प्रदर्शन पर आपके समय क्षेत्र में बदला गया"
|
||||
,"savePreferences": "वरीयताएँ सहेजें"
|
||||
,"preferencesSaved": "वरीयताएँ सहेजी गईं"
|
||||
}
|
||||
|
||||
@@ -1075,6 +1075,84 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Success'**
|
||||
String get successTitle;
|
||||
|
||||
/// No description provided for @timeZoneSettings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Time Zone Settings'**
|
||||
String get timeZoneSettings;
|
||||
|
||||
/// No description provided for @timeZoneSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Set your local time, date & week preferences'**
|
||||
String get timeZoneSubtitle;
|
||||
|
||||
/// No description provided for @livePreview.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LIVE PREVIEW'**
|
||||
String get livePreview;
|
||||
|
||||
/// No description provided for @currentTimeZone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'CURRENT TIME ZONE'**
|
||||
String get currentTimeZone;
|
||||
|
||||
/// No description provided for @commonTimeZones.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'COMMON TIME ZONES'**
|
||||
String get commonTimeZones;
|
||||
|
||||
/// No description provided for @dateFormat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'DATE FORMAT'**
|
||||
String get dateFormat;
|
||||
|
||||
/// No description provided for @timeFormat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'TIME FORMAT'**
|
||||
String get timeFormat;
|
||||
|
||||
/// No description provided for @hour12.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'12 Hour'**
|
||||
String get hour12;
|
||||
|
||||
/// No description provided for @hour24.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'24 Hour'**
|
||||
String get hour24;
|
||||
|
||||
/// No description provided for @firstDayOfWeek.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'FIRST DAY OF WEEK'**
|
||||
String get firstDayOfWeek;
|
||||
|
||||
/// No description provided for @storedInUtcNote.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Stored in UTC - converted to your time zone on display'**
|
||||
String get storedInUtcNote;
|
||||
|
||||
/// No description provided for @savePreferences.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save Preferences'**
|
||||
String get savePreferences;
|
||||
|
||||
/// No description provided for @preferencesSaved.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Preferences saved'**
|
||||
String get preferencesSaved;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -523,4 +523,44 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get successTitle => 'Success';
|
||||
|
||||
@override
|
||||
String get timeZoneSettings => 'Time Zone Settings';
|
||||
|
||||
@override
|
||||
String get timeZoneSubtitle => 'Set your local time, date & week preferences';
|
||||
|
||||
@override
|
||||
String get livePreview => 'LIVE PREVIEW';
|
||||
|
||||
@override
|
||||
String get currentTimeZone => 'CURRENT TIME ZONE';
|
||||
|
||||
@override
|
||||
String get commonTimeZones => 'COMMON TIME ZONES';
|
||||
|
||||
@override
|
||||
String get dateFormat => 'DATE FORMAT';
|
||||
|
||||
@override
|
||||
String get timeFormat => 'TIME FORMAT';
|
||||
|
||||
@override
|
||||
String get hour12 => '12 Hour';
|
||||
|
||||
@override
|
||||
String get hour24 => '24 Hour';
|
||||
|
||||
@override
|
||||
String get firstDayOfWeek => 'FIRST DAY OF WEEK';
|
||||
|
||||
@override
|
||||
String get storedInUtcNote =>
|
||||
'Stored in UTC - converted to your time zone on display';
|
||||
|
||||
@override
|
||||
String get savePreferences => 'Save Preferences';
|
||||
|
||||
@override
|
||||
String get preferencesSaved => 'Preferences saved';
|
||||
}
|
||||
|
||||
@@ -523,4 +523,45 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get successTitle => 'सफलता';
|
||||
|
||||
@override
|
||||
String get timeZoneSettings => 'समय क्षेत्र सेटिंग';
|
||||
|
||||
@override
|
||||
String get timeZoneSubtitle =>
|
||||
'अपना स्थानीय समय, तिथि और सप्ताह वरीयताएँ सेट करें';
|
||||
|
||||
@override
|
||||
String get livePreview => 'लाइव पूर्वावलोकन';
|
||||
|
||||
@override
|
||||
String get currentTimeZone => 'वर्तमान समय क्षेत्र';
|
||||
|
||||
@override
|
||||
String get commonTimeZones => 'सामान्य समय क्षेत्र';
|
||||
|
||||
@override
|
||||
String get dateFormat => 'तिथि प्रारूप';
|
||||
|
||||
@override
|
||||
String get timeFormat => 'समय प्रारूप';
|
||||
|
||||
@override
|
||||
String get hour12 => '12 घंटे';
|
||||
|
||||
@override
|
||||
String get hour24 => '24 घंटे';
|
||||
|
||||
@override
|
||||
String get firstDayOfWeek => 'सप्ताह का पहला दिन';
|
||||
|
||||
@override
|
||||
String get storedInUtcNote =>
|
||||
'UTC में संग्रहीत - प्रदर्शन पर आपके समय क्षेत्र में बदला गया';
|
||||
|
||||
@override
|
||||
String get savePreferences => 'वरीयताएँ सहेजें';
|
||||
|
||||
@override
|
||||
String get preferencesSaved => 'वरीयताएँ सहेजी गईं';
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import 'package:provider/provider.dart';
|
||||
import 'core/providers/dynamic_theme_provider.dart';
|
||||
import 'core/providers/locale_provider.dart';
|
||||
import 'core/providers/theme_provider.dart';
|
||||
import 'core/providers/timezone_provider.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tzdata;
|
||||
import 'l10n/app_localizations.dart';
|
||||
import 'routes/app_routes.dart';
|
||||
import 'utils/managers/user_manager.dart';
|
||||
@@ -19,6 +21,10 @@ void main() async {
|
||||
await UserManager().initialize();
|
||||
final localeProvider = LocaleProvider();
|
||||
await localeProvider.initialize();
|
||||
// Time-zone display prefs (local-only v1): IANA DB + persisted selection.
|
||||
tzdata.initializeTimeZones();
|
||||
final timezoneProvider = TimezoneProvider();
|
||||
await timezoneProvider.initialize();
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
runApp(MultiProvider(
|
||||
@@ -29,6 +35,7 @@ void main() async {
|
||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
||||
ChangeNotifierProvider(create: (context) => DynamicThemeProvider()),
|
||||
ChangeNotifierProvider.value(value: localeProvider),
|
||||
ChangeNotifierProvider.value(value: timezoneProvider),
|
||||
],
|
||||
child: const MyApp(),
|
||||
));
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:base_project/core/constants/ui_constants.dart';
|
||||
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
|
||||
import 'package:base_project/core/providers/locale_provider.dart';
|
||||
import 'package:base_project/core/providers/timezone_provider.dart';
|
||||
import 'package:base_project/l10n/app_localizations.dart';
|
||||
import 'package:base_project/resources/app_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Time Zone Settings card, themed for base_project.
|
||||
/// - Live ticking preview in the SELECTED zone (see time here / there).
|
||||
/// - Current zone dropdown + common-zone quick chips.
|
||||
/// - Date format / 12-24h / week-start prefs, persisted locally.
|
||||
/// - Theming: surfaces + text from Theme colorScheme (dark-mode safe),
|
||||
/// header/preview gradients from DynamicThemeProvider (logo-derived),
|
||||
/// radii/spacing from UIConstants, inputs/buttons/chips from AppTheme.
|
||||
/// - Multilingual: every label via AppLocalizations (EN/HI).
|
||||
class TimezoneSettingsCard extends StatefulWidget {
|
||||
const TimezoneSettingsCard({super.key});
|
||||
|
||||
@override
|
||||
State<TimezoneSettingsCard> createState() => _TimezoneSettingsCardState();
|
||||
}
|
||||
|
||||
class _TimezoneSettingsCardState extends State<TimezoneSettingsCard> {
|
||||
Timer? _ticker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final strings = AppLocalizations.of(context)!;
|
||||
final localeCode = context.watch<LocaleProvider>().locale.languageCode;
|
||||
// Listen to prefs so preview + chips update instantly on selection.
|
||||
final tzPrefs = context.watch<TimezoneProvider>();
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final gradients =
|
||||
context.watch<DynamicThemeProvider>().getGradientColors();
|
||||
final gradStart =
|
||||
gradients.isNotEmpty ? gradients.first : AppColors.primary;
|
||||
final gradEnd =
|
||||
gradients.length > 1 ? gradients.last : AppColors.primary2;
|
||||
|
||||
final now = tzPrefs.nowInZone();
|
||||
final timePattern = tzPrefs.is24h ? 'HH:mm:ss' : 'hh:mm:ss a';
|
||||
final timeStr = DateFormat(timePattern, localeCode).format(now);
|
||||
final dateLine =
|
||||
DateFormat('EEEE, MMMM d, yyyy', localeCode).format(now);
|
||||
final offset = tzPrefs.offsetLabel(now);
|
||||
final city = TimezoneProvider.cityOf(tzPrefs.zoneId);
|
||||
final onGradient = Colors.white;
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(UIConstants.spacing16,
|
||||
UIConstants.spacing14, UIConstants.spacing16, UIConstants.spacing14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [gradStart, gradEnd],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UIConstants.spacing8),
|
||||
decoration: BoxDecoration(
|
||||
color: onGradient.withValues(alpha: 0.2),
|
||||
borderRadius:
|
||||
BorderRadius.circular(UIConstants.radius12),
|
||||
),
|
||||
child: Icon(Icons.public,
|
||||
color: onGradient, size: UIConstants.iconSizeMedium),
|
||||
),
|
||||
const SizedBox(width: UIConstants.spacing12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(strings.timeZoneSettings,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
color: onGradient,
|
||||
fontWeight: FontWeight.w700)),
|
||||
const SizedBox(height: 2),
|
||||
Text(strings.timeZoneSubtitle,
|
||||
style: textTheme.bodySmall
|
||||
?.copyWith(color: onGradient.withValues(alpha: 0.85))),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.spacing12,
|
||||
vertical: UIConstants.spacing4),
|
||||
decoration: BoxDecoration(
|
||||
color: onGradient.withValues(alpha: 0.2),
|
||||
borderRadius:
|
||||
BorderRadius.circular(UIConstants.radiusFull),
|
||||
),
|
||||
child: Text('UTC',
|
||||
style: TextStyle(
|
||||
color: onGradient,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UIConstants.spacing16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Live preview
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UIConstants.spacing16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
gradStart.withValues(alpha: 0.9),
|
||||
gradEnd.withValues(alpha: 0.9)
|
||||
],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius:
|
||||
BorderRadius.circular(UIConstants.radius16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('◷ ${strings.livePreview}',
|
||||
style: TextStyle(
|
||||
color: onGradient.withValues(alpha: 0.8),
|
||||
fontSize: 11,
|
||||
letterSpacing: 1.2)),
|
||||
const SizedBox(height: UIConstants.spacing8),
|
||||
Text(timeStr,
|
||||
style: TextStyle(
|
||||
color: onGradient,
|
||||
fontSize: 38,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 1)),
|
||||
const SizedBox(height: 2),
|
||||
Text(dateLine,
|
||||
style: TextStyle(
|
||||
color: onGradient.withValues(alpha: 0.9),
|
||||
fontSize: 13)),
|
||||
const SizedBox(height: UIConstants.spacing12),
|
||||
Wrap(
|
||||
spacing: UIConstants.spacing8,
|
||||
runSpacing: UIConstants.spacing8,
|
||||
children: [
|
||||
_miniChip('◉ $city', onGradient),
|
||||
_miniChip(offset, onGradient),
|
||||
_miniChip(tzPrefs.datePattern, onGradient),
|
||||
_miniChip(
|
||||
tzPrefs.is24h
|
||||
? strings.hour24
|
||||
: strings.hour12,
|
||||
onGradient),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UIConstants.spacing16),
|
||||
_sectionLabel(strings.currentTimeZone, colorScheme),
|
||||
const SizedBox(height: UIConstants.spacing8),
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey('zone_${tzPrefs.zoneId}'),
|
||||
initialValue: tzPrefs.zoneId,
|
||||
items: TimezoneProvider.commonZoneIds
|
||||
.map((id) => DropdownMenuItem(
|
||||
value: id,
|
||||
child: Text(tzPrefs.displayLabel(id),
|
||||
style: textTheme.bodyMedium)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) tzPrefs.setZone(v);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UIConstants.spacing16),
|
||||
_sectionLabel(strings.commonTimeZones, colorScheme),
|
||||
const SizedBox(height: UIConstants.spacing8),
|
||||
Wrap(
|
||||
spacing: UIConstants.spacing8,
|
||||
runSpacing: UIConstants.spacing8,
|
||||
children: TimezoneProvider.commonZoneIds.map((id) {
|
||||
return ChoiceChip(
|
||||
label: Text(TimezoneProvider.cityOf(id)),
|
||||
selected: id == tzPrefs.zoneId,
|
||||
onSelected: (_) => tzPrefs.setZone(id),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: UIConstants.spacing16),
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
final narrow = constraints.maxWidth < 520;
|
||||
final fields = [
|
||||
_labelled(strings.dateFormat,
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey('pat_${tzPrefs.datePattern}'),
|
||||
initialValue: tzPrefs.datePattern,
|
||||
items: TimezoneProvider.datePatterns
|
||||
.map((p) => DropdownMenuItem(
|
||||
value: p,
|
||||
child: Text(p,
|
||||
style: textTheme.bodyMedium)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) tzPrefs.setDatePattern(v);
|
||||
},
|
||||
)),
|
||||
_labelled(
|
||||
strings.timeFormat,
|
||||
SegmentedButton<bool>(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
label: Text(strings.hour12)),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
label: Text(strings.hour24)),
|
||||
],
|
||||
selected: {tzPrefs.is24h},
|
||||
onSelectionChanged: (s) =>
|
||||
tzPrefs.setIs24h(s.first),
|
||||
)),
|
||||
_labelled(strings.firstDayOfWeek,
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey('ws_${tzPrefs.weekStart}'),
|
||||
initialValue: tzPrefs.weekStart,
|
||||
items: TimezoneProvider.weekDays
|
||||
.map((d) => DropdownMenuItem(
|
||||
value: d,
|
||||
child: Text(
|
||||
_localizedWeekday(d, localeCode),
|
||||
style: textTheme.bodyMedium)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) tzPrefs.setWeekStart(v);
|
||||
},
|
||||
)),
|
||||
];
|
||||
if (narrow) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
fields[0],
|
||||
const SizedBox(height: UIConstants.spacing12),
|
||||
fields[1],
|
||||
const SizedBox(height: UIConstants.spacing12),
|
||||
fields[2],
|
||||
],
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: fields[0]),
|
||||
const SizedBox(width: UIConstants.spacing8),
|
||||
Expanded(child: fields[1]),
|
||||
const SizedBox(width: UIConstants.spacing8),
|
||||
Expanded(child: fields[2]),
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: UIConstants.spacing16),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline,
|
||||
size: UIConstants.iconSizeSmall,
|
||||
color: colorScheme.onSurfaceVariant),
|
||||
const SizedBox(width: UIConstants.spacing8),
|
||||
Expanded(
|
||||
child: Text(strings.storedInUtcNote,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UIConstants.spacing12),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
await tzPrefs.save();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content:
|
||||
Text(strings.preferencesSaved)),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save_outlined, size: 16),
|
||||
label: Text(strings.savePreferences),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sectionLabel(String label, ColorScheme colorScheme) {
|
||||
return Text('◉ $label',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.6,
|
||||
color: colorScheme.onSurfaceVariant));
|
||||
}
|
||||
|
||||
Widget _miniChip(String text, Color onGradient) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.spacing12,
|
||||
vertical: UIConstants.spacing4),
|
||||
decoration: BoxDecoration(
|
||||
color: onGradient.withValues(alpha: 0.18),
|
||||
borderRadius: BorderRadius.circular(UIConstants.radiusFull),
|
||||
),
|
||||
child: Text(text, style: TextStyle(color: onGradient, fontSize: 11)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _labelled(String label, Widget child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Builder(builder: (context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.6,
|
||||
color: colorScheme.onSurfaceVariant));
|
||||
}),
|
||||
const SizedBox(height: UIConstants.spacing8),
|
||||
child,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Weekday names localized via intl for the current locale; values
|
||||
/// stay canonical English for storage.
|
||||
String _localizedWeekday(String canonical, String localeCode) {
|
||||
// 2026-10-05 is a Monday; add index to get each weekday name.
|
||||
final base = DateTime(2026, 10, 5);
|
||||
final idx = TimezoneProvider.weekDays.indexOf(canonical);
|
||||
if (idx < 0) return canonical;
|
||||
return DateFormat('EEEE', localeCode)
|
||||
.format(base.add(Duration(days: idx)));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
|
||||
import 'package:base_project/core/constants/ui_constants.dart';
|
||||
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
|
||||
import 'package:base_project/shared/widgets/timezone_settings_card.dart';
|
||||
|
||||
import 'account_update.dart';
|
||||
|
||||
@@ -131,6 +132,17 @@ class _ProfileViewState extends State<ProfileView>
|
||||
|
||||
// Profile Actions Section
|
||||
_buildProfileActions(context, colorScheme),
|
||||
|
||||
SizedBox(
|
||||
height: UIConstants.getResponsiveSpacing(
|
||||
context,
|
||||
mobile: UIConstants.spacing32,
|
||||
tablet: UIConstants.spacing40,
|
||||
desktop: UIConstants.spacing48,
|
||||
)),
|
||||
|
||||
// Time Zone Settings Section (local display prefs)
|
||||
const TimezoneSettingsCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -601,10 +601,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.6.0"
|
||||
http_parser:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1362,6 +1362,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
timezone:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: timezone
|
||||
sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -49,7 +49,10 @@ dependencies:
|
||||
path: ^1.9.0
|
||||
flutter_svg_provider: ^1.0.7
|
||||
pinput: ^5.0.1
|
||||
intl: ^0.20.2
|
||||
# Wide range so both SDK pins resolve: Docker Flutter 3.41.1 pins intl
|
||||
# 0.19.0 via flutter_localizations, local Flutter 3.47.x resolves 0.20.x.
|
||||
intl: '>=0.19.0 <0.21.0'
|
||||
timezone: ^0.11.1
|
||||
http_parser: ^4.1.2
|
||||
dotted_border: ^2.1.0
|
||||
redis: ^4.0.0
|
||||
|
||||
Reference in New Issue
Block a user