feat(legacy-settings): DST-aware timezone with phone auto-detect
This commit is contained in:
@@ -18,11 +18,13 @@ import 'package:navigation/navigation.dart';
|
|||||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||||
import 'package:sf_tracking/sf_tracking.dart';
|
import 'package:sf_tracking/sf_tracking.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:timezone/data/latest_all.dart' as tz;
|
||||||
|
|
||||||
Future<void> initApp(EnvironmentEnum env) async {
|
Future<void> initApp(EnvironmentEnum env) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
await initializeDateFormatting();
|
await initializeDateFormatting();
|
||||||
|
tz.initializeTimeZones();
|
||||||
|
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter_svg: ^2.2.2
|
flutter_svg: ^2.2.2
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
timezone: ^0.10.1
|
||||||
go_router_builder: ^4.1.1
|
go_router_builder: ^4.1.1
|
||||||
build_runner: ^2.7.1
|
build_runner: ^2.7.1
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ part 'timezone_selection_provider.g.dart';
|
|||||||
@riverpod
|
@riverpod
|
||||||
class TimezoneSelection extends _$TimezoneSelection {
|
class TimezoneSelection extends _$TimezoneSelection {
|
||||||
@override
|
@override
|
||||||
int? build() => null;
|
String? build() => null;
|
||||||
|
|
||||||
void select(int value) => state = value;
|
void select(String iana) => state = iana;
|
||||||
|
|
||||||
|
void clear() => state = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ part of 'timezone_selection_provider.dart';
|
|||||||
const timezoneSelectionProvider = TimezoneSelectionProvider._();
|
const timezoneSelectionProvider = TimezoneSelectionProvider._();
|
||||||
|
|
||||||
final class TimezoneSelectionProvider
|
final class TimezoneSelectionProvider
|
||||||
extends $NotifierProvider<TimezoneSelection, int?> {
|
extends $NotifierProvider<TimezoneSelection, String?> {
|
||||||
const TimezoneSelectionProvider._()
|
const TimezoneSelectionProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
@@ -33,28 +33,28 @@ final class TimezoneSelectionProvider
|
|||||||
TimezoneSelection create() => TimezoneSelection();
|
TimezoneSelection create() => TimezoneSelection();
|
||||||
|
|
||||||
/// {@macro riverpod.override_with_value}
|
/// {@macro riverpod.override_with_value}
|
||||||
Override overrideWithValue(int? value) {
|
Override overrideWithValue(String? value) {
|
||||||
return $ProviderOverride(
|
return $ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
providerOverride: $SyncValueProvider<int?>(value),
|
providerOverride: $SyncValueProvider<String?>(value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$timezoneSelectionHash() => r'f108a0d1664e0c7a8423b1ec735745ac1781795b';
|
String _$timezoneSelectionHash() => r'97150a64513baae3f18edde7da8396b91f4f1e2f';
|
||||||
|
|
||||||
abstract class _$TimezoneSelection extends $Notifier<int?> {
|
abstract class _$TimezoneSelection extends $Notifier<String?> {
|
||||||
int? build();
|
String? build();
|
||||||
@$mustCallSuper
|
@$mustCallSuper
|
||||||
@override
|
@override
|
||||||
void runBuild() {
|
void runBuild() {
|
||||||
final created = build();
|
final created = build();
|
||||||
final ref = this.ref as $Ref<int?, int?>;
|
final ref = this.ref as $Ref<String?, String?>;
|
||||||
final element =
|
final element =
|
||||||
ref.element
|
ref.element
|
||||||
as $ClassProviderElement<
|
as $ClassProviderElement<
|
||||||
AnyNotifier<int?, int?>,
|
AnyNotifier<String?, String?>,
|
||||||
int?,
|
String?,
|
||||||
Object?,
|
Object?,
|
||||||
Object?
|
Object?
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -21,185 +21,194 @@ class TimezoneScreen extends ConsumerWidget {
|
|||||||
prev.isLoading &&
|
prev.isLoading &&
|
||||||
!next.isLoading &&
|
!next.isLoading &&
|
||||||
!next.hasError) {
|
!next.hasError) {
|
||||||
|
ref.read(timezoneSelectionProvider.notifier).clear();
|
||||||
await showSuccessDialog(context, I18n.deviceUpdatedSuccess);
|
await showSuccessDialog(context, I18n.deviceUpdatedSuccess);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final primaryColor = context.sfColors.legacyPrimary;
|
final primaryColor = context.sfColors.legacyPrimary;
|
||||||
final device = ref.watch(selectedDeviceProvider).value;
|
final device = ref.watch(selectedDeviceProvider).value;
|
||||||
final currentTimezone = device?.settings.timezone ?? 0;
|
final selectedIana = ref.watch(timezoneSelectionProvider);
|
||||||
final selectedTimezone =
|
|
||||||
ref.watch(timezoneSelectionProvider) ?? currentTimezone;
|
|
||||||
final isSaving = ref.watch(
|
final isSaving = ref.watch(
|
||||||
timezoneControllerProvider.select((s) => s.isLoading),
|
timezoneControllerProvider.select((s) => s.isLoading),
|
||||||
);
|
);
|
||||||
|
|
||||||
final selected = timezoneEntries
|
Future<void> onPhoneTap() async {
|
||||||
.where((t) => t.$1 == selectedTimezone)
|
if (device == null) return;
|
||||||
.firstOrNull;
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
final others = timezoneEntries
|
if (!context.mounted) return;
|
||||||
.where((t) => t.$1 != selectedTimezone)
|
ref
|
||||||
.toList();
|
.read(timezoneControllerProvider.notifier)
|
||||||
|
.save(device: device, newTimezone: phoneOffsetHours());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSaveSelection() async {
|
||||||
|
if (device == null || selectedIana == null) return;
|
||||||
|
if (!await guardDeviceCommand(context, ref)) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
final entry = timezoneEntries.firstWhere((e) => e.iana == selectedIana);
|
||||||
|
ref.read(timezoneControllerProvider.notifier).save(
|
||||||
|
device: device,
|
||||||
|
newTimezone: entry.currentOffsetHours,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return LegacyPageLayout(
|
return LegacyPageLayout(
|
||||||
title: context.translate(I18n.timezone),
|
title: context.translate(I18n.timezone),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
children: [
|
children: [
|
||||||
if (selected != null) ...[
|
_UsePhoneCard(
|
||||||
_SelectedTimezoneCard(
|
primaryColor: primaryColor,
|
||||||
city: selected.$2,
|
onTap: isSaving ? null : onPhoneTap,
|
||||||
continent: selected.$3,
|
),
|
||||||
label: formatUtcOffset(selected.$1),
|
const SizedBox(height: 24),
|
||||||
primaryColor: primaryColor,
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
||||||
const SizedBox(height: 20),
|
child: Text(
|
||||||
Padding(
|
context.translate(I18n.timezoneOther),
|
||||||
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
style: TextStyle(
|
||||||
child: Text(
|
fontSize: 13,
|
||||||
context.translate(I18n.timezoneOther),
|
fontWeight: FontWeight.w600,
|
||||||
style: TextStyle(
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
...others.map(
|
...timezoneEntries.map(
|
||||||
(tz) => _TimezoneItem(
|
(tz) => _TimezoneItem(
|
||||||
city: tz.$2,
|
entry: tz,
|
||||||
continent: tz.$3,
|
selected: tz.iana == selectedIana,
|
||||||
label: formatUtcOffset(tz.$1),
|
primaryColor: primaryColor,
|
||||||
onTap: () => ref
|
onTap: () => ref
|
||||||
.read(timezoneSelectionProvider.notifier)
|
.read(timezoneSelectionProvider.notifier)
|
||||||
.select(tz.$1),
|
.select(tz.iana),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
footer: Padding(
|
footer: selectedIana == null
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
? null
|
||||||
child: PrimaryButton(
|
: Padding(
|
||||||
onPressed: isSaving
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||||
? null
|
child: PrimaryButton(
|
||||||
: () async {
|
onPressed: isSaving ? null : onSaveSelection,
|
||||||
if (device == null) return;
|
text: isSaving ? '...' : context.translate(I18n.save),
|
||||||
if (!await guardDeviceCommand(context, ref)) return;
|
color: primaryColor,
|
||||||
if (!context.mounted) return;
|
),
|
||||||
ref.read(timezoneControllerProvider.notifier).save(
|
),
|
||||||
device: device,
|
);
|
||||||
newTimezone: selectedTimezone,
|
}
|
||||||
);
|
}
|
||||||
},
|
|
||||||
text: isSaving ? '...' : context.translate(I18n.save),
|
class _UsePhoneCard extends StatelessWidget {
|
||||||
color: primaryColor,
|
final Color primaryColor;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const _UsePhoneCard({required this.primaryColor, required this.onTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final offsetLabel = formatUtcOffset(phoneOffsetHours());
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor.withValues(alpha: 0.08),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: primaryColor.withValues(alpha: 0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(Icons.phone_iphone, color: Colors.white, size: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translate(I18n.timezoneUsePhone),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
offsetLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: primaryColor.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(Icons.chevron_right, color: primaryColor, size: 24),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SelectedTimezoneCard extends StatelessWidget {
|
|
||||||
final String city;
|
|
||||||
final String continent;
|
|
||||||
final String label;
|
|
||||||
final Color primaryColor;
|
|
||||||
|
|
||||||
const _SelectedTimezoneCard({
|
|
||||||
required this.city,
|
|
||||||
required this.continent,
|
|
||||||
required this.label,
|
|
||||||
required this.primaryColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: primaryColor.withValues(alpha: 0.08),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: primaryColor.withValues(alpha: 0.3)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 52,
|
|
||||||
height: 52,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: primaryColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: Icon(Icons.access_time, color: Colors.white, size: 28),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'$city · $continent',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: primaryColor.withValues(alpha: 0.7),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(Icons.check_circle, color: primaryColor, size: 28),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TimezoneItem extends StatelessWidget {
|
class _TimezoneItem extends StatelessWidget {
|
||||||
final String city;
|
final TimezoneEntry entry;
|
||||||
final String continent;
|
final bool selected;
|
||||||
final String label;
|
final Color primaryColor;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
|
||||||
const _TimezoneItem({
|
const _TimezoneItem({
|
||||||
required this.city,
|
required this.entry,
|
||||||
required this.continent,
|
required this.selected,
|
||||||
required this.label,
|
required this.primaryColor,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final offsetLabel = formatUtcOffset(entry.currentOffsetHours);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
margin: const EdgeInsets.only(bottom: 4),
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
|
decoration: selected
|
||||||
|
? BoxDecoration(
|
||||||
|
color: primaryColor.withValues(alpha: 0.08),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.outlineVariant,
|
color: selected
|
||||||
|
? primaryColor
|
||||||
|
: Theme.of(context).colorScheme.outlineVariant,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.public,
|
Icons.public,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: selected
|
||||||
|
? Colors.white
|
||||||
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -210,12 +219,16 @@ class _TimezoneItem extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'$label · $city',
|
'$offsetLabel · ${entry.city}',
|
||||||
style: const TextStyle(fontSize: 15),
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||||
|
color: selected ? primaryColor : null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
continent,
|
entry.continent,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
@@ -224,6 +237,8 @@ class _TimezoneItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (selected)
|
||||||
|
Icon(Icons.check_circle, color: primaryColor, size: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,32 +1,53 @@
|
|||||||
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
|
class TimezoneEntry {
|
||||||
|
const TimezoneEntry({
|
||||||
|
required this.iana,
|
||||||
|
required this.city,
|
||||||
|
required this.continent,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String iana;
|
||||||
|
final String city;
|
||||||
|
final String continent;
|
||||||
|
|
||||||
|
int get currentOffsetHours {
|
||||||
|
final location = tz.getLocation(iana);
|
||||||
|
return tz.TZDateTime.now(location).timeZoneOffset.inHours;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const timezoneEntries = [
|
const timezoneEntries = [
|
||||||
(-12, 'Baker Island', 'Pacific'),
|
TimezoneEntry(iana: 'Pacific/Midway', city: 'Baker Island', continent: 'Pacific'),
|
||||||
(-11, 'Pago Pago', 'Pacific'),
|
TimezoneEntry(iana: 'Pacific/Pago_Pago', city: 'Pago Pago', continent: 'Pacific'),
|
||||||
(-10, 'Honolulu', 'Pacific'),
|
TimezoneEntry(iana: 'Pacific/Honolulu', city: 'Honolulu', continent: 'Pacific'),
|
||||||
(-9, 'Anchorage', 'America'),
|
TimezoneEntry(iana: 'America/Anchorage', city: 'Anchorage', continent: 'America'),
|
||||||
(-8, 'Los Angeles', 'America'),
|
TimezoneEntry(iana: 'America/Los_Angeles', city: 'Los Angeles', continent: 'America'),
|
||||||
(-7, 'Denver', 'America'),
|
TimezoneEntry(iana: 'America/Denver', city: 'Denver', continent: 'America'),
|
||||||
(-6, 'Mexico City', 'America'),
|
TimezoneEntry(iana: 'America/Mexico_City', city: 'Mexico City', continent: 'America'),
|
||||||
(-5, 'New York', 'America'),
|
TimezoneEntry(iana: 'America/New_York', city: 'New York', continent: 'America'),
|
||||||
(-4, 'Santiago', 'America'),
|
TimezoneEntry(iana: 'America/Santiago', city: 'Santiago', continent: 'America'),
|
||||||
(-3, 'Buenos Aires', 'America'),
|
TimezoneEntry(iana: 'America/Argentina/Buenos_Aires', city: 'Buenos Aires', continent: 'America'),
|
||||||
(-2, 'South Georgia', 'Atlantic'),
|
TimezoneEntry(iana: 'Atlantic/South_Georgia', city: 'South Georgia', continent: 'Atlantic'),
|
||||||
(-1, 'Azores', 'Atlantic'),
|
TimezoneEntry(iana: 'Atlantic/Azores', city: 'Azores', continent: 'Atlantic'),
|
||||||
(0, 'London', 'Europe'),
|
TimezoneEntry(iana: 'Europe/London', city: 'London', continent: 'Europe'),
|
||||||
(1, 'Madrid', 'Europe'),
|
TimezoneEntry(iana: 'Europe/Madrid', city: 'Madrid', continent: 'Europe'),
|
||||||
(2, 'Cairo', 'Africa'),
|
TimezoneEntry(iana: 'Africa/Cairo', city: 'Cairo', continent: 'Africa'),
|
||||||
(3, 'Moscow', 'Europe'),
|
TimezoneEntry(iana: 'Europe/Moscow', city: 'Moscow', continent: 'Europe'),
|
||||||
(4, 'Dubai', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Dubai', city: 'Dubai', continent: 'Asia'),
|
||||||
(5, 'Karachi', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Karachi', city: 'Karachi', continent: 'Asia'),
|
||||||
(6, 'Dhaka', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Dhaka', city: 'Dhaka', continent: 'Asia'),
|
||||||
(7, 'Bangkok', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Bangkok', city: 'Bangkok', continent: 'Asia'),
|
||||||
(8, 'Shanghai', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Shanghai', city: 'Shanghai', continent: 'Asia'),
|
||||||
(9, 'Tokyo', 'Asia'),
|
TimezoneEntry(iana: 'Asia/Tokyo', city: 'Tokyo', continent: 'Asia'),
|
||||||
(10, 'Sydney', 'Australia'),
|
TimezoneEntry(iana: 'Australia/Sydney', city: 'Sydney', continent: 'Australia'),
|
||||||
(11, 'Noumea', 'Pacific'),
|
TimezoneEntry(iana: 'Pacific/Noumea', city: 'Noumea', continent: 'Pacific'),
|
||||||
(12, 'Auckland', 'Pacific'),
|
TimezoneEntry(iana: 'Pacific/Auckland', city: 'Auckland', continent: 'Pacific'),
|
||||||
];
|
];
|
||||||
|
|
||||||
String formatUtcOffset(int offset) {
|
String formatUtcOffset(int offset) {
|
||||||
final sign = offset >= 0 ? '+' : '';
|
final sign = offset >= 0 ? '+' : '';
|
||||||
return 'UTC$sign$offset';
|
return 'UTC$sign$offset';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int phoneOffsetHours() => DateTime.now().timeZoneOffset.inHours;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ dependencies:
|
|||||||
json_serializable: ^6.11.2
|
json_serializable: ^6.11.2
|
||||||
url_launcher: ^6.3.2
|
url_launcher: ^6.3.2
|
||||||
flutter_contacts: ^1.1.9+2
|
flutter_contacts: ^1.1.9+2
|
||||||
|
timezone: ^0.10.1
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
@@ -766,6 +766,7 @@
|
|||||||
"timezoneUpdated": "Zeitzone aktualisiert",
|
"timezoneUpdated": "Zeitzone aktualisiert",
|
||||||
"errorTimezone": "Die Zeitzone konnte nicht aktualisiert werden",
|
"errorTimezone": "Die Zeitzone konnte nicht aktualisiert werden",
|
||||||
"timezoneOther": "Andere Zeitzonen",
|
"timezoneOther": "Andere Zeitzonen",
|
||||||
|
"timezoneUsePhone": "Zeitzone dieses Telefons verwenden",
|
||||||
"takingPhoto": "Foto wird aufgenommen...",
|
"takingPhoto": "Foto wird aufgenommen...",
|
||||||
"errorTakePicture": "Fehler beim Fotografieren",
|
"errorTakePicture": "Fehler beim Fotografieren",
|
||||||
"errorFetchPhotos": "Fehler beim Abrufen der Fotos",
|
"errorFetchPhotos": "Fehler beim Abrufen der Fotos",
|
||||||
|
|||||||
@@ -934,6 +934,7 @@
|
|||||||
"timezoneUpdated": "Timezone updated",
|
"timezoneUpdated": "Timezone updated",
|
||||||
"errorTimezone": "Could not update timezone",
|
"errorTimezone": "Could not update timezone",
|
||||||
"timezoneOther": "Other timezones",
|
"timezoneOther": "Other timezones",
|
||||||
|
"timezoneUsePhone": "Use this phone's timezone",
|
||||||
"takingPhoto": "Taking photo...",
|
"takingPhoto": "Taking photo...",
|
||||||
"errorTakePicture": "Error taking photo",
|
"errorTakePicture": "Error taking photo",
|
||||||
"errorFetchPhotos": "Error fetching photos",
|
"errorFetchPhotos": "Error fetching photos",
|
||||||
|
|||||||
@@ -935,6 +935,7 @@
|
|||||||
"timezoneUpdated": "Zona horaria actualizada",
|
"timezoneUpdated": "Zona horaria actualizada",
|
||||||
"errorTimezone": "No se pudo actualizar la zona horaria",
|
"errorTimezone": "No se pudo actualizar la zona horaria",
|
||||||
"timezoneOther": "Otras zonas horarias",
|
"timezoneOther": "Otras zonas horarias",
|
||||||
|
"timezoneUsePhone": "Usar la zona horaria del móvil",
|
||||||
"takingPhoto": "Tomando foto...",
|
"takingPhoto": "Tomando foto...",
|
||||||
"errorTakePicture": "Error al tomar la foto",
|
"errorTakePicture": "Error al tomar la foto",
|
||||||
"errorFetchPhotos": "Error al obtener las fotos",
|
"errorFetchPhotos": "Error al obtener las fotos",
|
||||||
|
|||||||
@@ -766,6 +766,7 @@
|
|||||||
"timezoneUpdated": "Fuseau horaire mis à jour",
|
"timezoneUpdated": "Fuseau horaire mis à jour",
|
||||||
"errorTimezone": "Impossible de mettre à jour le fuseau horaire",
|
"errorTimezone": "Impossible de mettre à jour le fuseau horaire",
|
||||||
"timezoneOther": "Autres fuseaux horaires",
|
"timezoneOther": "Autres fuseaux horaires",
|
||||||
|
"timezoneUsePhone": "Utiliser le fuseau horaire du téléphone",
|
||||||
"takingPhoto": "Prise de photo...",
|
"takingPhoto": "Prise de photo...",
|
||||||
"errorTakePicture": "Erreur lors de la prise de photo",
|
"errorTakePicture": "Erreur lors de la prise de photo",
|
||||||
"errorFetchPhotos": "Erreur lors de la récupération des photos",
|
"errorFetchPhotos": "Erreur lors de la récupération des photos",
|
||||||
|
|||||||
@@ -766,6 +766,7 @@
|
|||||||
"timezoneUpdated": "Fuso orario aggiornato",
|
"timezoneUpdated": "Fuso orario aggiornato",
|
||||||
"errorTimezone": "Impossibile aggiornare il fuso orario",
|
"errorTimezone": "Impossibile aggiornare il fuso orario",
|
||||||
"timezoneOther": "Altri fusi orari",
|
"timezoneOther": "Altri fusi orari",
|
||||||
|
"timezoneUsePhone": "Usa il fuso orario del telefono",
|
||||||
"takingPhoto": "Scattando foto...",
|
"takingPhoto": "Scattando foto...",
|
||||||
"errorTakePicture": "Errore durante lo scatto della foto",
|
"errorTakePicture": "Errore durante lo scatto della foto",
|
||||||
"errorFetchPhotos": "Errore durante il recupero delle foto",
|
"errorFetchPhotos": "Errore durante il recupero delle foto",
|
||||||
|
|||||||
@@ -766,6 +766,7 @@
|
|||||||
"timezoneUpdated": "Fuso horário atualizado",
|
"timezoneUpdated": "Fuso horário atualizado",
|
||||||
"errorTimezone": "Não foi possível atualizar o fuso horário",
|
"errorTimezone": "Não foi possível atualizar o fuso horário",
|
||||||
"timezoneOther": "Outros fusos horários",
|
"timezoneOther": "Outros fusos horários",
|
||||||
|
"timezoneUsePhone": "Usar o fuso horário do telemóvel",
|
||||||
"takingPhoto": "Tirando foto...",
|
"takingPhoto": "Tirando foto...",
|
||||||
"errorTakePicture": "Erro ao tirar foto",
|
"errorTakePicture": "Erro ao tirar foto",
|
||||||
"errorFetchPhotos": "Erro ao obter fotos",
|
"errorFetchPhotos": "Erro ao obter fotos",
|
||||||
|
|||||||
@@ -887,6 +887,7 @@ class I18n {
|
|||||||
static const String timezoneOther = 'timezoneOther';
|
static const String timezoneOther = 'timezoneOther';
|
||||||
static const String timezoneSearch = 'timezoneSearch';
|
static const String timezoneSearch = 'timezoneSearch';
|
||||||
static const String timezoneUpdated = 'timezoneUpdated';
|
static const String timezoneUpdated = 'timezoneUpdated';
|
||||||
|
static const String timezoneUsePhone = 'timezoneUsePhone';
|
||||||
static const String today = 'today';
|
static const String today = 'today';
|
||||||
static const String topApps = 'topApps';
|
static const String topApps = 'topApps';
|
||||||
static const String totalSteps = 'totalSteps';
|
static const String totalSteps = 'totalSteps';
|
||||||
|
|||||||
Reference in New Issue
Block a user