From 9d2871405d46144953504b4fc2317f2548af1200 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Sun, 11 Dec 2022 01:34:19 +0100 Subject: [PATCH] Add setting for timezone --- lib/main.dart | 2 + lib/models.dart | 1 + lib/models/ui_timezone.dart | 94 +++++++++++++++++++ lib/pages/settings/setings_page.dart | 1 + .../settings/settings_page_cupertino.dart | 20 ++++ lib/pages/settings/settings_page_fluent.dart | 23 +++++ .../settings/settings_page_material.dart | 23 +++++ .../view_station/view_station_cupertino.dart | 16 +++- .../view_station/view_station_fluent.dart | 35 ++++--- .../view_station/view_station_material.dart | 35 ++++--- ...in_info_cupertino_DisplayTrainStation.dart | 17 ++-- ...train_info_fluent_DisplayTrainStation.dart | 16 ++-- ...ain_info_material_DisplayTrainStation.dart | 16 ++-- lib/providers.dart | 34 +++++++ pubspec.lock | 7 ++ pubspec.yaml | 1 + 16 files changed, 293 insertions(+), 48 deletions(-) create mode 100644 lib/models/ui_timezone.dart diff --git a/lib/main.dart b/lib/main.dart index dc52227..3a48587 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,8 +17,10 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; import 'package:info_tren/providers.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:timezone/data/latest.dart'; void main() async { + initializeTimeZones(); final sharedPreferences = await SharedPreferences.getInstance(); runApp( ProviderScope( diff --git a/lib/models.dart b/lib/models.dart index 9d63a57..8e9613b 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -7,6 +7,7 @@ export 'package:info_tren/models/stations_result.dart'; export 'package:info_tren/models/train_data.dart' hide State; export 'package:info_tren/models/trains_result.dart'; export 'package:info_tren/models/ui_design.dart'; +export 'package:info_tren/models/ui_timezone.dart'; import 'package:info_tren/models/train_data.dart' show State; diff --git a/lib/models/ui_timezone.dart b/lib/models/ui_timezone.dart new file mode 100644 index 0000000..936835e --- /dev/null +++ b/lib/models/ui_timezone.dart @@ -0,0 +1,94 @@ +import 'package:timezone/timezone.dart' as tz; + +enum UiTimeZoneType { + ro, + local, + utc, + iana, +} + +extension UITimeZoneTypeName on UiTimeZoneType { + String get userInterfaceName => (const { + UiTimeZoneType.iana: 'Fus orar IANA', + UiTimeZoneType.local: 'Local', + UiTimeZoneType.ro: 'România', + UiTimeZoneType.utc: 'UTC', + })[this]!; +} + +const Map fromSerStringConstructors = { + UiTimeZoneType.ro: RoUiTimeZone.fromSerString, + UiTimeZoneType.local: LocalUiTimeZone.fromSerString, + UiTimeZoneType.utc: UtcUiTimeZone.fromSerString, + UiTimeZoneType.iana: IanaUiTimeZone.fromSerString, +}; + +abstract class UiTimeZone { + final UiTimeZoneType type; + + const UiTimeZone({required this.type}); + + DateTime convertDateTime(DateTime dt); + + factory UiTimeZone.fromSerString(String ser) { + final arr = ser.split('\n'); + return fromSerStringConstructors.map((key, value) => MapEntry(key.name, value))[arr[0]]!(ser); + } + + String toSerString() { + return '${type.name}\n'; + } +} + +class RoUiTimeZone extends UiTimeZone { + static final roTz = tz.getLocation('Europe/Bucharest'); + + const RoUiTimeZone() : super(type: UiTimeZoneType.ro); + + factory RoUiTimeZone.fromSerString(String ser) => const RoUiTimeZone(); + + @override + DateTime convertDateTime(DateTime dt) { + return tz.TZDateTime.from(dt, roTz); + } +} + +class LocalUiTimeZone extends UiTimeZone { + const LocalUiTimeZone() : super(type: UiTimeZoneType.local); + + factory LocalUiTimeZone.fromSerString(String ser) => LocalUiTimeZone(); + + @override + DateTime convertDateTime(DateTime dt) => dt.toLocal(); +} + +class UtcUiTimeZone extends UiTimeZone { + const UtcUiTimeZone() : super(type: UiTimeZoneType.utc); + + factory UtcUiTimeZone.fromSerString(String ser) => UtcUiTimeZone(); + + @override + DateTime convertDateTime(DateTime dt) => dt.toUtc(); +} + +class IanaUiTimeZone extends UiTimeZone { + late final tz.Location location; + + IanaUiTimeZone({required String ianaName}): super(type: UiTimeZoneType.iana) { + location = tz.getLocation(ianaName); + } + + factory IanaUiTimeZone.fromSerString(String ser) => IanaUiTimeZone( + ianaName: ser.split('\n').skip(1).join('\n'), + ); + + @override + DateTime convertDateTime(DateTime dt) { + return tz.TZDateTime.from(dt, location); + } + + @override + String toSerString() { + return '${type.name}\n${location.name}'; + } +} diff --git a/lib/pages/settings/setings_page.dart b/lib/pages/settings/setings_page.dart index f1c5c1d..bb4a90c 100644 --- a/lib/pages/settings/setings_page.dart +++ b/lib/pages/settings/setings_page.dart @@ -31,6 +31,7 @@ class SettingsPage extends ConsumerWidget { abstract class SettingsPageShared extends StatelessWidget { final String pageTitle = 'Setări'; final String appearanceTitle = 'Aspect'; + final String timeZoneTitle = 'Fus orar'; const SettingsPageShared({super.key}); diff --git a/lib/pages/settings/settings_page_cupertino.dart b/lib/pages/settings/settings_page_cupertino.dart index 3fadb6c..2875e4a 100644 --- a/lib/pages/settings/settings_page_cupertino.dart +++ b/lib/pages/settings/settings_page_cupertino.dart @@ -67,6 +67,26 @@ class SettingsPageCupertino extends SettingsPageShared { ); }, ), + Consumer( + builder: (context, ref, _) { + final currentTZ = ref.watch(uiTimeZoneProvider); + return CupertinoListTile( + title: Text(timeZoneTitle), + trailing: Text(currentTZ.type.userInterfaceName), + onTap: () async { + final choice = await singleChoice( + context: context, + choices: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).toList(), + labelBuilder: (UiTimeZoneType utzt) => utzt.userInterfaceName, + title: timeZoneTitle, + ); + if (choice != null) { + ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${choice.name}\n')); + } + }, + ); + }, + ), ], ), ); diff --git a/lib/pages/settings/settings_page_fluent.dart b/lib/pages/settings/settings_page_fluent.dart index 30cfe36..7da4ceb 100644 --- a/lib/pages/settings/settings_page_fluent.dart +++ b/lib/pages/settings/settings_page_fluent.dart @@ -34,6 +34,29 @@ class SettingsPageFluent extends SettingsPageShared { ); }, ), + Consumer( + builder: (context, ref, _) { + final currentTZ = ref.watch(uiTimeZoneProvider); + return ListTile( + title: Text(timeZoneTitle), + trailing: ComboBox( + items: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).map((tzt) => ComboBoxItem( + value: tzt, + child: Text(tzt.userInterfaceName), + )).toList(), + value: currentTZ.type, + onChanged: (newTZ) { + if (newTZ != null) { + ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${newTZ.name}\n')); + } + else { + ref.read(uiTimeZoneProvider.notifier).set(null); + } + }, + ), + ); + }, + ), ], ), ), diff --git a/lib/pages/settings/settings_page_material.dart b/lib/pages/settings/settings_page_material.dart index 470b65f..7f86db6 100644 --- a/lib/pages/settings/settings_page_material.dart +++ b/lib/pages/settings/settings_page_material.dart @@ -35,6 +35,29 @@ class SettingsPageMaterial extends SettingsPageShared { ); }, ), + Consumer( + builder: (context, ref, _) { + final currentTZ = ref.watch(uiTimeZoneProvider); + return ListTile( + title: Text(timeZoneTitle), + trailing: DropdownButton( + items: UiTimeZoneType.values.where((tz) => tz != UiTimeZoneType.iana).map((tzt) => DropdownMenuItem( + value: tzt, + child: Text(tzt.userInterfaceName), + )).toList(), + value: currentTZ.type, + onChanged: (newTZ) { + if (newTZ != null) { + ref.read(uiTimeZoneProvider.notifier).set(UiTimeZone.fromSerString('${newTZ.name}\n')); + } + else { + ref.read(uiTimeZoneProvider.notifier).set(null); + } + }, + ), + ); + }, + ), ], ), ), diff --git a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart b/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart index 83a30e7..f244834 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart @@ -78,7 +78,13 @@ class ViewStationPageCupertino extends ViewStationPageShared { ], ), ), - child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'), + child: Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final time = tz.convertDateTime(item.time); + return Text('${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'); + }, + ), ), ); } @@ -100,7 +106,13 @@ class ViewStationPageCupertino extends ViewStationPageShared { ], ), ), - child: Text('${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}'), + child: Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final time = tz.convertDateTime(item.time); + return Text('${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'); + }, + ), ), ); } diff --git a/lib/pages/station_arrdep_page/view_station/view_station_fluent.dart b/lib/pages/station_arrdep_page/view_station/view_station_fluent.dart index 9006547..e247f6c 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_fluent.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station_fluent.dart @@ -142,24 +142,31 @@ class ViewStationPageFluent extends ViewStationPageShared { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - '${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}', - style: TextStyle( - inherit: true, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], - decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, - fontSize: item.status.delay != 0 ? 12 : null, - ), + Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final time = tz.convertDateTime(item.time); + return Text( + '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}', + style: TextStyle( + inherit: true, + fontFeatures: const [ + FontFeature.tabularFigures(), + ], + decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, + fontSize: item.status.delay != 0 ? 12 : null, + ), + ); + }, ), - if (item.status.delay != 0) Builder( - builder: (context) { - final newTime = item.time.add(Duration(minutes: item.status.delay)); + if (item.status.delay != 0) Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay))); final delay = item.status.delay > 0; return Text( - '${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}', + '${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}', style: TextStyle( inherit: true, fontFeatures: const [ diff --git a/lib/pages/station_arrdep_page/view_station/view_station_material.dart b/lib/pages/station_arrdep_page/view_station/view_station_material.dart index c57ff28..b11932d 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_material.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station_material.dart @@ -107,24 +107,31 @@ class ViewStationPageMaterial extends ViewStationPageShared { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - '${item.time.toLocal().hour.toString().padLeft(2, '0')}:${item.time.toLocal().minute.toString().padLeft(2, '0')}', - style: TextStyle( - inherit: true, - fontFeatures: const [ - FontFeature.tabularFigures(), - ], - decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, - fontSize: item.status.delay != 0 ? 12 : null, - ), + Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final time = tz.convertDateTime(item.time); + return Text( + '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}', + style: TextStyle( + inherit: true, + fontFeatures: const [ + FontFeature.tabularFigures(), + ], + decoration: item.status.cancelled || item.status.delay != 0 ? TextDecoration.lineThrough : null, + fontSize: item.status.delay != 0 ? 12 : null, + ), + ); + }, ), - if (item.status.delay != 0) Builder( - builder: (context) { - final newTime = item.time.add(Duration(minutes: item.status.delay)); + if (item.status.delay != 0) Consumer( + builder: (context, ref, _) { + final tz = ref.watch(uiTimeZoneProvider); + final newTime = tz.convertDateTime(item.time.add(Duration(minutes: item.status.delay))); final delay = item.status.delay > 0; return Text( - '${newTime.toLocal().hour.toString().padLeft(2, '0')}:${newTime.toLocal().minute.toString().padLeft(2, '0')}', + '${newTime.hour.toString().padLeft(2, '0')}:${newTime.minute.toString().padLeft(2, '0')}', style: TextStyle( inherit: true, fontFeatures: const [ diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart index d1e105c..1113503 100644 --- a/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart +++ b/lib/pages/train_info_page/view_train/train_info_cupertino_DisplayTrainStation.dart @@ -1,6 +1,8 @@ import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/models.dart'; +import 'package:info_tren/providers.dart'; class DisplayTrainStation extends StatelessWidget { final Station station; @@ -156,7 +158,7 @@ class Time extends StatelessWidget { } } -class ArrivalTime extends StatelessWidget { +class ArrivalTime extends ConsumerWidget { final Station station; final bool finalStation; @@ -167,7 +169,9 @@ class ArrivalTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); + if (finalStation) { return Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -187,7 +191,7 @@ class ArrivalTime extends StatelessWidget { } else { final delay = station.arrival!.status?.delay ?? 0; - final time = station.arrival!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.arrival!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); @@ -291,7 +295,7 @@ class StopTime extends StatelessWidget { } } -class DepartureTime extends StatelessWidget { +class DepartureTime extends ConsumerWidget { final Station station; final bool firstStation; @@ -302,7 +306,8 @@ class DepartureTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); if (firstStation) { return Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -322,7 +327,7 @@ class DepartureTime extends StatelessWidget { } else { final delay = station.departure!.status?.delay ?? 0; - final time = station.departure!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.departure!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}"); diff --git a/lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart index b6bf523..b860b6a 100644 --- a/lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart +++ b/lib/pages/train_info_page/view_train/train_info_fluent_DisplayTrainStation.dart @@ -1,6 +1,8 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/models.dart'; +import 'package:info_tren/providers.dart'; class DisplayTrainStation extends StatelessWidget { final Station station; @@ -170,7 +172,7 @@ class Time extends StatelessWidget { } } -class ArrivalTime extends StatelessWidget { +class ArrivalTime extends ConsumerWidget { final Station station; final bool finalStation; @@ -181,7 +183,8 @@ class ArrivalTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); if (station.arrival == null) { return Container(); } @@ -204,7 +207,7 @@ class ArrivalTime extends StatelessWidget { } else { final delay = station.arrival!.status?.delay ?? 0; - final time = station.arrival!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.arrival!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); @@ -309,7 +312,7 @@ class StopTime extends StatelessWidget { } } -class DepartureTime extends StatelessWidget { +class DepartureTime extends ConsumerWidget { final Station station; final bool firstStation; @@ -320,7 +323,8 @@ class DepartureTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); if (station.departure == null) { return Container(); } @@ -343,7 +347,7 @@ class DepartureTime extends StatelessWidget { } else { final delay = station.departure!.status?.delay ?? 0; - final time = station.departure!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.departure!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); diff --git a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart index 296bbd1..e327b52 100644 --- a/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart +++ b/lib/pages/train_info_page/view_train/train_info_material_DisplayTrainStation.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/components/badge/badge.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; +import 'package:info_tren/providers.dart'; class DisplayTrainStation extends StatelessWidget { final Station station; @@ -168,7 +170,7 @@ class Time extends StatelessWidget { } } -class ArrivalTime extends StatelessWidget { +class ArrivalTime extends ConsumerWidget { final Station station; final bool finalStation; @@ -179,7 +181,8 @@ class ArrivalTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); if (station.arrival == null) { return Container(); } @@ -202,7 +205,7 @@ class ArrivalTime extends StatelessWidget { } else { final delay = station.arrival!.status?.delay ?? 0; - final time = station.arrival!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.arrival!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); @@ -305,7 +308,7 @@ class StopTime extends StatelessWidget { } } -class DepartureTime extends StatelessWidget { +class DepartureTime extends ConsumerWidget { final Station station; final bool firstStation; @@ -316,7 +319,8 @@ class DepartureTime extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final tz = ref.watch(uiTimeZoneProvider); if (station.departure == null) { return Container(); } @@ -339,7 +343,7 @@ class DepartureTime extends StatelessWidget { } else { final delay = station.departure!.status?.delay ?? 0; - final time = station.departure!.scheduleTime.toLocal(); + final time = tz.convertDateTime(station.departure!.scheduleTime); if (delay == 0) { return Text("${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"); diff --git a/lib/providers.dart b/lib/providers.dart index fd1da57..b3c0b97 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/api/station_data.dart'; @@ -36,6 +37,39 @@ final uiDesignProvider = StateNotifierProvider( (ref) => UiDesignNotifier( sharedPreferences: ref.watch(sharedPreferencesProvider), ), + dependencies: [sharedPreferencesProvider], +); + +class UiTimeZoneNotifier extends StateNotifier { + final SharedPreferences sharedPreferences; + + UiTimeZoneNotifier({required this.sharedPreferences,}) : super(const RoUiTimeZone()) { + final stored = sharedPreferences.getString('uiTimeZone'); + if (stored != null) { + try { + state = UiTimeZone.fromSerString(stored); + } + catch (e) { + log('Invalid UiTimeZone ser: $stored, error: $e', level: 1000); + } + } + } + + void set(UiTimeZone? timeZone) async { + if (timeZone != null) { + await sharedPreferences.setString('uiTimeZone', timeZone.toSerString()); + } + else { + await sharedPreferences.remove('uiTimeZone'); + } + state = timeZone ?? const LocalUiTimeZone(); + } +} +final uiTimeZoneProvider = StateNotifierProvider( + (ref) => UiTimeZoneNotifier( + sharedPreferences: ref.watch(sharedPreferencesProvider), + ), + dependencies: [sharedPreferencesProvider], ); final trainInfoArgumentsProvider = Provider( diff --git a/pubspec.lock b/pubspec.lock index 62c50c1..8579b55 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -616,6 +616,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + timezone: + dependency: "direct main" + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2ca02e8..5e7c91c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: json_annotation: ^4.7.0 shared_preferences: ^2.0.15 fluent_ui: ^4.0.3+1 + timezone: ^0.9.0 dev_dependencies: # flutter_test: