import 'dart:async'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../l10n/app_localizations.dart'; import '../providers/locale_provider.dart'; import '../providers/timezone_provider.dart'; /// Standalone Time Zone Settings card (matches mock). /// - 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, all persisted locally. /// - Fully multilingual: every label via AppLocalizations, so the EN/HI /// LanguageSwitcher rebuilds this card with no extra work. class TimezoneSettingsCard extends StatefulWidget { const TimezoneSettingsCard({super.key}); @override State createState() => _TimezoneSettingsCardState(); } class _TimezoneSettingsCardState extends State { Timer? _ticker; DateTime _tick = DateTime.now(); @override void initState() { super.initState(); _ticker = Timer.periodic(const Duration(seconds: 1), (_) { if (mounted) setState(() => _tick = DateTime.now()); }); } @override void dispose() { _ticker?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final localeCode = Provider.of(context).code; // Listen to prefs so preview + chips update instantly on selection. final tzPrefs = Provider.of(context); // Keep linter happy about the 1s tick even though zone time derives // from provider clock below. _tick; 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); return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.08), blurRadius: 24, offset: const Offset(0, 8), ), ], ), clipBehavior: Clip.antiAlias, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Header Container( padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF3B3BD6), Color(0xFF7B2FF7)], begin: Alignment.centerLeft, end: Alignment.centerRight, ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon(Icons.public, color: Colors.white, size: 22), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.timeZoneSettings, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w700)), const SizedBox(height: 2), Text(l10n.timeZoneSubtitle, style: TextStyle( color: Colors.white.withValues(alpha: 0.85), fontSize: 11)), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), ), child: const Text('UTC', style: TextStyle(color: Colors.white, fontSize: 11)), ), ], ), ), Padding( padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Live preview Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF1E2A78), Color(0xFF7B2FF7)], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: BorderRadius.all(Radius.circular(16)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('◷ ${l10n.livePreview}', style: TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 11, letterSpacing: 1.2)), const SizedBox(height: 6), Text(timeStr, style: const TextStyle( color: Colors.white, fontSize: 38, fontWeight: FontWeight.w800, letterSpacing: 1)), const SizedBox(height: 2), Text(dateLine, style: TextStyle( color: Colors.white.withValues(alpha: 0.9), fontSize: 13)), const SizedBox(height: 10), Wrap( spacing: 6, runSpacing: 6, children: [ _miniChip('◉ $city'), _miniChip(offset), _miniChip(tzPrefs.datePattern), _miniChip(tzPrefs.is24h ? l10n.hour24 : l10n.hour12), ], ), ], ), ), const SizedBox(height: 14), Text('◉ ${l10n.currentTimeZone}', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 0.6, color: Colors.black54)), const SizedBox(height: 6), DropdownButtonFormField( // ignore: deprecated_member_use value: tzPrefs.zoneId, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10), ), items: TimezoneProvider.commonZoneIds .map((id) => DropdownMenuItem( value: id, child: Text(tzPrefs.displayLabel(id), style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (v) { if (v != null) tzPrefs.setZone(v); }, ), const SizedBox(height: 12), Text('★ ${l10n.commonTimeZones}', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 0.6, color: Colors.black54)), const SizedBox(height: 6), Wrap( spacing: 6, runSpacing: 6, children: TimezoneProvider.commonZoneIds.map((id) { final selected = id == tzPrefs.zoneId; return ChoiceChip( label: Text( TimezoneProvider.cityOf(id), style: TextStyle( fontSize: 11, color: selected ? Colors.white : Colors.black87)), selected: selected, selectedColor: const Color(0xFF7B2FF7), backgroundColor: Colors.grey.shade100, onSelected: (_) => tzPrefs.setZone(id), ); }).toList(), ), const SizedBox(height: 12), LayoutBuilder(builder: (context, constraints) { final narrow = constraints.maxWidth < 520; Widget row(Widget a, Widget b, Widget c) { if (narrow) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [a, const SizedBox(height: 8), b, const SizedBox(height: 8), c], ); } return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: a), const SizedBox(width: 8), Expanded(child: b), const SizedBox(width: 8), Expanded(child: c), ], ); } return row( _labelled(l10n.dateFormat, DropdownButtonFormField( // ignore: deprecated_member_use value: tzPrefs.datePattern, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10), ), items: TimezoneProvider.datePatterns .map((p) => DropdownMenuItem( value: p, child: Text(p, style: const TextStyle( fontSize: 12)))) .toList(), onChanged: (v) { if (v != null) tzPrefs.setDatePattern(v); }, )), _labelled( l10n.timeFormat, SegmentedButton( style: const ButtonStyle( visualDensity: VisualDensity.compact), segments: [ ButtonSegment( value: false, label: Text(l10n.hour12)), ButtonSegment( value: true, label: Text(l10n.hour24)), ], selected: {tzPrefs.is24h}, onSelectionChanged: (s) => tzPrefs.setIs24h(s.first), )), _labelled(l10n.firstDayOfWeek, DropdownButtonFormField( // ignore: deprecated_member_use value: tzPrefs.weekStart, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10), ), items: TimezoneProvider.weekDays .map((d) => DropdownMenuItem( value: d, child: Text( _localizedWeekday( d, localeCode), style: const TextStyle( fontSize: 12)))) .toList(), onChanged: (v) { if (v != null) tzPrefs.setWeekStart(v); }, )), ); }), const SizedBox(height: 12), Row( children: [ const Icon(Icons.info_outline, size: 14, color: Colors.black45), const SizedBox(width: 6), Expanded( child: Text(l10n.storedInUtcNote, style: const TextStyle( fontSize: 11, color: Colors.black54)), ), ], ), const SizedBox(height: 10), Align( alignment: Alignment.centerRight, child: DecoratedBox( decoration: const BoxDecoration( gradient: LinearGradient(colors: [ Color(0xFF3B3BD6), Color(0xFFE040FB) ]), borderRadius: BorderRadius.all(Radius.circular(12)), ), child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), onPressed: () async { await tzPrefs.save(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(l10n.preferencesSaved)), ); } }, icon: const Icon(Icons.save_outlined, size: 16, color: Colors.white), label: Text(l10n.savePreferences, style: const TextStyle(color: Colors.white)), ), ), ), ], ), ), ], ), ); } Widget _miniChip(String text) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(20), ), child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 11)), ); } Widget _labelled(String label, Widget child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 0.6, color: Colors.black54)), const SizedBox(height: 6), 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))); } }