From ecfab7ce4b10fd1c9f57824968b99e309167f706 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 18 Mar 2023 01:10:18 +0100 Subject: [PATCH 01/65] chore(version): increment version to v1.0.0 --- CHANGELOG_en.md | 5 +++++ CHANGELOG_es.md | 5 +++++ CHANGELOG_fr.md | 5 +++++ pubspec.yaml | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index 808274f5..e15b9f65 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,8 @@ +# **v1.0.0** : +Primera versión estable +- *Características*: + - Se agregó soporte para cambiar el idioma. +*** # **v0.12.0** : - *Features*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 0ceb28c4..e9d840b9 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,8 @@ +# **v1.0.0** : +First stable release +- *Features*: + - Added support for changing language +*** # **v0.12.0**: - *Características*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 3f739a34..8424d39b 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,8 @@ +# **v1.0.0** : +Première version stable +- *Fonctionnalités*: + - Ajout du support de la modification de la langue +*** # **v0.12.0** : - *Fonctionnalités*: diff --git a/pubspec.yaml b/pubspec.yaml index 80bf3866..f3e89d2d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 0.12.0 +version: 1.0.0 environment: sdk: ">=2.17.6 <3.0.0" From 304a42a5f89f3f5147818c5f1348ffaa6d297217 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 18 Mar 2023 01:39:18 +0100 Subject: [PATCH 02/65] fix(changelog): switch languages --- CHANGELOG_en.md | 6 +++--- CHANGELOG_es.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index e15b9f65..fe551d83 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,7 +1,7 @@ # **v1.0.0** : -Primera versión estable -- *Características*: - - Se agregó soporte para cambiar el idioma. +First stable release +- *Features*: + - Added support for changing language *** # **v0.12.0** : diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index e9d840b9..25d34e1d 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,7 +1,7 @@ # **v1.0.0** : -First stable release -- *Features*: - - Added support for changing language +Primera versión estable +- *Características*: + - Se agregó soporte para cambiar el idioma. *** # **v0.12.0**: From 0dc8b6146acb4bf5f8f6c41a40187aff86a1ae20 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 19 Mar 2023 15:46:16 +0100 Subject: [PATCH 03/65] feat(ui): rework of the status card & status dialog --- lib/const.dart | 3 + ...{extensions.dart => boats_extensions.dart} | 10 --- lib/extensions/color_scheme_extension.dart | 15 +++++ lib/extensions/string_extension.dart | 9 +++ .../abstract_chaban_bridge_forecast.dart | 8 +-- lib/models/boat.dart | 12 +++- lib/models/chaban_bridge_boat_forecast.dart | 47 ++++++++++---- .../chaban_bridge_maintenance_forecast.dart | 54 +++++++++++----- lib/models/chaban_bridge_status.dart | 2 +- .../chaban_bridge_forecast_list_item.dart | 64 ++++++++++--------- linux/flutter/generated_plugin_registrant.cc | 15 +++++ linux/flutter/generated_plugin_registrant.h | 15 +++++ linux/flutter/generated_plugins.cmake | 24 +++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 20 ++++++ .../ephemeral/Flutter-Generated.xcconfig | 11 ++++ .../ephemeral/flutter_export_environment.sh | 12 ++++ 16 files changed, 243 insertions(+), 78 deletions(-) rename lib/extensions/{extensions.dart => boats_extensions.dart} (84%) create mode 100644 lib/extensions/color_scheme_extension.dart create mode 100644 lib/extensions/string_extension.dart create mode 100644 linux/flutter/generated_plugin_registrant.cc create mode 100644 linux/flutter/generated_plugin_registrant.h create mode 100644 linux/flutter/generated_plugins.cmake create mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 macos/Flutter/ephemeral/Flutter-Generated.xcconfig create mode 100644 macos/Flutter/ephemeral/flutter_export_environment.sh diff --git a/lib/const.dart b/lib/const.dart index f2ec0702..7a4ace77 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -10,6 +10,9 @@ class Const { static const String appLogoPath = 'assets/images/chabo_icon.png'; /// Link + static const String vesselFinderLinkPlaceholder = ':boatName:'; + static const String vesselFinderLink = + 'https://www.vesselfinder.com/fr/vessels?name=$vesselFinderLinkPlaceholder&type=301'; static const String githubLink = 'https://github.com/vareversat/chabo'; static const String privacyInfoLink = 'https://chabo.vareversat.fr/privacy'; static const List usefulLinks = [ diff --git a/lib/extensions/extensions.dart b/lib/extensions/boats_extensions.dart similarity index 84% rename from lib/extensions/extensions.dart rename to lib/extensions/boats_extensions.dart index 805cb8ff..d4026946 100644 --- a/lib/extensions/extensions.dart +++ b/lib/extensions/boats_extensions.dart @@ -2,16 +2,6 @@ import 'package:chabo/models/boat.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -extension StringExtension on String { - String capitalize() { - if (isEmpty) { - return this; - } else { - return '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; - } - } -} - extension BoatsExtension on List { TextSpan toLocalizedTextSpan(BuildContext context) { if (length == 1) { diff --git a/lib/extensions/color_scheme_extension.dart b/lib/extensions/color_scheme_extension.dart new file mode 100644 index 00000000..771dca95 --- /dev/null +++ b/lib/extensions/color_scheme_extension.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +extension ColorSchemeExtension on ColorScheme { + MaterialColor get timeColor { + return brightness == Brightness.light ? Colors.orange : Colors.amber; + } + + MaterialColor get boatColor { + return brightness == Brightness.light ? Colors.blue : Colors.cyan; + } + + MaterialColor get maintenanceColor { + return brightness == Brightness.light ? Colors.brown : Colors.grey; + } +} diff --git a/lib/extensions/string_extension.dart b/lib/extensions/string_extension.dart new file mode 100644 index 00000000..860aff14 --- /dev/null +++ b/lib/extensions/string_extension.dart @@ -0,0 +1,9 @@ +extension StringExtension on String { + String capitalize() { + if (isEmpty) { + return this; + } else { + return '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; + } + } +} diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index 444462c4..3e37515a 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -13,15 +13,13 @@ abstract class AbstractChabanBridgeForecast extends Equatable { late final DateTime _circulationClosingDate; late final DateTime _circulationReOpeningDate; final ChabanBridgeForecastClosingType closingType; - final Color color; AbstractChabanBridgeForecast( {required this.totalClosing, required this.closingReason, required DateTime circulationClosingDate, required DateTime circulationReOpeningDate, - required this.closingType, - required this.color}) { + required this.closingType}) { _circulationClosingDate = circulationClosingDate; var tmpCirculationReOpeningDate = circulationReOpeningDate; @@ -56,7 +54,7 @@ abstract class AbstractChabanBridgeForecast extends Equatable { Widget getInformationWidget(BuildContext context); - Widget getIconWidget(Color? color); + Widget getIconWidget(BuildContext context, bool reversed); String getNotificationDurationMessage( BuildContext context, DurationPickerState durationPickerState); @@ -66,6 +64,8 @@ abstract class AbstractChabanBridgeForecast extends Equatable { String getNotificationClosingMessage(BuildContext context); + Color getColor(BuildContext context, bool reversed); + String circulationClosingDateString(BuildContext context) { return DateFormat.jm(Localizations.localeOf(context).languageCode) .format(circulationClosingDate); diff --git a/lib/models/boat.dart b/lib/models/boat.dart index 288d76b7..6e4dd6d0 100644 --- a/lib/models/boat.dart +++ b/lib/models/boat.dart @@ -1,3 +1,5 @@ +import 'package:chabo/const.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -17,11 +19,15 @@ class Boat { final textSpanLink = TextSpan( recognizer: TapGestureRecognizer() ..onTap = () => _launchURL( - 'https://www.vesselfinder.com/fr/vessels?name=$name&type=301'), + Const.vesselFinderLink.replaceAll( + Const.vesselFinderLinkPlaceholder, + name, + ), + ), text: name, - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.bold, - color: Colors.blue, + color: Theme.of(context).colorScheme.boatColor, decoration: TextDecoration.underline, ), ); diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index 4bdac7aa..64e04b98 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -1,6 +1,8 @@ import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; -import 'package:chabo/extensions/extensions.dart'; +import 'package:chabo/extensions/boats_extensions.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/boat.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_reason.dart'; @@ -26,8 +28,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { circulationReOpeningDate: circulationReOpeningDate, closingReason: ChabanBridgeForecastClosingReason.boat, closingType: closingType, - totalClosing: totalClosing, - color: Colors.blue); + totalClosing: totalClosing); factory ChabanBridgeBoatForecast.fromJSON(Map json) { var apiTimezone = @@ -109,23 +110,30 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { return Text.rich( TextSpan( + style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), children: [ TextSpan(text: infoFromString), TextSpan( text: MaterialLocalizations.of(context) .formatFullDate(circulationClosingDate), - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan(text: infoToString), TextSpan( text: DateFormat.jm(Localizations.localeOf(context).languageCode) .format(circulationClosingDate), - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan(text: infoToString2), TextSpan( text: circulationReOpeningDateString, - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan( text: @@ -136,16 +144,20 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { '\n\n${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : '), TextSpan( text: '${durationString()}\n', - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.orange), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.timeColor, + ), ), TextSpan( text: '${AppLocalizations.of(context)!.dialogInformationContentTime_of_crossing.capitalize()} : '), TextSpan( text: scheduleString, - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.orange), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.timeColor, + ), ), ], ), @@ -181,14 +193,14 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { } @override - Widget getIconWidget(Color? color) { + Widget getIconWidget(BuildContext context, bool reversed) { return Stack( children: [ Padding( padding: const EdgeInsets.only(right: 15), child: Icon( Icons.directions_boat_rounded, - color: color ?? this.color, + color: getColor(context, reversed), size: 30, ), ), @@ -199,7 +211,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { quarterTurns: boats[0].isLeaving ? 0 : 2, child: Icon( Icons.double_arrow_rounded, - color: color ?? this.color, + color: getColor(context, reversed), size: 18, ), ), @@ -212,7 +224,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { quarterTurns: boats[1].isLeaving ? 0 : 2, child: Icon( Icons.double_arrow_rounded, - color: color ?? this.color, + color: getColor(context, reversed), size: 18, ), ), @@ -221,4 +233,11 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { ], ); } + + @override + Color getColor(BuildContext context, bool reversed) { + return reversed + ? Theme.of(context).dialogBackgroundColor + : Theme.of(context).colorScheme.boatColor; + } } diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index 0dcfdac7..4db24e58 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -1,6 +1,7 @@ import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; -import 'package:chabo/extensions/extensions.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_reason.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_type.dart'; @@ -19,8 +20,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { circulationReOpeningDate: circulationReOpeningDate, closingReason: ChabanBridgeForecastClosingReason.maintenance, closingType: closingType, - totalClosing: totalClosing, - color: Colors.brown); + totalClosing: totalClosing); factory ChabanBridgeMaintenanceForecast.fromJSON(Map json) { var apiTimezone = @@ -98,44 +98,59 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { ' ${AppLocalizations.of(context)!.dialogInformationContentFromEnd2} '; infoToString2 = ', '; circulationReOpeningDateString = - '${MaterialLocalizations.of(context).formatFullDate(circulationReOpeningDate)}, ${DateFormat.jm(Localizations.localeOf(context).languageCode).format(circulationReOpeningDate)}'; + '${MaterialLocalizations.of(context).formatFullDate(circulationReOpeningDate)}, ' + '${DateFormat.jm(Localizations.localeOf(context).languageCode).format(circulationReOpeningDate)}'; } return Text.rich( TextSpan( + style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), children: [ TextSpan(text: infoFromString), TextSpan( - text: MaterialLocalizations.of(context) - .formatFullDate(circulationClosingDate), - style: const TextStyle(fontWeight: FontWeight.bold), + text: MaterialLocalizations.of(context).formatFullDate( + circulationClosingDate, + ), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan(text: infoToString2), TextSpan( text: DateFormat.jm(Localizations.localeOf(context).languageCode) .format(circulationClosingDate), - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan(text: infoToString), TextSpan( text: circulationReOpeningDateString, - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), TextSpan( text: ', ${AppLocalizations.of(context)!.dialogInformationContentBridge_closed} ', ), TextSpan( - text: - '${AppLocalizations.of(context)!.dialogInformationContentBridge_closed_maintenance}\n\n', - style: TextStyle(color: color, fontWeight: FontWeight.bold)), + text: + '${AppLocalizations.of(context)!.dialogInformationContentBridge_closed_maintenance}\n\n', + style: TextStyle( + color: Theme.of(context).colorScheme.maintenanceColor, + fontWeight: FontWeight.bold, + ), + ), TextSpan( text: '${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : ', ), TextSpan( text: durationString(), - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.orange), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.timeColor, + ), ), ], ), @@ -143,11 +158,18 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { } @override - Widget getIconWidget(Color? color) { + Widget getIconWidget(BuildContext context, bool reversed) { return Icon( Icons.construction_rounded, - color: color ?? Colors.brown, + color: getColor(context, reversed), size: 30, ); } + + @override + Color getColor(BuildContext context, bool reversed) { + return reversed + ? Theme.of(context).dialogBackgroundColor + : Theme.of(context).colorScheme.maintenanceColor; + } } diff --git a/lib/models/chaban_bridge_status.dart b/lib/models/chaban_bridge_status.dart index 26dab65f..ab294870 100644 --- a/lib/models/chaban_bridge_status.dart +++ b/lib/models/chaban_bridge_status.dart @@ -1,4 +1,4 @@ -import 'package:chabo/extensions/extensions.dart'; +import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/chaban_bridge_forecast_list_item.dart index 7e03f04c..e459d3f3 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/chaban_bridge_forecast_list_item.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:chabo/custom_properties.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/widgets/information_dialog.dart'; import 'package:flutter/material.dart'; @@ -37,7 +38,7 @@ class ChabanBridgeForecastListItem extends StatelessWidget { ), side: BorderSide( // border color - color: chabanBridgeForecast.color, + color: chabanBridgeForecast.getColor(context, false), // border thickness width: 2, ), @@ -50,38 +51,41 @@ class ChabanBridgeForecastListItem extends StatelessWidget { ), ), onTap: onTap ?? - () => { - Navigator.of(context).push( - PageRouteBuilder( - transitionsBuilder: - (context, animation, secondaryAnimation, child) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - opaque: false, - barrierDismissible: true, - pageBuilder: (BuildContext context, _, __) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: InformationDialog( - chabanBridgeForecast: chabanBridgeForecast, - heroTag: 'forcast-$index', + () async => { + await showGeneralDialog( + context: context, + pageBuilder: (BuildContext context, _, __) { + return const SizedBox.shrink(); + }, + barrierDismissible: true, + transitionBuilder: (context, a1, a2, widget) { + return ScaleTransition( + scale: + Tween(begin: 0.0, end: 1.0).animate(a1), + child: FadeTransition( + opacity: Tween(begin: 0.0, end: 1.0) + .animate(a1), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: InformationDialog( + chabanBridgeForecast: chabanBridgeForecast, + ), ), - ); - }, + ), + ); + }, + barrierLabel: MaterialLocalizations.of(context) + .modalBarrierDismissLabel, + transitionDuration: const Duration( + milliseconds: 300, ), - ), + ) }, child: ListTile( horizontalTitleGap: 0, - leading: Hero( - tag: 'forcast-$index', - child: chabanBridgeForecast.getIconWidget(null), - ), + leading: chabanBridgeForecast.getIconWidget(context, false), title: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -117,8 +121,8 @@ class ChabanBridgeForecastListItem extends StatelessWidget { children: [ Text( chabanBridgeForecast.durationString(), - style: const TextStyle( - color: Colors.orange, + style: TextStyle( + color: Theme.of(context).colorScheme.timeColor, fontWeight: FontWeight.bold, fontSize: 15, ), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..f6f23bfe --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..f16b4c34 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cab7c7ab --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flutter_local_notifications +import package_info_plus +import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 00000000..f2c60a5d --- /dev/null +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,11 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=D:\src\Flutter +FLUTTER_APPLICATION_PATH=D:\Code\chabo +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=0.12.0 +FLUTTER_BUILD_NUMBER=0.12.0 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100644 index 00000000..6264566b --- /dev/null +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=D:\src\Flutter" +export "FLUTTER_APPLICATION_PATH=D:\Code\chabo" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=0.12.0" +export "FLUTTER_BUILD_NUMBER=0.12.0" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" From 34a02b5f5a5b960503477ad4a88389a982bb9561 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 19 Mar 2023 15:46:28 +0100 Subject: [PATCH 04/65] feat(ui): rework of the about dialog --- lib/const.dart | 20 +- lib/dialogs/chabo_about_dialog.dart | 269 ++++++++---------- lib/l10n/app_en.arb | 11 +- lib/l10n/app_es.arb | 13 +- lib/l10n/app_fr.arb | 11 +- lib/models/link_icon.dart | 14 + lib/widgets/information_dialog.dart | 79 +++-- .../.plugin_symlinks/package_info_plus | 1 + .../.plugin_symlinks/path_provider_linux | 1 + .../.plugin_symlinks/shared_preferences_linux | 1 + .../.plugin_symlinks/url_launcher_linux | 1 + 11 files changed, 228 insertions(+), 193 deletions(-) create mode 100644 lib/models/link_icon.dart create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/package_info_plus create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux create mode 120000 linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux diff --git a/lib/const.dart b/lib/const.dart index 7a4ace77..34f78b65 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -1,8 +1,12 @@ import 'package:chabo/models/enums/day.dart'; +import 'package:chabo/models/link_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class Const { /// App static const String appName = 'Chabo'; + static String legalLease = '© ${DateTime.now().year} - Valentin REVERSAT'; /// Paths static const String changelogPlaceholder = ':lang:'; @@ -15,10 +19,18 @@ class Const { 'https://www.vesselfinder.com/fr/vessels?name=$vesselFinderLinkPlaceholder&type=301'; static const String githubLink = 'https://github.com/vareversat/chabo'; static const String privacyInfoLink = 'https://chabo.vareversat.fr/privacy'; - static const List usefulLinks = [ - 'https://www.instagram.com/_yuhliet_/', - 'https://sedeplacer.bordeaux-metropole.fr/', - 'https://opendata.bordeaux-metropole.fr/' + + static List usefulLinks = [ + WebLinkIcon('https://www.instagram.com/_yuhliet_/', + FontAwesomeIcons.instagram, 'yuhliet_instagram'), + WebLinkIcon('https://bordeaux-metropole.fr/', Icons.location_city_rounded, + 'city_of_bordeaux'), + WebLinkIcon('https://opendata.bordeaux-metropole.fr/', + Icons.data_thresholding_rounded, 'bordeaux_open_data'), + WebLinkIcon('https://github.com/vareversat/chabo', FontAwesomeIcons.github, + 'source_code'), + WebLinkIcon('https://chabo.vareversat.fr/privacy', + Icons.privacy_tip_rounded, 'privacy_policy'), ]; /// Local storage diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart index 22f1efe7..a2d1f0f6 100644 --- a/lib/dialogs/chabo_about_dialog.dart +++ b/lib/dialogs/chabo_about_dialog.dart @@ -1,15 +1,12 @@ import 'package:chabo/const.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/screens/changelog_screen.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher_string.dart'; class ChaboAboutDialog extends StatelessWidget { - final String _legalLease = '© ${DateTime.now().year} - Valentin REVERSAT'; final Widget _iconWidget = Padding( padding: const EdgeInsets.all(5), child: SizedBox( @@ -21,10 +18,6 @@ class ChaboAboutDialog extends StatelessWidget { ChaboAboutDialog({Key? key}) : super(key: key); - void _launchURL(String url) async { - await launchUrlString(url, mode: LaunchMode.externalApplication); - } - @override Widget build(BuildContext context) { return FutureBuilder( @@ -60,7 +53,7 @@ class ChaboAboutDialog extends StatelessWidget { ), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -81,12 +74,10 @@ class ChaboAboutDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium), ], ), - const SizedBox(height: 10), Text( - _legalLease, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 15, - ), + Const.legalLease, + style: + Theme.of(context).textTheme.bodySmall!.copyWith(), ), ], ), @@ -95,156 +86,142 @@ class ChaboAboutDialog extends StatelessWidget { ], ), shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(CustomProperties.borderRadius)), + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, + ), + ), content: ListBody( children: [ Text( - '${AppLocalizations.of(context)!.appDescription}\n', - style: const TextStyle(fontSize: 18), + AppLocalizations.of(context)!.appDescription, + style: Theme.of(context).textTheme.bodyLarge, ), Text( AppLocalizations.of(context)!.disclaimer, - style: - const TextStyle(fontSize: 15, fontStyle: FontStyle.italic), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontStyle: FontStyle.italic), ), - const Padding( - padding: EdgeInsets.all(8.0), - child: Divider( - thickness: 2, - ), + const SizedBox( + height: 15, ), - Text( - '${AppLocalizations.of(context)!.usefulLinks}\n', - style: - const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: Const.usefulLinks + .map( + (link) => ElevatedButton( + onPressed: () async => link.launchURL(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + link.iconData, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.selectExample( + link.translationKey, + ), + ), + ), + const SizedBox( + width: 10, + ), + ], + ), + ), + ) + .toList(), ), - RichText( - text: TextSpan( - children: Const.usefulLinks - .map( - (link) => TextSpan( - text: '$link\n', - style: - Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - _launchURL(link); - }), - ) - .toList(), - ), + const SizedBox( + height: 10, ), - ElevatedButton.icon( - key: const ValueKey('sourceCodeButton'), - onPressed: () => _launchURL(Const.githubLink), - style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), + Wrap( + spacing: 10, + runSpacing: 5, + alignment: WrapAlignment.center, + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.secondaryContainer), + foregroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.onSecondaryContainer), ), - ), - ), - label: Text(AppLocalizations.of(context)!.sourceCode, - style: const TextStyle(fontSize: 18)), - icon: const Icon( - FontAwesomeIcons.github, - size: 20, - ), - ), - ElevatedButton.icon( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.secondary), - foregroundColor: MaterialStateProperty.all( - Theme.of(context).cardColor), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ChangeLogScreen(), ), ), - ), - ), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ChangeLogScreen(), - ), - ), - label: Text(AppLocalizations.of(context)!.changelog, - style: const TextStyle(fontSize: 18)), - icon: const Icon( - FontAwesomeIcons.fileCode, - size: 20, - ), - ), - ElevatedButton.icon( - style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + FontAwesomeIcons.codeMerge, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.selectExample( + 'changelog', + ), + ), + ), + ], ), ), - ), - onPressed: () { - showLicensePage( - context: context, - applicationName: snapshot.data!.appName, - applicationVersion: - 'v${snapshot.data!.version}+${snapshot.data!.buildNumber}', - applicationIcon: _iconWidget, - applicationLegalese: _legalLease, - ); - }, - label: Text( - MaterialLocalizations.of(context).viewLicensesButtonLabel[0] + - MaterialLocalizations.of(context) - .viewLicensesButtonLabel - .substring(1) - .toLowerCase(), - style: const TextStyle( - fontSize: 18, - ), - ), - icon: const Icon( - FontAwesomeIcons.fileLines, - size: 20, - ), - ), - ElevatedButton.icon( - key: const ValueKey('privacyButton'), - onPressed: () => _launchURL(Const.privacyInfoLink), - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(Colors.blueAccent), - foregroundColor: MaterialStateProperty.all( - Theme.of(context).cardColor), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.secondaryContainer), + foregroundColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.onSecondaryContainer), + ), + onPressed: () { + showLicensePage( + context: context, + applicationName: snapshot.data!.appName, + applicationVersion: + 'v${snapshot.data!.version}+${snapshot.data!.buildNumber}', + applicationIcon: _iconWidget, + applicationLegalese: Const.legalLease, + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + FontAwesomeIcons.fileLines, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)! + .selectExample('licenses'), + ), + ), + ], ), ), - ), - label: Text( - AppLocalizations.of(context)!.privacyPolicy, - style: const TextStyle( - fontSize: 18, - ), - ), - icon: const Icon( - FontAwesomeIcons.userShield, - size: 20, - ), + ], ), ], ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 22df956d..8e859081 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -31,13 +31,10 @@ "lisOfUpcomingClosures": "List of upcoming closures", "unableAppInfo": "Unable to get application information", "appDescription": "The Mobile app to get the closing and opening schedules of the Chaban Delmas bridge located in Bordeaux, France", - "sourceCode": "Source code", "changelog": "Changelog", - "privacyPolicy": "Privacy policy", "informationAboutTheApp": "Information about the app", "about": "About", "disclaimer": "Disclaimer: provisional closures. Subject to confirmation from the Harbor Master's Office.", - "usefulLinks": "Useful links:", "themeSetting": "Theme", "themeSettingSubtitle": "Set the theme of the app", "lightTheme": "Light theme", @@ -144,5 +141,11 @@ } }, "notificationTimeChannelName": "Next day closings", - "passedClosure": "Past closure" + "passedClosure": "Past closure", + "selectExample": "{choice, select, source_code {Source\ncode} privacy_policy {Privacy\npolicy} yuhliet_instagram {Yuhliet's\nInstagram} city_of_bordeaux {City of\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licenses} changelog {Changelog} other {Undefined}}", + "@selectExample": { + "placeholders": { + "choice": {} + } + } } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 784c7279..070679ed 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -30,14 +30,11 @@ "errorScreenContentTechnical_Info": "Información técnica", "lisOfUpcomingClosures": "Lista de próximos cierres", "unableAppInfo": "No se puede obtener información de la aplicación", - "appDescription": "La aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban Delmas ubicado en Burdeos, Francia", - "sourceCode": "Código fuente", + "appDescription": "La aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban Delmas ubicado en Bordeaux, Francia", "changelog": "Registro de cambios", - "privacyPolicy": "Política de privacidad", "informationAboutTheApp": "Información sobre la aplicación", "about": "Acerca de", "disclaimer": "Descargo de responsabilidad: cierres provisionales. Sujeto a confirmación de la Capitanía de Puerto.", - "usefulLinks": "Enlaces útiles:", "themeSetting": "Tema", "themeSettingSubtitle": "Establezca el tema de la aplicación", "lightTheme": "Tema claro", @@ -144,5 +141,11 @@ } }, "notificationTimeChannelName": "Cierres del próximo día", - "passedClosure": "Cierre pasado" + "passedClosure": "Cierre pasado", + "selectExample": "{choice, select, source_code {Código\nuente} privacy_policy {Política de\nprivacidad} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ciudad de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licencias} changelog {Registro de cambios} other {Undefined}}", + "@selectExample": { + "placeholders": { + "choice": {} + } + } } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index b5f33a64..05d705a4 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -31,13 +31,10 @@ "lisOfUpcomingClosures": "Liste des prochaines perturbations", "unableAppInfo": "Impossible d'obtenir les informations de l'application", "appDescription": "L'application Mobile pour connaître les horaires de fermeture et d'ouverture du pont Chaban Delmas situé à Bordeaux, France", - "sourceCode": "Code source", "changelog": "Journal des modifications", - "privacyPolicy": "Politique de confidentialité", "informationAboutTheApp": "Informations concernant l'application", "about": "À propos", "disclaimer": "Avis de non-responsabilité : fermetures provisoires. Sous réserve de confirmation de la Capitainerie.", - "usefulLinks": "Liens utiles :", "themeSetting": "Thème", "themeSettingSubtitle": "Choisir le thème de l'application", "lightTheme": "Thème clair", @@ -144,5 +141,11 @@ } }, "notificationTimeChannelName": "Fermetures du lendemain", - "passedClosure": "Fermeture passée" + "passedClosure": "Fermeture passée", + "selectExample": "{choice, select, source_code {Code\nsource} privacy_policy {Politique de\nconfidentialité} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ville de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} changelog {Journal des\nmodifications} licenses {Licences} other {Undefined}}", + "@selectExample": { + "placeholders": { + "choice": {} + } + } } \ No newline at end of file diff --git a/lib/models/link_icon.dart b/lib/models/link_icon.dart new file mode 100644 index 00000000..4487a4cb --- /dev/null +++ b/lib/models/link_icon.dart @@ -0,0 +1,14 @@ +import 'package:flutter/cupertino.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class WebLinkIcon { + final IconData iconData; + final String translationKey; + final String link; + + WebLinkIcon(this.link, this.iconData, this.translationKey); + + void launchURL() async { + await launchUrlString(link, mode: LaunchMode.externalApplication); + } +} diff --git a/lib/widgets/information_dialog.dart b/lib/widgets/information_dialog.dart index 05989521..1c127d1b 100644 --- a/lib/widgets/information_dialog.dart +++ b/lib/widgets/information_dialog.dart @@ -3,43 +3,62 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class InformationDialog extends StatelessWidget { - final String heroTag; final AbstractChabanBridgeForecast chabanBridgeForecast; - const InformationDialog( - {super.key, required this.chabanBridgeForecast, required this.heroTag}); + const InformationDialog({super.key, required this.chabanBridgeForecast}); @override Widget build(BuildContext context) { return AlertDialog( - insetPadding: const EdgeInsets.symmetric(horizontal: 20), - titlePadding: const EdgeInsets.all(0), - contentPadding: const EdgeInsets.all(20), - actionsPadding: const EdgeInsets.fromLTRB(0, 0, 20, 10), - title: Container( - decoration: BoxDecoration( - color: chabanBridgeForecast.color, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15.0), - topRight: Radius.circular(15.0))), - padding: const EdgeInsets.fromLTRB(20, 20, 0, 15), - child: Row( - children: [ - Hero( - tag: heroTag, - child: chabanBridgeForecast - .getIconWidget(Theme.of(context).cardColor), - ), - const SizedBox(width: 20), - Text( - AppLocalizations.of(context)!.information, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: Theme.of(context).cardColor), - ), - ], + insetPadding: const EdgeInsets.symmetric( + horizontal: 20, + ), + titlePadding: const EdgeInsets.all(0), + contentPadding: const EdgeInsets.all(20), + actionsPadding: const EdgeInsets.fromLTRB( + 0, + 0, + 20, + 10, + ), + title: Container( + decoration: BoxDecoration( + color: chabanBridgeForecast.getColor(context, false), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular( + 15.0, + ), + topRight: Radius.circular( + 15.0, + ), ), ), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), - content: chabanBridgeForecast.getInformationWidget(context)); + padding: const EdgeInsets.fromLTRB( + 20, + 20, + 0, + 15, + ), + child: Row( + children: [ + chabanBridgeForecast.getIconWidget(context, true), + const SizedBox(width: 20), + Text( + AppLocalizations.of(context)!.information, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context).cardColor, + ), + ), + ], + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 15, + ), + ), + content: chabanBridgeForecast.getInformationWidget(context), + ); } } diff --git a/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus b/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus new file mode 120000 index 00000000..2fb5e920 --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus @@ -0,0 +1 @@ +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/package_info_plus-3.0.3/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux new file mode 120000 index 00000000..61a5a8ff --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux @@ -0,0 +1 @@ +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.1.10/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux b/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux new file mode 120000 index 00000000..3d69ff4a --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux @@ -0,0 +1 @@ +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/shared_preferences_linux-2.1.5/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux b/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux new file mode 120000 index 00000000..a273bc61 --- /dev/null +++ b/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux @@ -0,0 +1 @@ +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.0.4/ \ No newline at end of file From 9002106994f85668f64fc3cbc3a728d6b7b277e3 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 19 Mar 2023 16:00:49 +0100 Subject: [PATCH 05/65] chore(flutter): upgrade flutter to `3.7.7` --- .github/workflows/default.yaml | 4 ++-- .github/workflows/dev.yaml | 4 ++-- .github/workflows/main.yaml | 4 ++-- .github/workflows/tag.yaml | 4 ++-- macos/Flutter/ephemeral/Flutter-Generated.xcconfig | 4 ++-- .../ephemeral/flutter_export_environment.sh | 4 ++-- pubspec.lock | 14 +++++++------- pubspec.yaml | 3 ++- 8 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/default.yaml b/.github/workflows/default.yaml index 5ea67d51..7c789d85 100644 --- a/.github/workflows/default.yaml +++ b/.github/workflows/default.yaml @@ -10,14 +10,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' android_output: 'apk' secrets: passphrase: ${{ secrets.PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index 50fb24ca..ed5216cc 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -9,7 +9,7 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' secrets: passphrase: ${{ secrets.PASSPHRASE }} page: @@ -18,7 +18,7 @@ jobs: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 15c433e7..5db0da7a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -9,14 +9,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index fead7ba8..1491e7d4 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -9,14 +9,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.1' + flutter_version: '3.7.7' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index f2c60a5d..faf5a4f0 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -3,8 +3,8 @@ FLUTTER_ROOT=D:\src\Flutter FLUTTER_APPLICATION_PATH=D:\Code\chabo COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=0.12.0 -FLUTTER_BUILD_NUMBER=0.12.0 +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1.0.0 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index 6264566b..23d30c66 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -4,8 +4,8 @@ export "FLUTTER_ROOT=D:\src\Flutter" export "FLUTTER_APPLICATION_PATH=D:\Code\chabo" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=0.12.0" -export "FLUTTER_BUILD_NUMBER=0.12.0" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1.0.0" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/pubspec.lock b/pubspec.lock index ce01c6da..9b643d68 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -401,10 +401,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "026b97a6c29da75181a37aae2eba9227f5fe13cb2838c6b975ce209328b8ab4e" + sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" path_provider_linux: dependency: transitive description: @@ -457,10 +457,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: "57b6b78df14175658f09c5dfcfc51a46ad9561a3504fe679913dab404d0cc0f2" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.0" process: dependency: transitive description: @@ -630,10 +630,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "7ab1e5b646623d6a2537aa59d5d039f90eebef75a7c25e105f6f75de1f7750c3" + sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.3" url_launcher_linux: dependency: transitive description: @@ -724,4 +724,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.0 <3.0.0" - flutter: ">=3.3.0" + flutter: ">=3.7.7" diff --git a/pubspec.yaml b/pubspec.yaml index f3e89d2d..33a12b35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,8 @@ publish_to: 'none' version: 1.0.0 environment: - sdk: ">=2.17.6 <3.0.0" + sdk: '>=2.17.6 <3.0.0' + flutter: '3.7.7' dependencies: bloc_concurrency: ^0.2.0 From 6a0812b34355a8766ba8a874a7847bb776594ed9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:04:59 +0000 Subject: [PATCH 06/65] chore(deps): update ruby/setup-ruby to v1.144.1 --- .github/workflows/fastlane.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 40faf6f2..272d9afe 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -34,7 +34,7 @@ jobs: - name: 'Generate changelog' run: ./.github/scripts/generate_changelog.sh - name: 'Setup Ruby' - uses: ruby/setup-ruby@v1.144.0 + uses: ruby/setup-ruby@v1.144.1 with: ruby-version: '3.0' bundler-cache: true From 935fe60994bdfe619598ca5e8003d2bf1b195ec6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 18:28:46 +0000 Subject: [PATCH 07/65] chore(deps): update actions/checkout to v3.4.0 --- .github/workflows/fastlane.action.yaml | 2 +- .github/workflows/flutter.build.action.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 272d9afe..f4023f8f 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.4.0 with: fetch-depth: 0 - name: 'Decrypt secret configuration' diff --git a/.github/workflows/flutter.build.action.yaml b/.github/workflows/flutter.build.action.yaml index 2272e09e..ef9ed0a0 100644 --- a/.github/workflows/flutter.build.action.yaml +++ b/.github/workflows/flutter.build.action.yaml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.4.0 with: fetch-depth: 0 - name: 'Decrypt secret configuration' From 6ef795acc4b738261f5adf046c89f6d7ef0b04b3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Mar 2023 21:53:23 +0000 Subject: [PATCH 08/65] chore(deps): update actions/deploy-pages to v2 --- .github/workflows/pages.deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.deploy.yaml b/.github/workflows/pages.deploy.yaml index 3c09a762..18f1d4db 100644 --- a/.github/workflows/pages.deploy.yaml +++ b/.github/workflows/pages.deploy.yaml @@ -25,4 +25,4 @@ jobs: path: 'page' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 \ No newline at end of file + uses: actions/deploy-pages@v2 \ No newline at end of file From 31c50c92e3c2c8c1fb6b1591f92c001433dbe151 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Wed, 22 Mar 2023 21:08:27 +0100 Subject: [PATCH 09/65] chore(google-ads): add `app-ads.txt` on chabo.vareversat.fr --- page/app-ads.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 page/app-ads.txt diff --git a/page/app-ads.txt b/page/app-ads.txt new file mode 100644 index 00000000..5610e143 --- /dev/null +++ b/page/app-ads.txt @@ -0,0 +1 @@ +google.com, pub-4365376442391282, DIRECT, f08c47fec0942fa0 \ No newline at end of file From bf1862a34ce430eeeecf65b82d1cc34a7de98a62 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 23 Mar 2023 22:53:19 +0100 Subject: [PATCH 10/65] feat(notifications): rework of the notification pop up --- lib/bloc/app_state_event.dart | 5 + .../chaban_bridge_forecast_bloc.dart | 1 - .../closing_notification_bloc.dart | 42 --- .../closing_notification_event.dart | 13 - .../closing_notification_state.dart | 11 - lib/bloc/day_picker/day_picker_bloc.dart | 69 ----- lib/bloc/day_picker/day_picker_event.dart | 25 -- lib/bloc/day_picker/day_picker_state.dart | 17 -- .../duration_picker/duration_picker_bloc.dart | 65 ----- .../duration_picker_event.dart | 19 -- .../duration_picker_state.dart | 26 -- lib/bloc/notification/notification_bloc.dart | 178 ++++++++++++ lib/bloc/notification/notification_event.dart | 55 ++++ lib/bloc/notification/notification_state.dart | 62 +++++ .../opening_notification_bloc.dart | 1 - .../scroll_status/scroll_status_bloc.dart | 1 - lib/bloc/time_picker/time_picker_bloc.dart | 58 ---- lib/bloc/time_picker/time_picker_event.dart | 19 -- lib/bloc/time_picker/time_picker_state.dart | 26 -- lib/chabo.dart | 69 +---- ...n_bridge_forecast_information_dialog.dart} | 5 +- lib/dialogs/days_of_the_week_dialog.dart | 93 +++++++ lib/dialogs/notification_dialog.dart | 103 ------- lib/l10n/app_en.arb | 6 +- lib/l10n/app_es.arb | 6 +- lib/l10n/app_fr.arb | 6 +- .../abstract_chaban_bridge_forecast.dart | 7 +- lib/models/chaban_bridge_boat_forecast.dart | 9 +- .../chaban_bridge_maintenance_forecast.dart | 9 +- .../chaban_bridge_forecast_screen.dart | 117 ++++---- lib/screens/notification_screen.dart | 262 ++++++++++++++++++ lib/screens/settings_screen.dart | 19 +- lib/service/notification_service.dart | 189 +++++++------ .../chaban_bridge_forecast_list_item.dart | 22 +- .../closing_notification_settings_widget.dart | 27 -- .../day_notification_settings_widget.dart | 155 ----------- ...duration_notification_settings_widget.dart | 58 ---- .../notification_settings_widget.dart | 79 ------ .../opening_notification_settings_widget.dart | 27 -- .../time_notification_settings_widget.dart | 58 ---- lib/widgets/notification_tile_widget.dart | 21 +- 41 files changed, 883 insertions(+), 1157 deletions(-) create mode 100644 lib/bloc/app_state_event.dart delete mode 100644 lib/bloc/closing_notification/closing_notification_bloc.dart delete mode 100644 lib/bloc/closing_notification/closing_notification_event.dart delete mode 100644 lib/bloc/closing_notification/closing_notification_state.dart delete mode 100644 lib/bloc/day_picker/day_picker_bloc.dart delete mode 100644 lib/bloc/day_picker/day_picker_event.dart delete mode 100644 lib/bloc/day_picker/day_picker_state.dart delete mode 100644 lib/bloc/duration_picker/duration_picker_bloc.dart delete mode 100644 lib/bloc/duration_picker/duration_picker_event.dart delete mode 100644 lib/bloc/duration_picker/duration_picker_state.dart create mode 100644 lib/bloc/notification/notification_bloc.dart create mode 100644 lib/bloc/notification/notification_event.dart create mode 100644 lib/bloc/notification/notification_state.dart delete mode 100644 lib/bloc/time_picker/time_picker_bloc.dart delete mode 100644 lib/bloc/time_picker/time_picker_event.dart delete mode 100644 lib/bloc/time_picker/time_picker_state.dart rename lib/{widgets/information_dialog.dart => dialogs/chaban_bridge_forecast_information_dialog.dart} (90%) create mode 100644 lib/dialogs/days_of_the_week_dialog.dart delete mode 100644 lib/dialogs/notification_dialog.dart create mode 100644 lib/screens/notification_screen.dart delete mode 100644 lib/widgets/notification/closing_notification_settings_widget.dart delete mode 100644 lib/widgets/notification/day_notification_settings_widget.dart delete mode 100644 lib/widgets/notification/duration_notification_settings_widget.dart delete mode 100644 lib/widgets/notification/notification_settings_widget.dart delete mode 100644 lib/widgets/notification/opening_notification_settings_widget.dart delete mode 100644 lib/widgets/notification/time_notification_settings_widget.dart diff --git a/lib/bloc/app_state_event.dart b/lib/bloc/app_state_event.dart new file mode 100644 index 00000000..3529a3b1 --- /dev/null +++ b/lib/bloc/app_state_event.dart @@ -0,0 +1,5 @@ +import 'package:chabo/bloc/chabo_event.dart'; + +class AppStateEvent extends ChaboEvent { + AppStateEvent() : super(); +} diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index b4ee752e..238dd2a3 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -9,7 +9,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:http/http.dart' as http; part 'chaban_bridge_forecast_event.dart'; - part 'chaban_bridge_forecast_state.dart'; const _chabanBridgeForecastLimit = 10000; diff --git a/lib/bloc/closing_notification/closing_notification_bloc.dart b/lib/bloc/closing_notification/closing_notification_bloc.dart deleted file mode 100644 index d8a3de4f..00000000 --- a/lib/bloc/closing_notification/closing_notification_bloc.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'closing_notification_event.dart'; - -part 'closing_notification_state.dart'; - -class ClosingNotificationBloc - extends Bloc { - final StorageService storageService; - - ClosingNotificationBloc({required this.storageService}) - : super(ClosingNotificationState(enabled: false)) { - on( - _onAppStateChanged, - ); - on( - _onStateChanged, - ); - } - - Future _onStateChanged(event, emit) async { - await storageService.saveBool( - Const.notificationClosingEnabledKey, event.enabled); - HapticFeedback.lightImpact(); - emit( - state.copyWith(enabled: event.enabled), - ); - } - - Future _onAppStateChanged(event, emit) async { - final enabledValue = - storageService.readBool(Const.notificationClosingEnabledKey) ?? - Const.notificationClosingEnabledDefaultValue; - emit( - state.copyWith(enabled: enabledValue), - ); - } -} diff --git a/lib/bloc/closing_notification/closing_notification_event.dart b/lib/bloc/closing_notification/closing_notification_event.dart deleted file mode 100644 index c8af038d..00000000 --- a/lib/bloc/closing_notification/closing_notification_event.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of 'closing_notification_bloc.dart'; - -class ClosingNotificationEvent extends ChaboEvent {} - -class ClosingNotificationChanged extends ClosingNotificationEvent { - final bool enabled; - - ClosingNotificationChanged({required this.enabled}) : super(); -} - -class ClosingAppStateChanged extends ClosingNotificationEvent { - ClosingAppStateChanged() : super(); -} diff --git a/lib/bloc/closing_notification/closing_notification_state.dart b/lib/bloc/closing_notification/closing_notification_state.dart deleted file mode 100644 index f5cc6c3b..00000000 --- a/lib/bloc/closing_notification/closing_notification_state.dart +++ /dev/null @@ -1,11 +0,0 @@ -part of 'closing_notification_bloc.dart'; - -class ClosingNotificationState { - final bool enabled; - - ClosingNotificationState({required this.enabled}); - - ClosingNotificationState copyWith({bool? enabled}) { - return ClosingNotificationState(enabled: enabled ?? this.enabled); - } -} diff --git a/lib/bloc/day_picker/day_picker_bloc.dart b/lib/bloc/day_picker/day_picker_bloc.dart deleted file mode 100644 index bd8e945d..00000000 --- a/lib/bloc/day_picker/day_picker_bloc.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/models/enums/day.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'day_picker_event.dart'; - -part 'day_picker_state.dart'; - -class DayPickerBloc extends Bloc { - final StorageService storageService; - - DayPickerBloc({required this.storageService}) - : super( - DayPickerState(day: Day.monday, enabled: false, icon: Icons.edit)) { - on( - _onDayChanged, - ); - on( - _onStateChanged, - ); - on( - _onSettingsChanged, - ); - on( - _onAppStateChanged, - ); - } - - Future _onDayChanged( - DayPickerChanged event, Emitter emit) async { - await storageService.saveDay(Const.notificationDayValueKey, event.day); - emit( - state.copyWith(day: event.day), - ); - } - - Future _onStateChanged( - DayPickerStateChanged event, Emitter emit) async { - await storageService.saveBool( - Const.notificationDayEnabledKey, event.enabled); - HapticFeedback.lightImpact(); - emit( - state.copyWith(enabled: event.enabled), - ); - } - - Future _onSettingsChanged( - DayPickerSettingChanged event, Emitter emit) async { - emit( - state.copyWith(icon: event.isOpen ? Icons.close : Icons.edit), - ); - } - - Future _onAppStateChanged( - DayAppStateChanged event, Emitter emit) async { - final dayValue = storageService.readDay(Const.notificationDayValueKey) ?? - Const.notificationDayValueDefaultValue; - final enabledValue = - storageService.readBool(Const.notificationDayEnabledKey) ?? - Const.notificationDayEnabledDefaultValue; - emit( - state.copyWith(enabled: enabledValue, day: dayValue), - ); - } -} diff --git a/lib/bloc/day_picker/day_picker_event.dart b/lib/bloc/day_picker/day_picker_event.dart deleted file mode 100644 index a7e35f80..00000000 --- a/lib/bloc/day_picker/day_picker_event.dart +++ /dev/null @@ -1,25 +0,0 @@ -part of 'day_picker_bloc.dart'; - -class DayPickerEvent extends ChaboEvent {} - -class DayPickerSettingChanged extends DayPickerEvent { - final bool isOpen; - - DayPickerSettingChanged({required this.isOpen}) : super(); -} - -class DayPickerChanged extends DayPickerEvent { - final Day day; - - DayPickerChanged({required this.day}) : super(); -} - -class DayPickerStateChanged extends DayPickerEvent { - final bool enabled; - - DayPickerStateChanged({required this.enabled}) : super(); -} - -class DayAppStateChanged extends DayPickerEvent { - DayAppStateChanged() : super(); -} diff --git a/lib/bloc/day_picker/day_picker_state.dart b/lib/bloc/day_picker/day_picker_state.dart deleted file mode 100644 index 69efd46f..00000000 --- a/lib/bloc/day_picker/day_picker_state.dart +++ /dev/null @@ -1,17 +0,0 @@ -part of 'day_picker_bloc.dart'; - -class DayPickerState { - final IconData icon; - final Day day; - final bool enabled; - - DayPickerState( - {required this.enabled, required this.day, required this.icon}); - - DayPickerState copyWith({Day? day, bool? enabled, IconData? icon}) { - return DayPickerState( - day: day ?? this.day, - enabled: enabled ?? this.enabled, - icon: icon ?? this.icon); - } -} diff --git a/lib/bloc/duration_picker/duration_picker_bloc.dart b/lib/bloc/duration_picker/duration_picker_bloc.dart deleted file mode 100644 index 48cf45e2..00000000 --- a/lib/bloc/duration_picker/duration_picker_bloc.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'duration_picker_event.dart'; - -part 'duration_picker_state.dart'; - -class DurationPickerBloc - extends Bloc { - final StorageService storageService; - - DurationPickerBloc({required this.storageService}) - : super( - DurationPickerState( - duration: const Duration(hours: 1), - enabled: false, - ), - ) { - on( - _onDurationChanged, - ); - on( - _onStateChanged, - ); - on( - _onAppStateChanged, - ); - } - - Future _onDurationChanged( - DurationPickerChanged event, Emitter emit) async { - await storageService.saveDuration( - Const.notificationDurationValueKey, event.duration); - emit( - state.copyWith(duration: event.duration), - ); - } - - Future _onStateChanged(DurationPickerStateChanged event, - Emitter emit) async { - await storageService.saveBool( - Const.notificationDurationEnabledKey, event.enabled); - HapticFeedback.lightImpact(); - emit( - state.copyWith(enabled: event.enabled), - ); - } - - Future _onAppStateChanged( - DurationAppStateChanged event, Emitter emit) async { - final durationValue = - storageService.readDuration(Const.notificationDurationValueKey) ?? - Const.notificationDurationValueDefaultValue; - final enabledValue = - storageService.readBool(Const.notificationDurationEnabledKey) ?? - Const.notificationDurationEnabledDefaultValue; - emit( - state.copyWith(enabled: enabledValue, duration: durationValue), - ); - } -} diff --git a/lib/bloc/duration_picker/duration_picker_event.dart b/lib/bloc/duration_picker/duration_picker_event.dart deleted file mode 100644 index 45e4c937..00000000 --- a/lib/bloc/duration_picker/duration_picker_event.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of 'duration_picker_bloc.dart'; - -class DurationPickerEvent extends ChaboEvent {} - -class DurationPickerChanged extends DurationPickerEvent { - final Duration duration; - - DurationPickerChanged({required this.duration}) : super(); -} - -class DurationPickerStateChanged extends DurationPickerEvent { - final bool enabled; - - DurationPickerStateChanged({required this.enabled}) : super(); -} - -class DurationAppStateChanged extends DurationPickerEvent { - DurationAppStateChanged() : super(); -} diff --git a/lib/bloc/duration_picker/duration_picker_state.dart b/lib/bloc/duration_picker/duration_picker_state.dart deleted file mode 100644 index 87acc305..00000000 --- a/lib/bloc/duration_picker/duration_picker_state.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of 'duration_picker_bloc.dart'; - -class DurationPickerState { - final bool enabled; - final Duration duration; - - DurationPickerState({required this.enabled, required this.duration}); - - DurationPickerState copyWith({Duration? duration, bool? enabled}) { - return DurationPickerState( - duration: duration ?? this.duration, enabled: enabled ?? this.enabled); - } - - TimeOfDay toTimeOfDay() { - return TimeOfDay(hour: duration.inHours, minute: duration.inMinutes % 60); - } - - String getDuration() { - if (duration.inMinutes % 60 == 0) { - return '${duration.inHours.toString()}h'; - } else if (duration.inHours == 0) { - return '${duration.inMinutes.toString()}mins'; - } - return '${duration.inHours.toString()}h ${(duration.inMinutes % 60).toString()}mins'; - } -} diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart new file mode 100644 index 00000000..5681f178 --- /dev/null +++ b/lib/bloc/notification/notification_bloc.dart @@ -0,0 +1,178 @@ +import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/const.dart'; +import 'package:chabo/models/enums/day.dart'; +import 'package:chabo/service/storage_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'notification_event.dart'; +part 'notification_state.dart'; + +class NotificationBloc extends Bloc { + final StorageService storageService; + + NotificationBloc({required this.storageService}) + : super( + NotificationSate( + durationNotificationEnabled: true, + durationNotificationValue: const Duration(hours: 6), + timeNotificationEnabled: true, + timeNotificationValue: const Duration(hours: 6), + dayNotificationEnabled: true, + dayNotificationValue: Day.saturday, + openingNotificationEnabled: true, + closingNotificationEnabled: true), + ) { + on( + _onOpeningNotificationStateEvent, + ); + on( + _onClosingNotificationStateEvent, + ); + on( + _onDayNotificationStateEvent, + ); + on( + _onDayNotificationValueEvent, + ); + on( + _onTimeNotificationStateEvent, + ); + on( + _onTimeNotificationValueEvent, + ); + on( + _onDurationNotificationStateEvent, + ); + on( + _onDurationNotificationValueEvent, + ); + on( + _onAppEvent, + ); + } + + Future _onOpeningNotificationStateEvent( + OpeningNotificationStateEvent event, + Emitter emit) async { + await storageService.saveBool( + Const.notificationOpeningEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + emit( + state.copyWith(openingNotificationEnabled: event.enabled), + ); + } + + Future _onClosingNotificationStateEvent( + ClosingNotificationStateEvent event, + Emitter emit) async { + await storageService.saveBool( + Const.notificationClosingEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + emit( + state.copyWith(closingNotificationEnabled: event.enabled), + ); + } + + Future _onDayNotificationStateEvent( + DayNotificationStateEvent event, Emitter emit) async { + await storageService.saveBool( + Const.notificationDayEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + emit( + state.copyWith(dayNotificationEnabled: event.enabled), + ); + } + + Future _onDayNotificationValueEvent( + DayNotificationValueEvent event, Emitter emit) async { + await storageService.saveDay(Const.notificationDayValueKey, event.day); + HapticFeedback.lightImpact(); + emit( + state.copyWith(dayNotificationValue: event.day), + ); + } + + Future _onTimeNotificationStateEvent( + TimeNotificationStateEvent event, Emitter emit) async { + await storageService.saveBool( + Const.notificationTimeEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + emit( + state.copyWith(timeNotificationEnabled: event.enabled), + ); + } + + Future _onTimeNotificationValueEvent( + TimeNotificationValueEvent event, Emitter emit) async { + await storageService.saveDuration( + Const.notificationTimeValueKey, event.time); + emit( + state.copyWith(timeNotificationValue: event.time), + ); + } + + Future _onDurationNotificationStateEvent( + DurationNotificationStateEvent event, + Emitter emit) async { + await storageService.saveBool( + Const.notificationDurationEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + emit( + state.copyWith(durationNotificationEnabled: event.enabled), + ); + } + + Future _onDurationNotificationValueEvent( + DurationNotificationValueEvent event, + Emitter emit) async { + await storageService.saveDuration( + Const.notificationDurationValueKey, event.duration); + emit( + state.copyWith(durationNotificationValue: event.duration), + ); + } + + Future _onAppEvent( + AppEvent event, Emitter emit) async { + final durationNotificationEnabled = + storageService.readBool(Const.notificationDurationEnabledKey) ?? + Const.notificationDurationEnabledDefaultValue; + + final durationNotificationValue = + storageService.readDuration(Const.notificationDurationValueKey) ?? + Const.notificationDurationValueDefaultValue; + + final timeNotificationEnabled = + storageService.readBool(Const.notificationTimeEnabledKey) ?? + Const.notificationTimeEnabledDefaultValue; + + final timeNotificationValue = + storageService.readDuration(Const.notificationTimeValueKey) ?? + Const.notificationTimeValueDefaultValue; + + final dayNotificationEnabled = + storageService.readBool(Const.notificationDayEnabledKey) ?? + Const.notificationDayEnabledDefaultValue; + + final openingNotificationEnabled = + storageService.readBool(Const.notificationOpeningEnabledKey) ?? + Const.notificationOpeningEnabledDefaultValue; + + final closingNotificationEnabled = + storageService.readBool(Const.notificationClosingEnabledKey) ?? + Const.notificationClosingEnabledDefaultValue; + + emit( + state.copyWith( + durationNotificationEnabled: durationNotificationEnabled, + durationNotificationValue: durationNotificationValue, + timeNotificationEnabled: timeNotificationEnabled, + timeNotificationValue: timeNotificationValue, + dayNotificationEnabled: dayNotificationEnabled, + openingNotificationEnabled: openingNotificationEnabled, + closingNotificationEnabled: closingNotificationEnabled), + ); + } +} diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart new file mode 100644 index 00000000..a6459f41 --- /dev/null +++ b/lib/bloc/notification/notification_event.dart @@ -0,0 +1,55 @@ +part of 'notification_bloc.dart'; + +class NotificationEvent extends ChaboEvent {} + +class OpeningNotificationStateEvent extends NotificationEvent { + final bool enabled; + + OpeningNotificationStateEvent({required this.enabled}) : super(); +} + +class ClosingNotificationStateEvent extends NotificationEvent { + final bool enabled; + + ClosingNotificationStateEvent({required this.enabled}) : super(); +} + +class DayNotificationStateEvent extends NotificationEvent { + final bool enabled; + + DayNotificationStateEvent({required this.enabled}) : super(); +} + +class TimeNotificationStateEvent extends NotificationEvent { + final bool enabled; + + TimeNotificationStateEvent({required this.enabled}) : super(); +} + +class DurationNotificationStateEvent extends NotificationEvent { + final bool enabled; + + DurationNotificationStateEvent({required this.enabled}) : super(); +} + +class DayNotificationValueEvent extends NotificationEvent { + final Day day; + + DayNotificationValueEvent({required this.day}) : super(); +} + +class DurationNotificationValueEvent extends NotificationEvent { + final Duration duration; + + DurationNotificationValueEvent({required this.duration}) : super(); +} + +class TimeNotificationValueEvent extends NotificationEvent { + final Duration time; + + TimeNotificationValueEvent({required this.time}) : super(); +} + +class AppEvent extends NotificationEvent { + AppEvent() : super(); +} diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart new file mode 100644 index 00000000..7f84a86e --- /dev/null +++ b/lib/bloc/notification/notification_state.dart @@ -0,0 +1,62 @@ +part of 'notification_bloc.dart'; + +class NotificationSate { + final bool durationNotificationEnabled; + final Duration durationNotificationValue; + final bool timeNotificationEnabled; + final Duration timeNotificationValue; + final bool dayNotificationEnabled; + final Day dayNotificationValue; + final bool openingNotificationEnabled; + final bool closingNotificationEnabled; + + NotificationSate( + {required this.durationNotificationEnabled, + required this.durationNotificationValue, + required this.timeNotificationEnabled, + required this.timeNotificationValue, + required this.dayNotificationEnabled, + required this.dayNotificationValue, + required this.openingNotificationEnabled, + required this.closingNotificationEnabled}); + + NotificationSate copyWith( + {bool? durationNotificationEnabled, + Duration? durationNotificationValue, + bool? timeNotificationEnabled, + Duration? timeNotificationValue, + bool? dayNotificationEnabled, + Day? dayNotificationValue, + bool? openingNotificationEnabled, + bool? closingNotificationEnabled}) { + return NotificationSate( + durationNotificationEnabled: + durationNotificationEnabled ?? this.durationNotificationEnabled, + durationNotificationValue: + durationNotificationValue ?? this.durationNotificationValue, + timeNotificationEnabled: + timeNotificationEnabled ?? this.timeNotificationEnabled, + timeNotificationValue: + timeNotificationValue ?? this.timeNotificationValue, + dayNotificationEnabled: + dayNotificationEnabled ?? this.dayNotificationEnabled, + dayNotificationValue: dayNotificationValue ?? this.dayNotificationValue, + openingNotificationEnabled: + openingNotificationEnabled ?? this.openingNotificationEnabled, + closingNotificationEnabled: + closingNotificationEnabled ?? this.closingNotificationEnabled); + } + + TimeOfDay durationToTimeOfDay(Duration duration) { + return TimeOfDay(hour: duration.inHours, minute: duration.inMinutes % 60); + } + + String durationToString(Duration duration) { + if (duration.inMinutes % 60 == 0) { + return '${duration.inHours.toString()}h'; + } else if (duration.inHours == 0) { + return '${duration.inMinutes.toString()}mins'; + } + return '${duration.inHours.toString()}h ${(duration.inMinutes % 60).toString()}mins'; + } +} diff --git a/lib/bloc/opening_notification/opening_notification_bloc.dart b/lib/bloc/opening_notification/opening_notification_bloc.dart index 2149f512..946137cc 100644 --- a/lib/bloc/opening_notification/opening_notification_bloc.dart +++ b/lib/bloc/opening_notification/opening_notification_bloc.dart @@ -5,7 +5,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'opening_notification_event.dart'; - part 'opening_notification_state.dart'; class OpeningNotificationBloc diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index dc6410fc..7af2b12a 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -5,7 +5,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'scroll_status_event.dart'; - part 'scroll_status_state.dart'; class ScrollStatusBloc extends Bloc { diff --git a/lib/bloc/time_picker/time_picker_bloc.dart b/lib/bloc/time_picker/time_picker_bloc.dart deleted file mode 100644 index 54978e1c..00000000 --- a/lib/bloc/time_picker/time_picker_bloc.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'time_picker_event.dart'; -part 'time_picker_state.dart'; - -class TimePickerBloc extends Bloc { - final StorageService storageService; - - TimePickerBloc({required this.storageService}) - : super( - TimePickerState(time: const Duration(hours: 20), enabled: false)) { - on( - _onTimePickerChanged, - ); - on( - _onStateChanged, - ); - on( - _onAppStateChanged, - ); - } - - Future _onTimePickerChanged( - TimePickerChanged event, Emitter emit) async { - await storageService.saveDuration( - Const.notificationTimeValueKey, event.time); - emit( - state.copyWith(time: event.time), - ); - } - - Future _onStateChanged( - TimePickerStateChanged event, Emitter emit) async { - await storageService.saveBool( - Const.notificationTimeEnabledKey, event.enabled); - HapticFeedback.lightImpact(); - emit( - state.copyWith(enabled: event.enabled), - ); - } - - Future _onAppStateChanged( - TimeAppStateChanged event, Emitter emit) async { - final time = storageService.readDuration(Const.notificationTimeValueKey) ?? - Const.notificationTimeValueDefaultValue; - final enabledValue = - storageService.readBool(Const.notificationTimeEnabledKey) ?? - Const.notificationTimeEnabledDefaultValue; - emit( - state.copyWith(enabled: enabledValue, time: time), - ); - } -} diff --git a/lib/bloc/time_picker/time_picker_event.dart b/lib/bloc/time_picker/time_picker_event.dart deleted file mode 100644 index 3c845509..00000000 --- a/lib/bloc/time_picker/time_picker_event.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of 'time_picker_bloc.dart'; - -class TimePickerEvent extends ChaboEvent {} - -class TimePickerChanged extends TimePickerEvent { - final Duration time; - - TimePickerChanged({required this.time}) : super(); -} - -class TimePickerStateChanged extends TimePickerEvent { - final bool enabled; - - TimePickerStateChanged({required this.enabled}) : super(); -} - -class TimeAppStateChanged extends TimePickerEvent { - TimeAppStateChanged() : super(); -} diff --git a/lib/bloc/time_picker/time_picker_state.dart b/lib/bloc/time_picker/time_picker_state.dart deleted file mode 100644 index 85c9b1df..00000000 --- a/lib/bloc/time_picker/time_picker_state.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of 'time_picker_bloc.dart'; - -class TimePickerState { - final bool enabled; - final Duration time; - - TimePickerState({required this.enabled, required this.time}); - - TimePickerState copyWith({Duration? time, bool? enabled}) { - return TimePickerState( - time: time ?? this.time, enabled: enabled ?? this.enabled); - } - - TimeOfDay toTimeOfDay() { - return TimeOfDay(hour: time.inHours, minute: time.inMinutes % 60); - } - - String getDuration() { - if (time.inMinutes % 60 == 0) { - return '${time.inHours.toString()}h'; - } else if (time.inHours == 0) { - return '${time.inMinutes.toString()}mins'; - } - return '${time.inHours.toString()}h ${(time.inMinutes % 60).toString()}mins'; - } -} diff --git a/lib/chabo.dart b/lib/chabo.dart index 7626ba36..a64cb038 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,12 +1,8 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; -import 'package:chabo/bloc/closing_notification/closing_notification_bloc.dart'; -import 'package:chabo/bloc/day_picker/day_picker_bloc.dart'; -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; -import 'package:chabo/bloc/opening_notification/opening_notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; @@ -39,60 +35,6 @@ class Chabo extends StatelessWidget { ), ), - /// Bloc intended to manage the duration type Notification - BlocProvider( - create: (_) => DurationPickerBloc( - storageService: storageService, - )..add( - DurationAppStateChanged(), - ), - ), - - /// Bloc intended to manage the time type Notification - BlocProvider( - create: (_) => TimePickerBloc( - storageService: storageService, - )..add( - TimeAppStateChanged(), - ), - ), - - /// Bloc intended to manage the day type Notification - BlocProvider( - create: (_) => DayPickerBloc( - storageService: storageService, - )..add( - DayAppStateChanged(), - ), - ), - - /// Bloc intended to manage the opening type Notification - BlocProvider( - create: (_) => OpeningNotificationBloc( - storageService: storageService, - )..add( - OpeningAppStateChanged(), - ), - ), - - /// Bloc intended to manage the closing type Notification - BlocProvider( - create: (_) => ClosingNotificationBloc( - storageService: storageService, - )..add( - ClosingAppStateChanged(), - ), - ), - - /// Bloc intended to manage the day type Notification - BlocProvider( - create: (_) => DayPickerBloc( - storageService: storageService, - )..add( - DayAppStateChanged(), - ), - ), - /// Bloc intended to manage the Notifications service BlocProvider( create: (_) => NotificationServiceCubit( @@ -115,6 +57,15 @@ class Chabo extends StatelessWidget { scrollController: ScrollController(), ), ), + + /// Bloc intended to manage all Notifications + BlocProvider( + create: (_) => NotificationBloc( + storageService: storageService, + )..add( + AppEvent(), + ), + ), ], child: BlocBuilder( builder: (context, state) { diff --git a/lib/widgets/information_dialog.dart b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart similarity index 90% rename from lib/widgets/information_dialog.dart rename to lib/dialogs/chaban_bridge_forecast_information_dialog.dart index 1c127d1b..21f75ea5 100644 --- a/lib/widgets/information_dialog.dart +++ b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart @@ -2,10 +2,11 @@ import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class InformationDialog extends StatelessWidget { +class ChabanBridgeForecastInformationDialog extends StatelessWidget { final AbstractChabanBridgeForecast chabanBridgeForecast; - const InformationDialog({super.key, required this.chabanBridgeForecast}); + const ChabanBridgeForecastInformationDialog( + {super.key, required this.chabanBridgeForecast}); @override Widget build(BuildContext context) { diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart new file mode 100644 index 00000000..f8ef0088 --- /dev/null +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -0,0 +1,93 @@ +import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/models/enums/day.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class DaysOfTheWeekDialog extends StatelessWidget { + const DaysOfTheWeekDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + insetPadding: const EdgeInsets.symmetric( + horizontal: 20, + ), + titlePadding: const EdgeInsets.all(0), + contentPadding: const EdgeInsets.all(20), + actionsPadding: const EdgeInsets.fromLTRB( + 0, + 0, + 20, + 10, + ), + title: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular( + 15.0, + ), + topRight: Radius.circular( + 15.0, + ), + ), + ), + padding: const EdgeInsets.fromLTRB( + 20, + 20, + 0, + 15, + ), + child: Row( + children: [ + Icon(Icons.calendar_month_outlined, + color: Theme.of(context).colorScheme.onPrimaryContainer), + const SizedBox(width: 20), + Text( + AppLocalizations.of(context)!.day, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + ], + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 15, + ), + ), + content: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: Day.values + .map( + (day) => RadioListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, + ), + ), + title: Text( + day.localizedName(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + value: day, + groupValue: state.dayNotificationValue, + onChanged: (Day? value) { + Navigator.pop(context, value); + }, + ), + ) + .toList(), + ); + }), + ); + } +} diff --git a/lib/dialogs/notification_dialog.dart b/lib/dialogs/notification_dialog.dart deleted file mode 100644 index 76998f94..00000000 --- a/lib/dialogs/notification_dialog.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:chabo/bloc/closing_notification/closing_notification_bloc.dart'; -import 'package:chabo/bloc/day_picker/day_picker_bloc.dart'; -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/bloc/opening_notification/opening_notification_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; -import 'package:chabo/models/enums/day.dart'; -import 'package:chabo/widgets/notification/closing_notification_settings_widget.dart'; -import 'package:chabo/widgets/notification/day_notification_settings_widget.dart'; -import 'package:chabo/widgets/notification/duration_notification_settings_widget.dart'; -import 'package:chabo/widgets/notification/opening_notification_settings_widget.dart'; -import 'package:chabo/widgets/notification/time_notification_settings_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class NotificationDialog extends StatelessWidget { - const NotificationDialog({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return AlertDialog( - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - BlocBuilder( - builder: (context, state) { - return OpeningNotificationSettingsWidget( - enabled: state.enabled, - title: AppLocalizations.of(context)!.openingNotificationTitle, - subtitle: AppLocalizations.of(context)! - .openingNotificationExplanation, - ); - }, - ), - const SizedBox( - height: 15, - ), - BlocBuilder( - builder: (context, state) { - return ClosingNotificationSettingsWidget( - enabled: state.enabled, - title: AppLocalizations.of(context)!.closingNotificationTitle, - subtitle: AppLocalizations.of(context)! - .closingNotificationExplanation, - ); - }, - ), - const Divider(), - BlocBuilder( - builder: (context, state) { - return DurationNotificationSettingsWidget( - state: state, - enabled: state.enabled, - title: - AppLocalizations.of(context)!.durationNotificationTitle( - state.getDuration(), - ), - subtitle: AppLocalizations.of(context)! - .durationNotificationExplanation( - state.getDuration(), - ), - ); - }, - ), - const SizedBox( - height: 25, - ), - BlocBuilder( - builder: (context, state) { - return TimeNotificationSettingsWidget( - state: state, - enabled: state.enabled, - title: AppLocalizations.of(context)! - .timeNotificationTitle(state.getDuration()), - subtitle: AppLocalizations.of(context)! - .timeNotificationExplanation(state.getDuration()), - ); - }, - ), - const SizedBox( - height: 25, - ), - BlocBuilder( - builder: (context, state) { - return DayNotificationSettingsWidget( - state: state, - title: AppLocalizations.of(context)!.dayNotificationTitle( - state.day.localizedName(context), - ), - subtitle: - AppLocalizations.of(context)!.dayNotificationExplanation( - state.day.localizedName(context), - ), - ); - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8e859081..d0c96956 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -13,6 +13,7 @@ "scheduledToOpen": "scheduled to open in", "theBridgeIsCurrently": "the Chaban bridge is", "settingsTitle": "Settings", + "notificationsTitle": "Notifications", "information": "Information", "dialogInformationContentThe": "", "dialogInformationContentThe2": "from ", @@ -147,5 +148,8 @@ "placeholders": { "choice": {} } - } + }, + "day": "Day", + "refreshingNotifications": "Refreshing your notifications", + "refreshingNotificationsDone": "Done !" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 070679ed..4a0831f7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -13,6 +13,7 @@ "scheduledToOpen": "programado para abrir en", "theBridgeIsCurrently": "el puente Chaban está", "settingsTitle": "Ajustes", + "notificationsTitle": "Notificaciónes", "information": "Información", "dialogInformationContentThe": "el ", "dialogInformationContentThe2": "desde ", @@ -147,5 +148,8 @@ "placeholders": { "choice": {} } - } + }, + "day": "Día", + "refreshingNotifications": "Refrescando tus notificaciones", + "refreshingNotificationsDone": "Terminado !" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 05d705a4..1a96ac99 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -13,6 +13,7 @@ "scheduledToOpen": "ouverture prévue dans", "theBridgeIsCurrently": "le pont Chaban est", "settingsTitle": "Paramètres", + "notificationsTitle": "Notifications", "information": "Information", "dialogInformationContentThe": "le ", "dialogInformationContentThe2": "du ", @@ -147,5 +148,8 @@ "placeholders": { "choice": {} } - } + }, + "day": "Jour", + "refreshingNotifications": "Actualisation de vos notifications", + "refreshingNotificationsDone": "Terminé !" } \ No newline at end of file diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index 3e37515a..8972993a 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -1,5 +1,3 @@ -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_reason.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_type.dart'; import 'package:equatable/equatable.dart'; @@ -57,10 +55,9 @@ abstract class AbstractChabanBridgeForecast extends Equatable { Widget getIconWidget(BuildContext context, bool reversed); String getNotificationDurationMessage( - BuildContext context, DurationPickerState durationPickerState); + BuildContext context, String pickedDuration); - String getNotificationTimeMessage( - BuildContext context, TimePickerState timePickerState); + String getNotificationTimeMessage(BuildContext context); String getNotificationClosingMessage(BuildContext context); diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index 64e04b98..bc2e2a5b 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -1,5 +1,3 @@ -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; import 'package:chabo/extensions/boats_extensions.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; @@ -166,17 +164,16 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { @override String getNotificationDurationMessage( - BuildContext context, DurationPickerState durationPickerState) { + BuildContext context, String pickedDuration) { return AppLocalizations.of(context)!.notificationDurationBoatMessage( boats.toLocalizedString(context), - durationPickerState.getDuration(), + pickedDuration, durationString(), ); } @override - String getNotificationTimeMessage( - BuildContext context, TimePickerState timePickerState) { + String getNotificationTimeMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationTimeBoatMessage( boats.toLocalizedString(context), DateFormat.Hm().format(circulationClosingDate), diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index 4db24e58..f70d0609 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -1,5 +1,3 @@ -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; @@ -56,16 +54,15 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { @override String getNotificationDurationMessage( - BuildContext context, DurationPickerState durationPickerState) { + BuildContext context, String pickedDuration) { return AppLocalizations.of(context)!.notificationDurationMaintenanceMessage( - durationPickerState.getDuration(), + pickedDuration, durationString(), ); } @override - String getNotificationTimeMessage( - BuildContext context, TimePickerState timePickerState) { + String getNotificationTimeMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationTimeMaintenanceMessage( DateFormat.Hm().format(circulationClosingDate), durationString(), diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index fde22301..323ba633 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -1,9 +1,6 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; -import 'package:chabo/bloc/closing_notification/closing_notification_bloc.dart'; -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; -import 'package:chabo/bloc/opening_notification/opening_notification_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/models/chaban_bridge_status.dart'; import 'package:chabo/screens/error_screen.dart'; @@ -12,6 +9,7 @@ import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ChabanBridgeForecastScreen extends StatefulWidget { const ChabanBridgeForecastScreen({Key? key}) : super(key: key); @@ -56,62 +54,77 @@ class _ChabanBridgeForecastScreenState return MultiBlocListener( listeners: [ - BlocListener( - listener: (context, state) { - context + BlocListener( + listener: (context, state) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: CircularProgressIndicator( + color: Theme.of(context) + .colorScheme + .inversePrimary, + strokeWidth: 5, + ), + ), + Expanded( + flex: 5, + child: Text( + AppLocalizations.of(context)! + .refreshingNotifications, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + await context .read() .state - .computeDurationScheduledNotifications( - BlocProvider.of( - context) - .state - .chabanBridgeForecasts, - state, - context); - }, - ), - BlocListener( - listener: (context, state) { - context - .read() - .state - .computeTimeScheduledNotifications( - BlocProvider.of( - context) - .state - .chabanBridgeForecasts, - state, - context); - }, - ), - BlocListener( - listener: (context, state) { - context - .read() - .state - .computeOpeningScheduledNotifications( - BlocProvider.of( - context) - .state - .chabanBridgeForecasts, - state, - context); - }, - ), - BlocListener( - listener: (context, state) { - context - .read() - .state - .computeClosingScheduledNotifications( + .computeNotifications( BlocProvider.of( context) .state .chabanBridgeForecasts, state, context); + ScaffoldMessenger.of(context).removeCurrentSnackBar( + reason: SnackBarClosedReason.remove, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration( + milliseconds: 1000, + ), + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Icon( + Icons.check, + color: Theme.of(context) + .colorScheme + .inversePrimary, + )), + Expanded( + flex: 8, + child: Text( + AppLocalizations.of(context)! + .refreshingNotificationsDone, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); }, ) ], diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart new file mode 100644 index 00000000..f1fbef7f --- /dev/null +++ b/lib/screens/notification_screen.dart @@ -0,0 +1,262 @@ +import 'dart:ui'; + +import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; +import 'package:chabo/models/enums/day.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class NotificationScreen extends StatefulWidget { + const NotificationScreen({Key? key}) : super(key: key); + + @override + State createState() => _NotificationScreenState(); +} + +class _NotificationScreenState extends CustomWidgetState { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + leading: const Icon(Icons.notifications_active_outlined), + title: Text( + AppLocalizations.of(context)!.notificationsTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.only( + top: 20, + ), + child: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + _CustomListTile( + onChanged: (bool value) => + BlocProvider.of(context).add( + OpeningNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.openingNotificationEnabled, + title: AppLocalizations.of(context)!.openingNotificationTitle, + subtitle: AppLocalizations.of(context)! + .openingNotificationExplanation, + leadingIcon: Icons.check_circle, + iconColor: Colors.green, + ), + _CustomListTile( + onChanged: (bool value) => + BlocProvider.of(context).add( + ClosingNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.closingNotificationEnabled, + title: AppLocalizations.of(context)!.closingNotificationTitle, + subtitle: AppLocalizations.of(context)! + .closingNotificationExplanation, + leadingIcon: Icons.block_rounded, + iconColor: Colors.red, + ), + const SizedBox( + height: 20, + ), + _CustomListTile( + onTap: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state + .durationToTimeOfDay(state.durationNotificationValue), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: true, + ), + child: child!, + ); + }, + ); + if (time != null) { + // ignore: use_build_context_synchronously + BlocProvider.of(context).add( + DurationNotificationValueEvent( + duration: Duration( + hours: time.hour, + minutes: time.minute, + ), + ), + ); + } + }, + onChanged: (bool value) => + BlocProvider.of(context).add( + DurationNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.durationNotificationEnabled, + title: + AppLocalizations.of(context)!.durationNotificationTitle( + state.durationToString(state.durationNotificationValue), + ), + subtitle: AppLocalizations.of(context)! + .durationNotificationExplanation( + state.durationToString(state.durationNotificationValue), + ), + leadingIcon: Icons.timer_outlined, + ), + _CustomListTile( + onTap: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state + .durationToTimeOfDay(state.timeNotificationValue), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: false, + ), + child: child!, + ); + }, + ); + if (time != null) { + // ignore: use_build_context_synchronously + BlocProvider.of(context).add( + TimeNotificationValueEvent( + time: Duration( + hours: time.hour, + minutes: time.minute, + ), + ), + ); + } + }, + onChanged: (bool value) => + BlocProvider.of(context).add( + TimeNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.timeNotificationEnabled, + title: AppLocalizations.of(context)!.timeNotificationTitle( + state.durationToString(state.timeNotificationValue), + ), + subtitle: + AppLocalizations.of(context)!.timeNotificationExplanation( + state.durationToString(state.timeNotificationValue), + ), + leadingIcon: Icons.plus_one_outlined, + ), + BlocBuilder( + builder: (context, state) { + return _CustomListTile( + onTap: () async { + final day = await showDialog( + context: context, + builder: ( + BuildContext context, + ) { + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: const DaysOfTheWeekDialog(), + ); + }, + ); + if (day != null) { + BlocProvider.of(context).add( + DayNotificationValueEvent(day: day), + ); + } + }, + enabled: state.dayNotificationEnabled, + title: AppLocalizations.of(context)!.dayNotificationTitle( + state.dayNotificationValue.localizedName(context), + ), + subtitle: AppLocalizations.of(context)! + .dayNotificationExplanation( + state.dayNotificationValue.localizedName(context), + ), + leadingIcon: Icons.calendar_month_outlined, + onChanged: (bool value) => + BlocProvider.of(context).add( + DayNotificationStateEvent( + enabled: value, + ), + ), + ); + }, + ), + ], + ); + }, + ), + ), + ); + } +} + +class _CustomListTile extends StatelessWidget { + final bool enabled; + final Function()? onTap; + final Function(bool) onChanged; + final String title; + final String subtitle; + final IconData leadingIcon; + final Color? iconColor; + + const _CustomListTile( + {Key? key, + required this.enabled, + this.onTap, + this.iconColor, + required this.title, + required this.subtitle, + required this.leadingIcon, + required this.onChanged}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + title, + style: Theme.of(context).textTheme.titleLarge, + ), + horizontalTitleGap: 0, + subtitle: Text(subtitle), + leading: Icon( + leadingIcon, + ), + onTap: onTap, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + onTap != null + ? const VerticalDivider( + width: 20, + ) + : const SizedBox.shrink(), + Switch.adaptive( + value: enabled, + onChanged: onChanged, + ), + ], + ), + iconColor: iconColor ?? Theme.of(context).colorScheme.primary, + enabled: enabled, + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 89c9d997..6db27377 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -25,11 +25,24 @@ class _SettingsScreenState extends CustomWidgetState { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - leading: - const Hero(tag: 'settingsButtonIcon', child: Icon(Icons.settings)), - title: Text(AppLocalizations.of(context)!.settingsTitle), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + leading: const Hero( + tag: 'settingsButtonIcon', + child: Icon( + Icons.settings, + ), + ), + title: Text( + AppLocalizations.of(context)!.settingsTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), ), body: SingleChildScrollView( + padding: const EdgeInsets.only( + top: 20, + ), child: Column( children: [ BlocBuilder( diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index af5c5d28..51732f0f 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -1,15 +1,12 @@ import 'dart:developer' as developer; import 'dart:io'; -import 'package:chabo/bloc/closing_notification/closing_notification_bloc.dart'; -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/bloc/opening_notification/opening_notification_bloc.dart'; -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/const.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest_all.dart' as tz; @@ -74,111 +71,119 @@ class NotificationService { return false; } - void computeOpeningScheduledNotifications( + Future computeNotifications( List chabanBridgeForecasts, - OpeningNotificationState openingNotificationState, + NotificationSate notificationSate, BuildContext context) async { tz.initializeTimeZones(); - NotificationDetails notificationDetails = _notificationDetails( - Const.notificationOpeningChannelId, - AppLocalizations.of(context)!.notificationOpeningChannelName); - if (openingNotificationState.enabled) { - int index = Const.openingNotificationStartId; - for (final chabanBridgeForecast in chabanBridgeForecasts) { - final notificationScheduleTime = - chabanBridgeForecast.circulationReOpeningDate; - await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationOpeningTitle, - AppLocalizations.of(context)!.notificationOpeningMessage, - notificationScheduleTime, - notificationDetails); + int index = 0; + await localNotifications.cancelAll(); + for (final chabanBridgeForecast in chabanBridgeForecasts) { + if (notificationSate.openingNotificationEnabled) { + index += 1; + await _createOpeningScheduledNotifications( + index, chabanBridgeForecast, context); + } + if (notificationSate.closingNotificationEnabled) { index += 1; + await _createClosingScheduledNotifications( + index, chabanBridgeForecast, context); + } + if (notificationSate.timeNotificationEnabled) { + index += 1; + await _createTimeScheduledNotifications(index, chabanBridgeForecast, + context, notificationSate.timeNotificationValue); + } + if (notificationSate.durationNotificationEnabled) { + index += 1; + await _createDurationScheduledNotifications( + index, + chabanBridgeForecast, + context, + notificationSate.durationNotificationValue, + notificationSate.durationToString( + notificationSate.durationNotificationValue, + ), + ); } } } - void computeClosingScheduledNotifications( - List chabanBridgeForecasts, - ClosingNotificationState closingNotificationState, + Future _createOpeningScheduledNotifications( + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, BuildContext context) async { - tz.initializeTimeZones(); + final notificationScheduleTime = + chabanBridgeForecast.circulationReOpeningDate; NotificationDetails notificationDetails = _notificationDetails( - Const.notificationClosingChannelId, - AppLocalizations.of(context)!.notificationClosingChannelName); - if (closingNotificationState.enabled) { - int index = Const.closingNotificationStartId; - for (final chabanBridgeForecast in chabanBridgeForecasts) { - final notificationScheduleTime = - chabanBridgeForecast.circulationClosingDate; - await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationClosingTitle, - chabanBridgeForecast.getNotificationClosingMessage(context), - notificationScheduleTime, - notificationDetails); - index += 1; - } - } + Const.notificationOpeningChannelId, + AppLocalizations.of(context)!.notificationOpeningChannelName); + await _scheduleNotification( + index, + AppLocalizations.of(context)!.notificationOpeningTitle, + AppLocalizations.of(context)!.notificationOpeningMessage, + notificationScheduleTime, + notificationDetails); } - void computeDurationScheduledNotifications( - List chabanBridgeForecasts, - DurationPickerState durationPickerState, + Future _createClosingScheduledNotifications( + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, BuildContext context) async { - tz.initializeTimeZones(); + final notificationScheduleTime = + chabanBridgeForecast.circulationClosingDate; NotificationDetails notificationDetails = _notificationDetails( - Const.notificationDurationChannelId, - AppLocalizations.of(context)!.notificationDurationChannelName); - if (durationPickerState.enabled) { - int index = Const.durationNotificationStartId; - for (final chabanBridgeForecast in chabanBridgeForecasts) { - final notificationScheduleTime = chabanBridgeForecast - .circulationClosingDate - .subtract(durationPickerState.duration); - await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationDurationTitle, - chabanBridgeForecast.getNotificationDurationMessage( - context, durationPickerState), - notificationScheduleTime, - notificationDetails); - index += 1; - } - } + Const.notificationClosingChannelId, + AppLocalizations.of(context)!.notificationClosingChannelName); + await _scheduleNotification( + index, + AppLocalizations.of(context)!.notificationClosingTitle, + chabanBridgeForecast.getNotificationClosingMessage(context), + notificationScheduleTime, + notificationDetails); } - void computeTimeScheduledNotifications( - List chabanBridgeForecasts, - TimePickerState timePickerState, - BuildContext context) async { - tz.initializeTimeZones(); + Future _createTimeScheduledNotifications( + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, + BuildContext context, + Duration value) async { + final notificationScheduleTime = chabanBridgeForecast.circulationClosingDate + .subtract( + const Duration( + days: 1, + ), + ) + .copyWith(hour: value.inHours, minute: value.inMinutes % 60); NotificationDetails notificationDetails = _notificationDetails( Const.notificationTimeChannelId, AppLocalizations.of(context)!.notificationTimeChannelName); - if (timePickerState.enabled) { - int index = Const.timeNotificationStartId; - for (final chabanBridgeForecast in chabanBridgeForecasts) { - final notificationScheduleTime = chabanBridgeForecast - .circulationClosingDate - .subtract( - const Duration( - days: 1, - ), - ) - .copyWith( - hour: timePickerState.time.inHours, - minute: timePickerState.time.inMinutes % 60); - await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationTimeTitle, - chabanBridgeForecast.getNotificationTimeMessage( - context, timePickerState), - notificationScheduleTime, - notificationDetails); - index += 1; - } - } + await _scheduleNotification( + index, + AppLocalizations.of(context)!.notificationTimeTitle, + chabanBridgeForecast.getNotificationTimeMessage(context), + notificationScheduleTime, + notificationDetails); + } + + Future _createDurationScheduledNotifications( + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, + BuildContext context, + Duration durationValue, + String durationString) async { + final notificationScheduleTime = + chabanBridgeForecast.circulationClosingDate.subtract(durationValue); + NotificationDetails notificationDetails = _notificationDetails( + Const.notificationDurationChannelId, + AppLocalizations.of(context)!.notificationDurationChannelName); + await _scheduleNotification( + index, + AppLocalizations.of(context)!.notificationDurationTitle, + chabanBridgeForecast.getNotificationDurationMessage( + context, durationString), + notificationScheduleTime, + notificationDetails); } NotificationDetails _notificationDetails( @@ -207,7 +212,7 @@ class NotificationService { developer.log( 'Creating a notification on channel ${notificationDetails.android!.channelId} with ID $notificationId scheduled at $notificationScheduleTime', name: 'notification-service.on.scheduleNotification'); - return await localNotifications.zonedSchedule( + await localNotifications.zonedSchedule( notificationId, notificationTitle, notificationMessage, diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/chaban_bridge_forecast_list_item.dart index e459d3f3..235c14bb 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/chaban_bridge_forecast_list_item.dart @@ -1,9 +1,9 @@ import 'dart:ui'; import 'package:chabo/custom_properties.dart'; +import 'package:chabo/dialogs/chaban_bridge_forecast_information_dialog.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; -import 'package:chabo/widgets/information_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -59,19 +59,15 @@ class ChabanBridgeForecastListItem extends StatelessWidget { }, barrierDismissible: true, transitionBuilder: (context, a1, a2, widget) { - return ScaleTransition( - scale: + return FadeTransition( + opacity: Tween(begin: 0.0, end: 1.0).animate(a1), - child: FadeTransition( - opacity: Tween(begin: 0.0, end: 1.0) - .animate(a1), - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: InformationDialog( - chabanBridgeForecast: chabanBridgeForecast, - ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: ChabanBridgeForecastInformationDialog( + chabanBridgeForecast: chabanBridgeForecast, ), ), ); diff --git a/lib/widgets/notification/closing_notification_settings_widget.dart b/lib/widgets/notification/closing_notification_settings_widget.dart deleted file mode 100644 index f0c2b1d4..00000000 --- a/lib/widgets/notification/closing_notification_settings_widget.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:chabo/bloc/closing_notification/closing_notification_bloc.dart'; -import 'package:chabo/widgets/notification/notification_settings_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class ClosingNotificationSettingsWidget extends NotificationSettingsWidget { - const ClosingNotificationSettingsWidget( - {required String title, - required bool enabled, - required String subtitle, - Key? key}) - : super(key: key, title: title, enabled: enabled, subtitle: subtitle); - - @override - void onEnablePressed(bool value, BuildContext context) { - BlocProvider.of(context).add( - ClosingNotificationChanged( - enabled: value, - ), - ); - } - - @override - void onEditPressed(BuildContext context) { - throw UnimplementedError(); - } -} diff --git a/lib/widgets/notification/day_notification_settings_widget.dart b/lib/widgets/notification/day_notification_settings_widget.dart deleted file mode 100644 index b7f63d2f..00000000 --- a/lib/widgets/notification/day_notification_settings_widget.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:chabo/bloc/day_picker/day_picker_bloc.dart'; -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/models/enums/day.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class DayNotificationSettingsWidget extends StatefulWidget { - final String title; - final String subtitle; - final DayPickerState state; - - const DayNotificationSettingsWidget( - {Key? key, - required this.title, - required this.subtitle, - required this.state}) - : super(key: key); - - @override - State createState() { - return _DayNotificationSettingsWidgetState(); - } -} - -class _DayNotificationSettingsWidgetState - extends State with TickerProviderStateMixin { - late final AnimationController _controllerAnimation = AnimationController( - duration: - const Duration(milliseconds: CustomProperties.animationDurationMs), - vsync: this, - ); - late final Animation _settingsWidgetAnimation = CurvedAnimation( - parent: _controllerAnimation, - curve: Curves.linear, - ); - - @override - void dispose() { - _controllerAnimation.dispose(); - super.dispose(); - } - - _toggleSettingsContainer() { - if (_settingsWidgetAnimation.status != AnimationStatus.completed) { - _controllerAnimation.forward(); - BlocProvider.of(context).add( - DayPickerSettingChanged( - isOpen: true, - ), - ); - } else { - _controllerAnimation.animateBack( - 0, - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs, - ), - ); - BlocProvider.of(context).add( - DayPickerSettingChanged( - isOpen: false, - ), - ); - } - } - - Widget _buildChip(Day day, Day selectedDay) { - final isSelected = selectedDay == day; - return FilterChip( - padding: const EdgeInsets.symmetric(horizontal: 5), - showCheckmark: false, - label: Text( - day.localizedName(context), - style: TextStyle( - fontWeight: FontWeight.bold, - color: isSelected - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.tertiary), - ), - avatar: isSelected ? const Icon(Icons.check) : null, - onSelected: (bool value) => { - BlocProvider.of(context).add( - DayPickerChanged(day: day), - ), - }, - selectedColor: Theme.of(context).colorScheme.primary, - disabledColor: Theme.of(context).colorScheme.secondary, - selected: isSelected, - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.title, - style: const TextStyle(fontSize: 28), - ), - Text( - widget.subtitle, - style: const TextStyle(fontSize: 15), - ), - ], - ), - ), - Column( - children: [ - Switch( - value: widget.state.enabled, - onChanged: null, - ), - IconButton( - onPressed: () => {_toggleSettingsContainer()}, - icon: AnimatedSwitcher( - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition(scale: animation, child: child); - }, - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs), - child: Icon( - key: ValueKey(widget.state.icon.toString()), - widget.state.icon, - size: 25, - ), - ), - ), - ], - ), - ], - ), - SizedBox( - width: double.infinity, - child: SizeTransition( - sizeFactor: _settingsWidgetAnimation, - child: Wrap( - spacing: 5, - children: Day.values - .map((day) => _buildChip(day, widget.state.day)) - .toList(), - ), - ), - ), - ], - ); - } -} diff --git a/lib/widgets/notification/duration_notification_settings_widget.dart b/lib/widgets/notification/duration_notification_settings_widget.dart deleted file mode 100644 index 0c4c7045..00000000 --- a/lib/widgets/notification/duration_notification_settings_widget.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:chabo/bloc/duration_picker/duration_picker_bloc.dart'; -import 'package:chabo/widgets/notification/notification_settings_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class DurationNotificationSettingsWidget extends NotificationSettingsWidget { - final DurationPickerState state; - - const DurationNotificationSettingsWidget( - {required this.state, - required String title, - required String subtitle, - required bool enabled, - Key? key}) - : super( - key: key, - title: title, - subtitle: subtitle, - enabled: enabled, - iconData: Icons.calendar_month); - - @override - void onEditPressed(BuildContext context) async { - var time = await showTimePicker( - initialEntryMode: TimePickerEntryMode.dialOnly, - context: context, - initialTime: state.toTimeOfDay(), - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: true, - ), - child: child!, - ); - }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - DurationPickerChanged( - duration: Duration( - hours: time.hour, - minutes: time.minute, - ), - ), - ); - } - } - - @override - void onEnablePressed(bool value, BuildContext context) { - BlocProvider.of(context).add( - DurationPickerStateChanged( - enabled: value, - ), - ); - } -} diff --git a/lib/widgets/notification/notification_settings_widget.dart b/lib/widgets/notification/notification_settings_widget.dart deleted file mode 100644 index b5aa96f1..00000000 --- a/lib/widgets/notification/notification_settings_widget.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:chabo/custom_properties.dart'; -import 'package:flutter/material.dart'; - -abstract class NotificationSettingsWidget extends StatelessWidget { - final String title; - final String? subtitle; - final IconData? iconData; - final bool enabled; - - const NotificationSettingsWidget( - {Key? key, - this.iconData, - this.subtitle, - required this.title, - required this.enabled}) - : super(key: key); - - void onEnablePressed(bool value, BuildContext context); - - void onEditPressed(BuildContext context); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle(fontSize: 28), - ), - subtitle != null - ? Text( - subtitle!, - style: const TextStyle(fontSize: 15), - ) - : const SizedBox.shrink(), - ], - ), - ), - Column( - children: [ - Switch( - value: enabled, - onChanged: (bool value) => onEnablePressed(value, context), - ), - iconData != null - ? IconButton( - onPressed: () => onEditPressed(context), - icon: AnimatedSwitcher( - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs, - ), - child: Icon( - iconData, - size: 25, - ), - ), - ) - : const SizedBox.shrink(), - ], - ), - ], - ), - ], - ); - } -} diff --git a/lib/widgets/notification/opening_notification_settings_widget.dart b/lib/widgets/notification/opening_notification_settings_widget.dart deleted file mode 100644 index 7d8f4433..00000000 --- a/lib/widgets/notification/opening_notification_settings_widget.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:chabo/bloc/opening_notification/opening_notification_bloc.dart'; -import 'package:chabo/widgets/notification/notification_settings_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class OpeningNotificationSettingsWidget extends NotificationSettingsWidget { - const OpeningNotificationSettingsWidget( - {required String title, - required bool enabled, - required String subtitle, - Key? key}) - : super(key: key, title: title, enabled: enabled, subtitle: subtitle); - - @override - void onEnablePressed(bool value, BuildContext context) { - BlocProvider.of(context).add( - OpeningNotificationChanged( - enabled: value, - ), - ); - } - - @override - void onEditPressed(BuildContext context) { - throw UnimplementedError(); - } -} diff --git a/lib/widgets/notification/time_notification_settings_widget.dart b/lib/widgets/notification/time_notification_settings_widget.dart deleted file mode 100644 index de16e05f..00000000 --- a/lib/widgets/notification/time_notification_settings_widget.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:chabo/bloc/time_picker/time_picker_bloc.dart'; -import 'package:chabo/widgets/notification/notification_settings_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class TimeNotificationSettingsWidget extends NotificationSettingsWidget { - final TimePickerState state; - - const TimeNotificationSettingsWidget( - {required this.state, - required String title, - required String subtitle, - required bool enabled, - Key? key}) - : super( - key: key, - title: title, - subtitle: subtitle, - enabled: enabled, - iconData: Icons.calendar_month); - - @override - void onEditPressed(BuildContext context) async { - var time = await showTimePicker( - initialEntryMode: TimePickerEntryMode.dialOnly, - context: context, - initialTime: state.toTimeOfDay(), - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: false, - ), - child: child!, - ); - }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - TimePickerChanged( - time: Duration( - hours: time.hour, - minutes: time.minute, - ), - ), - ); - } - } - - @override - void onEnablePressed(bool value, BuildContext context) { - BlocProvider.of(context).add( - TimePickerStateChanged( - enabled: value, - ), - ); - } -} diff --git a/lib/widgets/notification_tile_widget.dart b/lib/widgets/notification_tile_widget.dart index 22e1c5b4..268f16ae 100644 --- a/lib/widgets/notification_tile_widget.dart +++ b/lib/widgets/notification_tile_widget.dart @@ -1,7 +1,4 @@ -import 'dart:ui'; - -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/dialogs/notification_dialog.dart'; +import 'package:chabo/screens/notification_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -27,18 +24,10 @@ class NotificationTileWidget extends StatelessWidget { Icons.notifications_active_outlined, size: 30, ), - onTap: () async => await showDialog( - context: context, - builder: ( - BuildContext context, - ) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: const NotificationDialog(), - ); - }, + onTap: () async => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const NotificationScreen(), + ), ), ); } From 37c4b75fcf2a9cd8da539ce8574372ae295e4446 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 23 Mar 2023 23:00:36 +0100 Subject: [PATCH 11/65] chore(bloc): remove unnecessary BlocBuilder --- lib/dialogs/days_of_the_week_dialog.dart | 54 +++++++++--------- lib/screens/notification_screen.dart | 71 ++++++++++++------------ 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index f8ef0088..8c128fdf 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -1,12 +1,13 @@ -import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class DaysOfTheWeekDialog extends StatelessWidget { - const DaysOfTheWeekDialog({Key? key}) : super(key: key); + final Day selectedDay; + + const DaysOfTheWeekDialog({Key? key, required this.selectedDay}) + : super(key: key); @override Widget build(BuildContext context) { @@ -60,34 +61,31 @@ class DaysOfTheWeekDialog extends StatelessWidget { 15, ), ), - content: BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - children: Day.values - .map( - (day) => RadioListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: Day.values + .map( + (day) => RadioListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, ), - title: Text( - day.localizedName(context), - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + ), + title: Text( + day.localizedName(context), + style: const TextStyle( + fontWeight: FontWeight.bold, ), - value: day, - groupValue: state.dayNotificationValue, - onChanged: (Day? value) { - Navigator.pop(context, value); - }, ), - ) - .toList(), - ); - }), + value: day, + groupValue: selectedDay, + onChanged: (Day? value) { + Navigator.pop(context, value); + }, + ), + ) + .toList(), + ), ); } } diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index f1fbef7f..87651cc9 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -158,47 +158,44 @@ class _NotificationScreenState extends CustomWidgetState { ), leadingIcon: Icons.plus_one_outlined, ), - BlocBuilder( - builder: (context, state) { - return _CustomListTile( - onTap: () async { - final day = await showDialog( - context: context, - builder: ( - BuildContext context, - ) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: const DaysOfTheWeekDialog(), - ); - }, + _CustomListTile( + onTap: () async { + final day = await showDialog( + context: context, + builder: ( + BuildContext context, + ) { + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: DaysOfTheWeekDialog( + selectedDay: state.dayNotificationValue), ); - if (day != null) { - BlocProvider.of(context).add( - DayNotificationValueEvent(day: day), - ); - } }, - enabled: state.dayNotificationEnabled, - title: AppLocalizations.of(context)!.dayNotificationTitle( - state.dayNotificationValue.localizedName(context), - ), - subtitle: AppLocalizations.of(context)! - .dayNotificationExplanation( - state.dayNotificationValue.localizedName(context), - ), - leadingIcon: Icons.calendar_month_outlined, - onChanged: (bool value) => - BlocProvider.of(context).add( - DayNotificationStateEvent( - enabled: value, - ), - ), ); + if (day != null) { + BlocProvider.of(context).add( + DayNotificationValueEvent(day: day), + ); + } }, - ), + enabled: state.dayNotificationEnabled, + title: AppLocalizations.of(context)!.dayNotificationTitle( + state.dayNotificationValue.localizedName(context), + ), + subtitle: + AppLocalizations.of(context)!.dayNotificationExplanation( + state.dayNotificationValue.localizedName(context), + ), + leadingIcon: Icons.calendar_month_outlined, + onChanged: (bool value) => + BlocProvider.of(context).add( + DayNotificationStateEvent( + enabled: value, + ), + ), + ) ], ); }, From a4c3f5fa069aea2baee7ab6c35ef565fa0bc737e Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 23 Mar 2023 23:06:59 +0100 Subject: [PATCH 12/65] fix(notifications): add the persistence of the day picked for the day notification --- lib/bloc/notification/notification_bloc.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 5681f178..24df42a3 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -156,6 +156,10 @@ class NotificationBloc extends Bloc { storageService.readBool(Const.notificationDayEnabledKey) ?? Const.notificationDayEnabledDefaultValue; + final dayNotificationValue = + storageService.readDay(Const.notificationDayValueKey) ?? + Const.notificationDayValueDefaultValue; + final openingNotificationEnabled = storageService.readBool(Const.notificationOpeningEnabledKey) ?? Const.notificationOpeningEnabledDefaultValue; @@ -171,6 +175,7 @@ class NotificationBloc extends Bloc { timeNotificationEnabled: timeNotificationEnabled, timeNotificationValue: timeNotificationValue, dayNotificationEnabled: dayNotificationEnabled, + dayNotificationValue: dayNotificationValue, openingNotificationEnabled: openingNotificationEnabled, closingNotificationEnabled: closingNotificationEnabled), ); From 50b885778f3102bd8562d282a49ac3504606ecd9 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Fri, 24 Mar 2023 22:26:37 +0100 Subject: [PATCH 13/65] fix(forecast): the first forecast is now properly showing --- lib/widgets/chaban_bridge_forecast_list.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/widgets/chaban_bridge_forecast_list.dart b/lib/widgets/chaban_bridge_forecast_list.dart index 64652160..9760d289 100644 --- a/lib/widgets/chaban_bridge_forecast_list.dart +++ b/lib/widgets/chaban_bridge_forecast_list.dart @@ -41,10 +41,6 @@ class _ChabanBridgeForecastListState extends State { cacheExtent: 5000, padding: const EdgeInsets.all(0), itemBuilder: (BuildContext context, int index) { - if (index == 0) { - return _MonthWidget( - chabanBridgeForecast: widget.chabanBridgeForecasts[0]); - } return ChabanBridgeForecastListItem( key: GlobalObjectKey(widget.chabanBridgeForecasts[index].hashCode), From 239cb982b4735912d89c830e753d0e780ea1cf79 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Fri, 24 Mar 2023 22:27:32 +0100 Subject: [PATCH 14/65] feat(ui): better transitions between pages --- .../chaban_bridge_forecast_screen.dart | 26 +++++++++++++++---- lib/screens/settings_screen.dart | 7 ++--- lib/widgets/notification_tile_widget.dart | 21 +++++++++++++-- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 323ba633..61f97f5f 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -27,14 +27,30 @@ class _ChabanBridgeForecastScreenState return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SettingsScreen(), + Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const SettingsScreen(), + transitionsBuilder: + (context, animation, secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); + const end = Offset.zero; + const curve = Curves.ease; + + var tween = Tween(begin: begin, end: end).chain( + CurveTween( + curve: curve, + ), + ); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, ), ); }, - heroTag: 'settingsButtonIcon', child: const Icon(Icons.settings), ), body: SafeArea( diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 6db27377..95edf0a3 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -26,11 +26,8 @@ class _SettingsScreenState extends CustomWidgetState { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.primaryContainer, - leading: const Hero( - tag: 'settingsButtonIcon', - child: Icon( - Icons.settings, - ), + leading: const Icon( + Icons.settings, ), title: Text( AppLocalizations.of(context)!.settingsTitle, diff --git a/lib/widgets/notification_tile_widget.dart b/lib/widgets/notification_tile_widget.dart index 268f16ae..c1904f0c 100644 --- a/lib/widgets/notification_tile_widget.dart +++ b/lib/widgets/notification_tile_widget.dart @@ -25,8 +25,25 @@ class NotificationTileWidget extends StatelessWidget { size: 30, ), onTap: () async => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const NotificationScreen(), + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const NotificationScreen(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1.0, 0.0); + const end = Offset.zero; + const curve = Curves.ease; + + var tween = Tween(begin: begin, end: end).chain( + CurveTween( + curve: curve, + ), + ); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, ), ), ); From f75b094320bd88e4e531b458df69ee2b76741a6f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 00:17:35 +0100 Subject: [PATCH 15/65] feat(day-notifications): day notifications are know properly working --- .../chaban_bridge_forecast_bloc.dart | 4 +- lib/bloc/notification/notification_bloc.dart | 2 +- lib/bloc/notification/notification_state.dart | 2 + lib/const.dart | 10 +---- lib/dialogs/days_of_the_week_dialog.dart | 35 +---------------- lib/extensions/date_time_extension.dart | 14 +++++++ lib/l10n/app_en.arb | 15 ++++++-- lib/l10n/app_es.arb | 15 ++++++-- lib/l10n/app_fr.arb | 15 ++++++-- lib/models/enums/day.dart | 7 ++-- lib/screens/notification_screen.dart | 8 ++-- lib/service/notification_service.dart | 38 +++++++++++++++++++ lib/service/storage_service.dart | 11 +++--- 13 files changed, 109 insertions(+), 67 deletions(-) create mode 100644 lib/extensions/date_time_extension.dart diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index 238dd2a3..c7acc2d2 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -50,7 +50,9 @@ class ChabanBridgeForecastBloc } final boatForecast = ChabanBridgeBoatForecast.fromJSON(json); return boatForecast; - }).toList(); + }).toList() + ..sort((a, b) => + a.circulationClosingDate.compareTo(b.circulationClosingDate)); } return []; } diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 24df42a3..7ccc4b9b 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -1,10 +1,10 @@ import 'package:chabo/bloc/chabo_event.dart'; import 'package:chabo/const.dart'; -import 'package:chabo/models/enums/day.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; part 'notification_event.dart'; part 'notification_state.dart'; diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index 7f84a86e..f7386a9a 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -7,6 +7,8 @@ class NotificationSate { final Duration timeNotificationValue; final bool dayNotificationEnabled; final Day dayNotificationValue; + final TimeOfDay dayNotificationTimeValue = + const TimeOfDay(hour: 20, minute: 00); final bool openingNotificationEnabled; final bool closingNotificationEnabled; diff --git a/lib/const.dart b/lib/const.dart index 34f78b65..de20da38 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -1,6 +1,6 @@ -import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/link_icon.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class Const { @@ -72,13 +72,7 @@ class Const { static const String notificationTimeChannelId = 'tomorrow_closures'; static const String notificationOpeningChannelId = 'opening'; static const String notificationClosingChannelId = 'closing'; - - /// Notification misc - static const int durationNotificationStartId = 0; - static const int timeNotificationStartId = 1000; - static const int dayNotificationStartId = 2000; - static const int openingNotificationStartId = 3000; - static const int closingNotificationStartId = 4000; + static const String notificationDayChannelId = 'next_week_closures'; /// AdMod static const String androidInlineBanner = diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 8c128fdf..fd446f1b 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -1,7 +1,7 @@ import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; class DaysOfTheWeekDialog extends StatelessWidget { final Day selectedDay; @@ -23,39 +23,6 @@ class DaysOfTheWeekDialog extends StatelessWidget { 20, 10, ), - title: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular( - 15.0, - ), - topRight: Radius.circular( - 15.0, - ), - ), - ), - padding: const EdgeInsets.fromLTRB( - 20, - 20, - 0, - 15, - ), - child: Row( - children: [ - Icon(Icons.calendar_month_outlined, - color: Theme.of(context).colorScheme.onPrimaryContainer), - const SizedBox(width: 20), - Text( - AppLocalizations.of(context)!.day, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), - ], - ), - ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 15, diff --git a/lib/extensions/date_time_extension.dart b/lib/extensions/date_time_extension.dart new file mode 100644 index 00000000..0c12f24e --- /dev/null +++ b/lib/extensions/date_time_extension.dart @@ -0,0 +1,14 @@ +extension DateTimeExtension on DateTime { + DateTime previous(int day) { + if (day == weekday) { + return subtract(const Duration(days: 7)); + } else { + return subtract( + Duration( + days: (weekday - day) % DateTime.daysPerWeek, + hours: hour, + minutes: minute), + ); + } + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d0c96956..ed8ec60e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -73,10 +73,11 @@ "day": {} } }, - "dayNotificationExplanation": "Receive a notification on {day} listing all planned closures for the coming week", + "dayNotificationExplanation": "Receive a notification on {day} at {time} listing all planned closures for the coming week", "@dayNotificationExplanation": { "placeholders": { - "day": {} + "day": {}, + "time": {} } }, "closingNotificationTitle": "At closing", @@ -151,5 +152,13 @@ }, "day": "Day", "refreshingNotifications": "Refreshing your notifications", - "refreshingNotificationsDone": "Done !" + "refreshingNotificationsDone": "Done !", + "notificationDayTitle": "\uD83D\uDD2E Closing scheduled", + "notificationDayMessage": "{count, plural, =0{No closures scheduled for next week} =1{1 closure scheduled for next week} other{{count} closures scheduled for next week}}", + "@notificationDayMessage": { + "placeholders": { + "count": {} + } + }, + "notificationDayChannelName": "Planned closures" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4a0831f7..b03a1411 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -73,10 +73,11 @@ "day": {} } }, - "dayNotificationExplanation": "Reciba una notificación el {day} que enumere todos los cierres planeados para la próxima semana", + "dayNotificationExplanation": "Reciba una notificación el {day} a las {time} que enumere todos los cierres planeados para la próxima semana", "@dayNotificationExplanation": { "placeholders": { - "day": {} + "day": {}, + "time": {} } }, "closingNotificationTitle": "En el cierre", @@ -151,5 +152,13 @@ }, "day": "Día", "refreshingNotifications": "Refrescando tus notificaciones", - "refreshingNotificationsDone": "Terminado !" + "refreshingNotificationsDone": "Terminado !", + "notificationDayTitle": "\uD83D\uDD2E Cierre programado", + "notificationDayMessage": "{count, plural, =0{No hay cierres programados para la próxima semana} =1{1 cierre programado para la próxima semana} other{{count} cierres programados para la próxima semana}}", + "@notificationDayMessage": { + "placeholders": { + "count": {} + } + }, + "notificationDayChannelName": "Cierres planificados" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1a96ac99..a761645a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -73,10 +73,11 @@ "day": {} } }, - "dayNotificationExplanation": "Recevoir une notification le {day} listant toutes les fermetures prévues pour la semaine qui arrive", + "dayNotificationExplanation": "Recevoir une notification le {day} à {time} listant toutes les fermetures prévues pour la semaine qui arrive", "@dayNotificationExplanation": { "placeholders": { - "day": {} + "day": {}, + "time": {} } }, "closingNotificationTitle": "À la fermeture", @@ -151,5 +152,13 @@ }, "day": "Jour", "refreshingNotifications": "Actualisation de vos notifications", - "refreshingNotificationsDone": "Terminé !" + "refreshingNotificationsDone": "Terminé !", + "notificationDayTitle": "\uD83D\uDD2E Fermetures prévues", + "notificationDayMessage": "{count, plural, =0{Aucune fermetures de prévue pour la semaine prochaine} =1{1 fermeture prévue pour la semaine prochaine} other{{count} fermetures prévues pour la semaine prochaine}}", + "@notificationDayMessage": { + "placeholders": { + "count": {} + } + }, + "notificationDayChannelName": "Fermetures prévues" } \ No newline at end of file diff --git a/lib/models/enums/day.dart b/lib/models/enums/day.dart index 975fce1f..0e0197a4 100644 --- a/lib/models/enums/day.dart +++ b/lib/models/enums/day.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -enum Day { monday, tuesday, wednesday, thursday, friday, saturday, sunday } +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; extension DayExtension on Day? { String localizedName(BuildContext context) { @@ -20,8 +19,8 @@ extension DayExtension on Day? { return AppLocalizations.of(context)!.saturday; case Day.sunday: return AppLocalizations.of(context)!.sunday; - case null: - throw Exception('Day not defined for null'); + default: + return ''; } } } diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 87651cc9..db30da5a 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -184,10 +184,10 @@ class _NotificationScreenState extends CustomWidgetState { title: AppLocalizations.of(context)!.dayNotificationTitle( state.dayNotificationValue.localizedName(context), ), - subtitle: - AppLocalizations.of(context)!.dayNotificationExplanation( - state.dayNotificationValue.localizedName(context), - ), + subtitle: AppLocalizations.of(context)! + .dayNotificationExplanation( + state.dayNotificationValue.localizedName(context), + state.dayNotificationTimeValue.format(context)), leadingIcon: Icons.calendar_month_outlined, onChanged: (bool value) => BlocProvider.of(context).add( diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 51732f0f..c09dccef 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/const.dart'; +import 'package:chabo/extensions/date_time_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/foundation.dart'; @@ -78,6 +79,7 @@ class NotificationService { tz.initializeTimeZones(); int index = 0; await localNotifications.cancelAll(); + List weekSeparatedChabanBridgeForecast = []; for (final chabanBridgeForecast in chabanBridgeForecasts) { if (notificationSate.openingNotificationEnabled) { index += 1; @@ -94,6 +96,27 @@ class NotificationService { await _createTimeScheduledNotifications(index, chabanBridgeForecast, context, notificationSate.timeNotificationValue); } + if (notificationSate.dayNotificationEnabled) { + var last = chabanBridgeForecast.circulationClosingDate + .previous(notificationSate.dayNotificationValue.value); + if (weekSeparatedChabanBridgeForecast.isEmpty || + weekSeparatedChabanBridgeForecast.last.circulationClosingDate + .previous(notificationSate.dayNotificationValue.value) + .day == + last.day) { + weekSeparatedChabanBridgeForecast.add(chabanBridgeForecast); + } else { + index += 1; + await _createDayScheduledNotifications( + index, + weekSeparatedChabanBridgeForecast.length, + last, + notificationSate.dayNotificationTimeValue, + context, + ); + weekSeparatedChabanBridgeForecast = []; + } + } if (notificationSate.durationNotificationEnabled) { index += 1; await _createDurationScheduledNotifications( @@ -186,6 +209,21 @@ class NotificationService { notificationDetails); } + Future _createDayScheduledNotifications(int index, int closingCount, + DateTime day, TimeOfDay timeOfDay, BuildContext context) async { + final notificationScheduleTime = + day.copyWith(hour: timeOfDay.hour, minute: timeOfDay.minute, second: 0); + NotificationDetails notificationDetails = _notificationDetails( + Const.notificationDayChannelId, + AppLocalizations.of(context)!.notificationDayChannelName); + await _scheduleNotification( + index, + AppLocalizations.of(context)!.notificationDayTitle, + AppLocalizations.of(context)!.notificationDayMessage(closingCount), + notificationScheduleTime, + notificationDetails); + } + NotificationDetails _notificationDetails( String notificationChannelId, String notificationChannelName) { final AndroidNotificationDetails androidNotificationDetails = diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 54ee53c0..4237d38c 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -1,9 +1,9 @@ import 'dart:developer' as developer; -import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/enums/theme_state_status.dart'; import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -35,7 +35,7 @@ class StorageService { Future saveDay(String key, Day value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveDay'); - return await sharedPreferences.setString(key, value.name); + return await sharedPreferences.setInt(key, value.value); } Future saveTheme(String key, ThemeStateStatus value) async { @@ -80,13 +80,12 @@ class StorageService { } Day? readDay(String key) { - final stringValue = sharedPreferences.getString(key); - if (stringValue == null) { + final value = sharedPreferences.getInt(key); + if (value == null) { return null; } else { - final value = EnumToString.fromString(Day.values, stringValue); developer.log('{$key: $value}', name: 'storage-service.on.readDay'); - return value; + return Day(value); } } From 5023d5f75380aa762187e3ead0a9192e9567e572 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 00:38:58 +0100 Subject: [PATCH 16/65] fix(ui): display proper hour for time notification --- lib/bloc/notification/notification_bloc.dart | 7 ++++--- lib/bloc/notification/notification_event.dart | 2 +- lib/bloc/notification/notification_state.dart | 4 ++-- lib/const.dart | 4 ++-- lib/screens/notification_screen.dart | 13 ++++++------- lib/service/notification_service.dart | 4 ++-- lib/service/storage_service.dart | 16 ++++++++++++++++ 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 7ccc4b9b..4b817d3f 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; part 'notification_event.dart'; + part 'notification_state.dart'; class NotificationBloc extends Bloc { @@ -18,7 +19,7 @@ class NotificationBloc extends Bloc { durationNotificationEnabled: true, durationNotificationValue: const Duration(hours: 6), timeNotificationEnabled: true, - timeNotificationValue: const Duration(hours: 6), + timeNotificationValue: const TimeOfDay(hour: 6, minute: 0), dayNotificationEnabled: true, dayNotificationValue: Day.saturday, openingNotificationEnabled: true, @@ -106,7 +107,7 @@ class NotificationBloc extends Bloc { Future _onTimeNotificationValueEvent( TimeNotificationValueEvent event, Emitter emit) async { - await storageService.saveDuration( + await storageService.saveTimeOfDay( Const.notificationTimeValueKey, event.time); emit( state.copyWith(timeNotificationValue: event.time), @@ -149,7 +150,7 @@ class NotificationBloc extends Bloc { Const.notificationTimeEnabledDefaultValue; final timeNotificationValue = - storageService.readDuration(Const.notificationTimeValueKey) ?? + storageService.readTimeOfDay(Const.notificationTimeValueKey) ?? Const.notificationTimeValueDefaultValue; final dayNotificationEnabled = diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart index a6459f41..3ed5c2ce 100644 --- a/lib/bloc/notification/notification_event.dart +++ b/lib/bloc/notification/notification_event.dart @@ -45,7 +45,7 @@ class DurationNotificationValueEvent extends NotificationEvent { } class TimeNotificationValueEvent extends NotificationEvent { - final Duration time; + final TimeOfDay time; TimeNotificationValueEvent({required this.time}) : super(); } diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index f7386a9a..94a631bf 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -4,7 +4,7 @@ class NotificationSate { final bool durationNotificationEnabled; final Duration durationNotificationValue; final bool timeNotificationEnabled; - final Duration timeNotificationValue; + final TimeOfDay timeNotificationValue; final bool dayNotificationEnabled; final Day dayNotificationValue; final TimeOfDay dayNotificationTimeValue = @@ -26,7 +26,7 @@ class NotificationSate { {bool? durationNotificationEnabled, Duration? durationNotificationValue, bool? timeNotificationEnabled, - Duration? timeNotificationValue, + TimeOfDay? timeNotificationValue, bool? dayNotificationEnabled, Day? dayNotificationValue, bool? openingNotificationEnabled, diff --git a/lib/const.dart b/lib/const.dart index de20da38..2f7414a9 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -58,8 +58,8 @@ class Const { static const Duration notificationDurationValueDefaultValue = Duration(minutes: 60); static const bool notificationDurationEnabledDefaultValue = true; - static const Duration notificationTimeValueDefaultValue = - Duration(hours: 20, minutes: 00); + static TimeOfDay notificationTimeValueDefaultValue = + const TimeOfDay(hour: 6, minute: 0); static const bool notificationTimeEnabledDefaultValue = false; static const Day notificationDayValueDefaultValue = Day.sunday; static const bool notificationDayEnabledDefaultValue = false; diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index db30da5a..2c0ab860 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -119,8 +119,7 @@ class _NotificationScreenState extends CustomWidgetState { var time = await showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state - .durationToTimeOfDay(state.timeNotificationValue), + initialTime: state.timeNotificationValue, builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context).copyWith( @@ -134,9 +133,9 @@ class _NotificationScreenState extends CustomWidgetState { // ignore: use_build_context_synchronously BlocProvider.of(context).add( TimeNotificationValueEvent( - time: Duration( - hours: time.hour, - minutes: time.minute, + time: TimeOfDay( + hour: time.hour, + minute: time.minute, ), ), ); @@ -150,11 +149,11 @@ class _NotificationScreenState extends CustomWidgetState { ), enabled: state.timeNotificationEnabled, title: AppLocalizations.of(context)!.timeNotificationTitle( - state.durationToString(state.timeNotificationValue), + state.timeNotificationValue.format(context), ), subtitle: AppLocalizations.of(context)!.timeNotificationExplanation( - state.durationToString(state.timeNotificationValue), + state.timeNotificationValue.format(context), ), leadingIcon: Icons.plus_one_outlined, ), diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index c09dccef..c4a74665 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -170,14 +170,14 @@ class NotificationService { int index, AbstractChabanBridgeForecast chabanBridgeForecast, BuildContext context, - Duration value) async { + TimeOfDay value) async { final notificationScheduleTime = chabanBridgeForecast.circulationClosingDate .subtract( const Duration( days: 1, ), ) - .copyWith(hour: value.inHours, minute: value.inMinutes % 60); + .copyWith(hour: value.hour, minute: value.minute); NotificationDetails notificationDetails = _notificationDetails( Const.notificationTimeChannelId, AppLocalizations.of(context)!.notificationTimeChannelName); diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 4237d38c..3e5cc119 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -27,6 +27,11 @@ class StorageService { return await sharedPreferences.setString(key, value.inMinutes.toString()); } + Future saveDateTime(String key, DateTime value) async { + developer.log('{$key: $value}', name: 'storage-service.on.saveDuration'); + return await sharedPreferences.setString(key, value.toString()); + } + Future saveTimeOfDay(String key, TimeOfDay value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveTimeOfDay'); return await sharedPreferences.setString( @@ -55,6 +60,17 @@ class StorageService { return value; } + DateTime? reaDateTime(String key) { + final stringValue = sharedPreferences.getString(key); + if (stringValue == null) { + return null; + } else { + final value = DateTime.parse(sharedPreferences.getString(key)!); + developer.log('{$key: $value}', name: 'storage-service.on.readDuration'); + return value; + } + } + Duration? readDuration(String key) { final stringValue = sharedPreferences.getString(key); if (stringValue == null) { From 85b617c6e888a10cb39d0787741d793e70e95ae6 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:27:19 +0100 Subject: [PATCH 17/65] fix(ui): add Sizebox between description and disclaimer --- lib/dialogs/chabo_about_dialog.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart index a2d1f0f6..ac116a55 100644 --- a/lib/dialogs/chabo_about_dialog.dart +++ b/lib/dialogs/chabo_about_dialog.dart @@ -96,6 +96,9 @@ class ChaboAboutDialog extends StatelessWidget { AppLocalizations.of(context)!.appDescription, style: Theme.of(context).textTheme.bodyLarge, ), + const SizedBox( + height: 15, + ), Text( AppLocalizations.of(context)!.disclaimer, style: Theme.of(context) From 35f9832c379171994348e15ae51f2bd0a1dcb615 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:32:32 +0100 Subject: [PATCH 18/65] chore(extension): use a common extension to display a Duration as String --- lib/bloc/notification/notification_state.dart | 13 ------------- lib/extensions/duration_extension.dart | 16 ++++++++++++++++ lib/models/abstract_chaban_bridge_forecast.dart | 8 -------- lib/models/chaban_bridge_boat_forecast.dart | 9 +++++---- .../chaban_bridge_maintenance_forecast.dart | 9 +++++---- lib/screens/notification_screen.dart | 9 +++++---- lib/service/notification_service.dart | 4 +--- .../chaban_bridge_forecast_list_item.dart | 3 ++- 8 files changed, 34 insertions(+), 37 deletions(-) create mode 100644 lib/extensions/duration_extension.dart diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index 94a631bf..b31ac4b4 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -48,17 +48,4 @@ class NotificationSate { closingNotificationEnabled: closingNotificationEnabled ?? this.closingNotificationEnabled); } - - TimeOfDay durationToTimeOfDay(Duration duration) { - return TimeOfDay(hour: duration.inHours, minute: duration.inMinutes % 60); - } - - String durationToString(Duration duration) { - if (duration.inMinutes % 60 == 0) { - return '${duration.inHours.toString()}h'; - } else if (duration.inHours == 0) { - return '${duration.inMinutes.toString()}mins'; - } - return '${duration.inHours.toString()}h ${(duration.inMinutes % 60).toString()}mins'; - } } diff --git a/lib/extensions/duration_extension.dart b/lib/extensions/duration_extension.dart new file mode 100644 index 00000000..70f268ad --- /dev/null +++ b/lib/extensions/duration_extension.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +extension DurationExtention on Duration { + String durationToString() { + if (inMinutes % 60 == 0) { + return '${inHours.toString()}h'; + } else if (inHours == 0) { + return '${inMinutes.toString()}mins'; + } + return '${inHours.toString()}h ${(inMinutes % 60).toString()}mins'; + } + + TimeOfDay durationToTimeOfDay() { + return TimeOfDay(hour: inHours, minute: inMinutes % 60); + } +} diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index 8972993a..bb1d1fdc 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -73,14 +73,6 @@ abstract class AbstractChabanBridgeForecast extends Equatable { .format(circulationReOpeningDate); } - String durationString() { - if (duration.inMinutes.remainder(60) == 0) { - return '${duration.inHours}h'; - } else { - return '${duration.inHours}h ${duration.inMinutes.remainder(60)}mins'; - } - } - bool isCurrentlyClosed() { var now = DateTime.now(); return now.isAfter(circulationClosingDate) && diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index bc2e2a5b..4e250621 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -1,5 +1,6 @@ import 'package:chabo/extensions/boats_extensions.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/boat.dart'; @@ -141,7 +142,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { text: '\n\n${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : '), TextSpan( - text: '${durationString()}\n', + text: '${duration.durationToString()}\n', style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.timeColor, @@ -168,7 +169,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { return AppLocalizations.of(context)!.notificationDurationBoatMessage( boats.toLocalizedString(context), pickedDuration, - durationString(), + duration.durationToString(), ); } @@ -177,7 +178,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { return AppLocalizations.of(context)!.notificationTimeBoatMessage( boats.toLocalizedString(context), DateFormat.Hm().format(circulationClosingDate), - durationString(), + duration.durationToString(), ); } @@ -185,7 +186,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { String getNotificationClosingMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationClosingBoatMessage( boats.toLocalizedString(context), - durationString(), + duration.durationToString(), ); } diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index f70d0609..1a53fe60 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -1,4 +1,5 @@ import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/enums/chaban_bridge_forecast_closing_reason.dart'; @@ -57,7 +58,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { BuildContext context, String pickedDuration) { return AppLocalizations.of(context)!.notificationDurationMaintenanceMessage( pickedDuration, - durationString(), + duration.durationToString(), ); } @@ -65,14 +66,14 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { String getNotificationTimeMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationTimeMaintenanceMessage( DateFormat.Hm().format(circulationClosingDate), - durationString(), + duration.durationToString(), ); } @override String getNotificationClosingMessage(BuildContext context) { return AppLocalizations.of(context)! - .notificationClosingMaintenanceMessage(durationString()); + .notificationClosingMaintenanceMessage(duration.durationToString()); } @override @@ -143,7 +144,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { '${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : ', ), TextSpan( - text: durationString(), + text: duration.durationToString(), style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.timeColor, diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 2c0ab860..9202ae26 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -4,6 +4,7 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -74,8 +75,8 @@ class _NotificationScreenState extends CustomWidgetState { var time = await showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state - .durationToTimeOfDay(state.durationNotificationValue), + initialTime: + state.durationNotificationValue.durationToTimeOfDay(), builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context).copyWith( @@ -106,11 +107,11 @@ class _NotificationScreenState extends CustomWidgetState { enabled: state.durationNotificationEnabled, title: AppLocalizations.of(context)!.durationNotificationTitle( - state.durationToString(state.durationNotificationValue), + state.durationNotificationValue.durationToString(), ), subtitle: AppLocalizations.of(context)! .durationNotificationExplanation( - state.durationToString(state.durationNotificationValue), + state.durationNotificationValue.durationToString(), ), leadingIcon: Icons.timer_outlined, ), diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index c4a74665..6781688f 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -124,9 +124,7 @@ class NotificationService { chabanBridgeForecast, context, notificationSate.durationNotificationValue, - notificationSate.durationToString( - notificationSate.durationNotificationValue, - ), + notificationSate.durationNotificationValue.durationToString(), ); } } diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/chaban_bridge_forecast_list_item.dart index 235c14bb..46df7bc7 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/chaban_bridge_forecast_list_item.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/dialogs/chaban_bridge_forecast_information_dialog.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -116,7 +117,7 @@ class ChabanBridgeForecastListItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - chabanBridgeForecast.durationString(), + chabanBridgeForecast.duration.durationToString(), style: TextStyle( color: Theme.of(context).colorScheme.timeColor, fontWeight: FontWeight.bold, From 9691b73f06309f664b2933c8fe199e563a736b4f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:37:40 +0100 Subject: [PATCH 19/65] chore(day-enum): use a custom implementation of Day --- lib/dialogs/days_of_the_week_dialog.dart | 1 - lib/models/enums/day.dart | 26 ++++++++++++++++-- lib/service/notification_service.dart | 7 +++-- lib/service/storage_service.dart | 11 ++++---- lib/simple_bloc_observer.dart | 34 ++++++++++++------------ 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index fd446f1b..96294153 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -1,7 +1,6 @@ import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; class DaysOfTheWeekDialog extends StatelessWidget { final Day selectedDay; diff --git a/lib/models/enums/day.dart b/lib/models/enums/day.dart index 0e0197a4..89f6ff68 100644 --- a/lib/models/enums/day.dart +++ b/lib/models/enums/day.dart @@ -1,8 +1,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +enum Day { monday, tuesday, wednesday, thursday, friday, saturday, sunday } extension DayExtension on Day? { + int get weekPosition { + switch (this) { + case Day.monday: + return 1; + case Day.tuesday: + return 2; + case Day.wednesday: + return 3; + case Day.thursday: + return 4; + case Day.friday: + return 5; + case Day.saturday: + return 6; + case Day.sunday: + return 7; + default: + return -1; + } + } + String localizedName(BuildContext context) { switch (this) { case Day.monday: @@ -20,7 +42,7 @@ extension DayExtension on Day? { case Day.sunday: return AppLocalizations.of(context)!.sunday; default: - return ''; + return 'no_date'; } } } diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 6781688f..2b6de495 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -4,7 +4,9 @@ import 'dart:io'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/const.dart'; import 'package:chabo/extensions/date_time_extension.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:chabo/models/enums/day.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -98,10 +100,11 @@ class NotificationService { } if (notificationSate.dayNotificationEnabled) { var last = chabanBridgeForecast.circulationClosingDate - .previous(notificationSate.dayNotificationValue.value); + .previous(notificationSate.dayNotificationValue.weekPosition); if (weekSeparatedChabanBridgeForecast.isEmpty || weekSeparatedChabanBridgeForecast.last.circulationClosingDate - .previous(notificationSate.dayNotificationValue.value) + .previous( + notificationSate.dayNotificationValue.weekPosition) .day == last.day) { weekSeparatedChabanBridgeForecast.add(chabanBridgeForecast); diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 3e5cc119..223d8610 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -1,9 +1,9 @@ import 'dart:developer' as developer; +import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/enums/theme_state_status.dart'; import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -40,7 +40,7 @@ class StorageService { Future saveDay(String key, Day value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveDay'); - return await sharedPreferences.setInt(key, value.value); + return await sharedPreferences.setString(key, value.name); } Future saveTheme(String key, ThemeStateStatus value) async { @@ -96,12 +96,13 @@ class StorageService { } Day? readDay(String key) { - final value = sharedPreferences.getInt(key); - if (value == null) { + final stringValue = sharedPreferences.getString(key); + if (stringValue == null) { return null; } else { + final value = EnumToString.fromString(Day.values, stringValue); developer.log('{$key: $value}', name: 'storage-service.on.readDay'); - return Day(value); + return value; } } diff --git a/lib/simple_bloc_observer.dart b/lib/simple_bloc_observer.dart index 6092e927..fe8198a1 100644 --- a/lib/simple_bloc_observer.dart +++ b/lib/simple_bloc_observer.dart @@ -1,17 +1,17 @@ -import 'dart:developer' as developer; - -import 'package:flutter_bloc/flutter_bloc.dart'; - -class SimpleBlocObserver extends BlocObserver { - @override - void onTransition(Bloc bloc, Transition transition) { - super.onTransition(bloc, transition); - developer.log(transition.toString(), name: 'bloc.on.transition'); - } - - @override - void onError(BlocBase bloc, Object error, StackTrace stackTrace) { - super.onError(bloc, error, stackTrace); - developer.log(stackTrace.toString(), name: 'bloc.on.error'); - } -} +import 'dart:developer' as developer; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SimpleBlocObserver extends BlocObserver { + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + developer.log(transition.toString(), name: 'bloc.on.transition'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + super.onError(bloc, error, stackTrace); + developer.log(stackTrace.toString(), name: 'bloc.on.error'); + } +} From b091a036da3a4212729ee58b9936a77a0affbddc Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:38:21 +0100 Subject: [PATCH 20/65] chore(const-values): correctly use all const values into the Notification state construction --- lib/bloc/notification/notification_bloc.dart | 25 ++++++++++++------- lib/bloc/notification/notification_state.dart | 7 ++++-- lib/const.dart | 4 ++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 4b817d3f..30ccb307 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -1,10 +1,10 @@ import 'package:chabo/bloc/chabo_event.dart'; import 'package:chabo/const.dart'; +import 'package:chabo/models/enums/day.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; part 'notification_event.dart'; @@ -16,14 +16,21 @@ class NotificationBloc extends Bloc { NotificationBloc({required this.storageService}) : super( NotificationSate( - durationNotificationEnabled: true, - durationNotificationValue: const Duration(hours: 6), - timeNotificationEnabled: true, - timeNotificationValue: const TimeOfDay(hour: 6, minute: 0), - dayNotificationEnabled: true, - dayNotificationValue: Day.saturday, - openingNotificationEnabled: true, - closingNotificationEnabled: true), + durationNotificationEnabled: + Const.notificationDurationEnabledDefaultValue, + durationNotificationValue: + Const.notificationDurationValueDefaultValue, + timeNotificationEnabled: + Const.notificationTimeEnabledDefaultValue, + timeNotificationValue: Const.notificationTimeValueDefaultValue, + dayNotificationEnabled: Const.notificationDayEnabledDefaultValue, + dayNotificationValue: Const.notificationDayValueDefaultValue, + dayNotificationTimeValue: + Const.notificationDayValueDefaultTimeValue, + openingNotificationEnabled: + Const.notificationOpeningEnabledDefaultValue, + closingNotificationEnabled: + Const.notificationClosingEnabledDefaultValue), ) { on( _onOpeningNotificationStateEvent, diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index b31ac4b4..3ff54583 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -7,8 +7,7 @@ class NotificationSate { final TimeOfDay timeNotificationValue; final bool dayNotificationEnabled; final Day dayNotificationValue; - final TimeOfDay dayNotificationTimeValue = - const TimeOfDay(hour: 20, minute: 00); + final TimeOfDay dayNotificationTimeValue; final bool openingNotificationEnabled; final bool closingNotificationEnabled; @@ -19,6 +18,7 @@ class NotificationSate { required this.timeNotificationValue, required this.dayNotificationEnabled, required this.dayNotificationValue, + required this.dayNotificationTimeValue, required this.openingNotificationEnabled, required this.closingNotificationEnabled}); @@ -29,6 +29,7 @@ class NotificationSate { TimeOfDay? timeNotificationValue, bool? dayNotificationEnabled, Day? dayNotificationValue, + TimeOfDay? dayNotificationTimeValue, bool? openingNotificationEnabled, bool? closingNotificationEnabled}) { return NotificationSate( @@ -43,6 +44,8 @@ class NotificationSate { dayNotificationEnabled: dayNotificationEnabled ?? this.dayNotificationEnabled, dayNotificationValue: dayNotificationValue ?? this.dayNotificationValue, + dayNotificationTimeValue: + dayNotificationTimeValue ?? this.dayNotificationTimeValue, openingNotificationEnabled: openingNotificationEnabled ?? this.openingNotificationEnabled, closingNotificationEnabled: diff --git a/lib/const.dart b/lib/const.dart index 2f7414a9..124a0d5a 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -1,6 +1,6 @@ +import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/link_icon.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class Const { @@ -62,6 +62,8 @@ class Const { const TimeOfDay(hour: 6, minute: 0); static const bool notificationTimeEnabledDefaultValue = false; static const Day notificationDayValueDefaultValue = Day.sunday; + static TimeOfDay notificationDayValueDefaultTimeValue = + const TimeOfDay(hour: 20, minute: 00); static const bool notificationDayEnabledDefaultValue = false; static const bool notificationOpeningEnabledDefaultValue = false; static const bool notificationClosingEnabledDefaultValue = false; From 5f45827ad19c595e5ef624d5be32db54c1ee4fbe Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:48:17 +0100 Subject: [PATCH 21/65] feat(ui): display the remaining time for the next closing schedule more visible --- lib/models/chaban_bridge_status.dart | 2 +- lib/widgets/chaban_bridge_status_widget.dart | 169 ++++++++++--------- 2 files changed, 94 insertions(+), 77 deletions(-) diff --git a/lib/models/chaban_bridge_status.dart b/lib/models/chaban_bridge_status.dart index ab294870..2fd806f5 100644 --- a/lib/models/chaban_bridge_status.dart +++ b/lib/models/chaban_bridge_status.dart @@ -32,7 +32,7 @@ class ChabanBridgeStatus { AppLocalizations.of(context)!.nextClosingScheduled.capitalize(); currentStatusShort = AppLocalizations.of(context)!.open; } - nextStatusMessagePrefix += ' : '; + nextStatusMessagePrefix += ' '; remainingTime = _formatRemainingTime( differenceStartingPoint.inDays, differenceStartingPoint.inHours.remainder(24), diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index a2487e8f..52798a50 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -36,16 +36,16 @@ class ChabanBridgeStatusWidgetState @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 10, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 10, + ), + child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: widget.bridgeStatus.getBackgroundColor(context), @@ -69,79 +69,96 @@ class ChabanBridgeStatusWidgetState ], ), ), - const SizedBox( - height: 20, - ), - Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text( - widget.bridgeStatus.nextStatusMessagePrefix, - style: const TextStyle( - fontSize: 20, + ), + const SizedBox( + height: 20, + ), + Container( + color: Theme.of(context).colorScheme.inversePrimary, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + widget.bridgeStatus.nextStatusMessagePrefix, + style: const TextStyle( + fontSize: 20, + ), ), - ), - Text( - widget.bridgeStatus.remainingTime, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, + Text( + widget.bridgeStatus.remainingTime, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), - ), - ], + ], + ), ), - Flexible( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.showCurrentStatus - ? ChabanBridgeForecastListItem( - onTap: () => - BlocProvider.of(context).add( - GoTo( - goTo: widget - .bridgeStatus.currentChabanBridgeForecast, + ), + const SizedBox( + height: 20, + ), + Flexible( + child: BlocBuilder( + builder: (context, state) { + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 800), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.showCurrentStatus + ? Column( + children: [ + ChabanBridgeForecastListItem( + onTap: () => + BlocProvider.of(context) + .add( + GoTo( + goTo: widget + .bridgeStatus.currentChabanBridgeForecast, + ), ), + hasPassed: false, + isCurrent: true, + chabanBridgeForecast: widget + .bridgeStatus.currentChabanBridgeForecast, + index: -1, ), - hasPassed: false, - isCurrent: true, - chabanBridgeForecast: - widget.bridgeStatus.currentChabanBridgeForecast, - index: -1, - ) - : const SizedBox.shrink(), - ), - ); - }, - ), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)!.lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, + const SizedBox( + height: 20, + ), + ], + ) + : const SizedBox.shrink(), ), - ), - const Icon(Icons.arrow_circle_down), - ], + ); + }, ), - ], - ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + AppLocalizations.of(context)!.lisOfUpcomingClosures, + style: const TextStyle( + fontSize: 20, + ), + ), + const Icon(Icons.arrow_circle_down), + ], + ), + ], ); } } From b496c0ddca61e21d269386d7fc40b7e0d6f4e7f1 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 10:51:56 +0100 Subject: [PATCH 22/65] feat(ui): add more Padding --- lib/widgets/chaban_bridge_status_widget.dart | 60 ++++++++++---------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index 52798a50..9f8ec0a5 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -118,27 +118,26 @@ class ChabanBridgeStatusWidgetState ); }, child: state.showCurrentStatus - ? Column( - children: [ - ChabanBridgeForecastListItem( - onTap: () => - BlocProvider.of(context) - .add( - GoTo( - goTo: widget - .bridgeStatus.currentChabanBridgeForecast, - ), + ? Padding( + padding: const EdgeInsets.only( + left: 10.0, + right: 10.0, + bottom: 15.0, + ), + child: ChabanBridgeForecastListItem( + onTap: () => + BlocProvider.of(context).add( + GoTo( + goTo: widget + .bridgeStatus.currentChabanBridgeForecast, ), - hasPassed: false, - isCurrent: true, - chabanBridgeForecast: widget - .bridgeStatus.currentChabanBridgeForecast, - index: -1, ), - const SizedBox( - height: 20, - ), - ], + hasPassed: false, + isCurrent: true, + chabanBridgeForecast: + widget.bridgeStatus.currentChabanBridgeForecast, + index: -1, + ), ) : const SizedBox.shrink(), ), @@ -146,17 +145,20 @@ class ChabanBridgeStatusWidgetState }, ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)!.lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + AppLocalizations.of(context)!.lisOfUpcomingClosures, + style: const TextStyle( + fontSize: 20, + ), ), - ), - const Icon(Icons.arrow_circle_down), - ], + const Icon(Icons.arrow_circle_down), + ], + ), ), ], ); From d37d4e84e514c6b610e8c141c9c049dd8944be87 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 11:10:53 +0100 Subject: [PATCH 23/65] fix(api): set a lower limit to fetch the api --- .../chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index c7acc2d2..eb80eb43 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -11,7 +11,7 @@ import 'package:http/http.dart' as http; part 'chaban_bridge_forecast_event.dart'; part 'chaban_bridge_forecast_state.dart'; -const _chabanBridgeForecastLimit = 10000; +const _chabanBridgeForecastLimit = 1000; const throttleDuration = Duration(milliseconds: 1000); class ChabanBridgeForecastBloc From df9213e3da0fa9e204e2e96dea1256224c8cd770 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 25 Mar 2023 11:23:22 +0100 Subject: [PATCH 24/65] chore(version): increment version to `v1.1.0` --- CHANGELOG_en.md | 12 ++++++++++++ CHANGELOG_es.md | 12 ++++++++++++ CHANGELOG_fr.md | 11 +++++++++++ README.md | 2 +- pubspec.yaml | 2 +- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index fe551d83..35e5029e 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,15 @@ +# **v1.1.0** : + +- *Fixed*: + - The first closing is now displayed correctly +- *Interface*: + - Rework of the "About" window + - Complete rework of the notification management window + - Some improvements for readability +- *Features*: + - Recap notifications are now functional +*** + # **v1.0.0** : First stable release - *Features*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 25d34e1d..1770af24 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,15 @@ +# **v1.1.0** : + +- *Fijado*: + - El primer cierre ahora se muestra correctamente +- *Interfaz*: + - Reelaboración de la ventana "Acerca de" + - Reelaboración completa de la ventana de gestión de notificaciones. + - Algunas mejoras para la legibilidad. +- *Características*: + - Las notificaciones de resumen ahora son funcionales +*** + # **v1.0.0** : Primera versión estable - *Características*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 8424d39b..5d280a6e 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,14 @@ +# **v1.1.0** : + +- *Fix*: + - La première fermeture s'affiche maintenant correctement +- *Interface*: + - Retravail de la fenêtre "A propos" + - Retravail complet de la fenêtre de gestion des notifications + - Quelques améliorations pour la lisibilité +- *Fonctionnalités*: + - Les notifications de recap sont maintenant fonctionnelles +*** # **v1.0.0** : Première version stable - *Fonctionnalités*: diff --git a/README.md b/README.md index e887e55b..a866f235 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

diff --git a/pubspec.yaml b/pubspec.yaml index 33a12b35..57bad34b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.0.0 +version: 1.1.0 environment: sdk: '>=2.17.6 <3.0.0' From 8413797822bb116b8c3466cf4fdf8988cc7a1936 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Mar 2023 17:07:06 +0000 Subject: [PATCH 25/65] chore(deps): update ruby/setup-ruby to v1.144.2 --- .github/workflows/fastlane.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index f4023f8f..41ba9dfe 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -34,7 +34,7 @@ jobs: - name: 'Generate changelog' run: ./.github/scripts/generate_changelog.sh - name: 'Setup Ruby' - uses: ruby/setup-ruby@v1.144.1 + uses: ruby/setup-ruby@v1.144.2 with: ruby-version: '3.0' bundler-cache: true From 622e99477c8501044677c7c0a0dacd26eaf1d8d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Mar 2023 21:40:35 +0000 Subject: [PATCH 26/65] chore(deps): update actions/checkout to v3.5.0 --- .github/workflows/fastlane.action.yaml | 2 +- .github/workflows/flutter.build.action.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 41ba9dfe..470fb0d0 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.4.0 + uses: actions/checkout@v3.5.0 with: fetch-depth: 0 - name: 'Decrypt secret configuration' diff --git a/.github/workflows/flutter.build.action.yaml b/.github/workflows/flutter.build.action.yaml index ef9ed0a0..285826ca 100644 --- a/.github/workflows/flutter.build.action.yaml +++ b/.github/workflows/flutter.build.action.yaml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.4.0 + uses: actions/checkout@v3.5.0 with: fetch-depth: 0 - name: 'Decrypt secret configuration' From 5faea66d7ab5b93c90e061976f05a6efd05135ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 01:16:16 +0000 Subject: [PATCH 27/65] chore(deps): update subosito/flutter-action to v2.10.0 --- .github/workflows/flutter.analyze-test.action.yaml | 6 +++--- .github/workflows/flutter.build.action.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index cbfa445b..da0ead3d 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -21,7 +21,7 @@ jobs: - name: 'Checkout source code' uses: actions/checkout@v3 - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.10.0 with: flutter-version: ${{ inputs.flutter_version }} - name: 'Flutter analyze' @@ -33,7 +33,7 @@ jobs: - name: 'Checkout source code' uses: actions/checkout@v3 - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.10.0 with: flutter-version: ${{ inputs.flutter_version }} - name: 'Flutter format' @@ -51,7 +51,7 @@ jobs: - name: 'Check secret configuration' run: ./.github/scripts/check_secrets_decryption.sh - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.10.0 with: flutter-version: ${{ inputs.flutter_version }} - name: 'Flutter test (with coverage)' diff --git a/.github/workflows/flutter.build.action.yaml b/.github/workflows/flutter.build.action.yaml index 285826ca..5d432630 100644 --- a/.github/workflows/flutter.build.action.yaml +++ b/.github/workflows/flutter.build.action.yaml @@ -38,7 +38,7 @@ jobs: distribution: 'zulu' java-version: '11.x' - name: 'Setup Flutter' - uses: subosito/flutter-action@v2.8.0 + uses: subosito/flutter-action@v2.10.0 with: flutter-version: ${{ inputs.flutter_version }} - name: 'Build Android APK' From 18c3362603efd97b4883ba40a4ba73b5bf6dcf10 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 26 Mar 2023 14:50:03 +0200 Subject: [PATCH 28/65] fix(notification-icon): notification icon is now properly showing --- android/app/src/main/AndroidManifest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 86fae75b..c71fca2b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -24,6 +24,9 @@ + Date: Sun, 26 Mar 2023 14:52:12 +0200 Subject: [PATCH 29/65] chore(script): add `.ps1` version of the decrypt script --- .github/scripts/decrypt_secret.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/scripts/decrypt_secret.ps1 diff --git a/.github/scripts/decrypt_secret.ps1 b/.github/scripts/decrypt_secret.ps1 new file mode 100644 index 00000000..a4bd55d4 --- /dev/null +++ b/.github/scripts/decrypt_secret.ps1 @@ -0,0 +1,13 @@ +Set-Item -Path Env:PASSPHRASE -Value "MY_SECRET" +# Decrypt keystore.jks.gpg +& gpg --batch --yes --decrypt --pinentry-mode loopback --passphrase="$env:PASSPHRASE" --output "android/app/keystore.jks" "encrypted_config/keystore.jks.gpg" +# Decrypt fastlane-key.json.gpg +& gpg --batch --yes --decrypt --pinentry-mode loopback --passphrase="$env:PASSPHRASE" --output "android/fastlane/fastlane-key.json" "encrypted_config/fastlane-key.json.gpg" +# Decrypt key_password.txt.gpg +$tmp_key_password = (& gpg --quiet --batch --yes --decrypt --passphrase="$env:PASSPHRASE" "encrypted_config/key_password.txt.gpg") -join "`n" + +# Inject +echo "storePassword=$tmp_key_password" > "android/key.properties" +echo "keyPassword=$tmp_key_password" >> "android/key.properties" +echo "keyAlias=upload" >> "android/key.properties" +echo "storeFile=./keystore.jks" >> "android/key.properties" From f7c7bf6cb1bb9ceefb09bff4be60cc7f82408014 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 2 Apr 2023 16:23:00 +0200 Subject: [PATCH 30/65] fix(typo): change key translation `selectExample` to `selectAboutDialog` --- lib/dialogs/chabo_about_dialog.dart | 6 +++--- lib/l10n/app_en.arb | 4 ++-- lib/l10n/app_es.arb | 4 ++-- lib/l10n/app_fr.arb | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart index ac116a55..b4e0f450 100644 --- a/lib/dialogs/chabo_about_dialog.dart +++ b/lib/dialogs/chabo_about_dialog.dart @@ -131,7 +131,7 @@ class ChaboAboutDialog extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( - AppLocalizations.of(context)!.selectExample( + AppLocalizations.of(context)!.selectAboutDialog( link.translationKey, ), ), @@ -179,7 +179,7 @@ class ChaboAboutDialog extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( - AppLocalizations.of(context)!.selectExample( + AppLocalizations.of(context)!.selectAboutDialog( 'changelog', ), ), @@ -218,7 +218,7 @@ class ChaboAboutDialog extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( AppLocalizations.of(context)! - .selectExample('licenses'), + .selectAboutDialog('licenses'), ), ), ], diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ed8ec60e..b3a9f840 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -144,8 +144,8 @@ }, "notificationTimeChannelName": "Next day closings", "passedClosure": "Past closure", - "selectExample": "{choice, select, source_code {Source\ncode} privacy_policy {Privacy\npolicy} yuhliet_instagram {Yuhliet's\nInstagram} city_of_bordeaux {City of\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licenses} changelog {Changelog} other {Undefined}}", - "@selectExample": { + "selectAboutDialog": "{choice, select, source_code {Source\ncode} privacy_policy {Privacy\npolicy} yuhliet_instagram {Yuhliet's\nInstagram} city_of_bordeaux {City of\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licenses} changelog {Changelog} other {Undefined}}", + "@selectAboutDialog": { "placeholders": { "choice": {} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b03a1411..33e84c7c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -144,8 +144,8 @@ }, "notificationTimeChannelName": "Cierres del próximo día", "passedClosure": "Cierre pasado", - "selectExample": "{choice, select, source_code {Código\nuente} privacy_policy {Política de\nprivacidad} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ciudad de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licencias} changelog {Registro de cambios} other {Undefined}}", - "@selectExample": { + "selectAboutDialog": "{choice, select, source_code {Código\nfuente} privacy_policy {Política de\nprivacidad} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ciudad de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} licenses {Licencias} changelog {Registro de cambios} other {Undefined}}", + "@selectAboutDialog": { "placeholders": { "choice": {} } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a761645a..ec1c8d52 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -144,8 +144,8 @@ }, "notificationTimeChannelName": "Fermetures du lendemain", "passedClosure": "Fermeture passée", - "selectExample": "{choice, select, source_code {Code\nsource} privacy_policy {Politique de\nconfidentialité} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ville de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} changelog {Journal des\nmodifications} licenses {Licences} other {Undefined}}", - "@selectExample": { + "selectAboutDialog": "{choice, select, source_code {Code\nsource} privacy_policy {Politique de\nconfidentialité} yuhliet_instagram {Instagram de\nYuhliet's} city_of_bordeaux {Ville de\nBordeaux} bordeaux_open_data {Bordeaux\nOpen Data} changelog {Journal des\nmodifications} licenses {Licences} other {Undefined}}", + "@selectAboutDialog": { "placeholders": { "choice": {} } From bfe302252d2950d5d36729100aadeff64a4c9178 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 2 Apr 2023 16:23:31 +0200 Subject: [PATCH 31/65] feat(ui): rework of the settings section --- lib/bloc/floating_actions_cubit.dart | 9 + lib/bloc/notification/notification_bloc.dart | 1 - lib/chabo.dart | 8 + lib/models/enums/theme_state_status.dart | 31 +++ .../chaban_bridge_forecast_screen.dart | 31 +-- lib/screens/notification_screen.dart | 15 ++ lib/widgets/floating_actions_widget.dart | 231 ++++++++++++++++++ lib/widgets/theme_switcher_widget.dart | 88 +++++++ pubspec.lock | 8 + pubspec.yaml | 1 + 10 files changed, 393 insertions(+), 30 deletions(-) create mode 100644 lib/bloc/floating_actions_cubit.dart create mode 100644 lib/widgets/floating_actions_widget.dart create mode 100644 lib/widgets/theme_switcher_widget.dart diff --git a/lib/bloc/floating_actions_cubit.dart b/lib/bloc/floating_actions_cubit.dart new file mode 100644 index 00000000..758f14c7 --- /dev/null +++ b/lib/bloc/floating_actions_cubit.dart @@ -0,0 +1,9 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +class FloatingActionsCubit extends Cubit { + FloatingActionsCubit(super.initialState); + + void openActions() { + emit(!state); + } +} diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 30ccb307..ee0c98ff 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'notification_event.dart'; - part 'notification_state.dart'; class NotificationBloc extends Bloc { diff --git a/lib/chabo.dart b/lib/chabo.dart index a64cb038..83337226 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; @@ -42,6 +43,13 @@ class Chabo extends StatelessWidget { ), ), + /// Bloc intended to manage the FloatingActions + BlocProvider( + create: (_) => FloatingActionsCubit( + false, + ), + ), + /// Bloc intended to manage the forecast displayed BlocProvider( create: (_) => ChabanBridgeForecastBloc( diff --git a/lib/models/enums/theme_state_status.dart b/lib/models/enums/theme_state_status.dart index 89916fe3..98c028f7 100644 --- a/lib/models/enums/theme_state_status.dart +++ b/lib/models/enums/theme_state_status.dart @@ -1 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + enum ThemeStateStatus { light, dark, system } + +extension ThemeStateStatusExtension on ThemeStateStatus? { + IconData get icon { + switch (this) { + case ThemeStateStatus.light: + return Icons.brightness_5_rounded; + case ThemeStateStatus.dark: + return Icons.dark_mode_rounded; + case ThemeStateStatus.system: + return Icons.devices_rounded; + default: + return Icons.error; + } + } + + String text(BuildContext context) { + switch (this) { + case ThemeStateStatus.light: + return AppLocalizations.of(context)!.lightTheme; + case ThemeStateStatus.dark: + return AppLocalizations.of(context)!.darkTheme; + case ThemeStateStatus.system: + return AppLocalizations.of(context)!.systemTheme; + default: + return 'no_value'; + } + } +} diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 61f97f5f..ca9192ac 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -4,9 +4,9 @@ import 'package:chabo/bloc/notification_service_cubit.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/models/chaban_bridge_status.dart'; import 'package:chabo/screens/error_screen.dart'; -import 'package:chabo/screens/settings_screen.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; +import 'package:chabo/widgets/floating_actions_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -25,34 +25,7 @@ class _ChabanBridgeForecastScreenState @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.of(context).push( - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const SettingsScreen(), - transitionsBuilder: - (context, animation, secondaryAnimation, child) { - const begin = Offset(0.0, 1.0); - const end = Offset.zero; - const curve = Curves.ease; - - var tween = Tween(begin: begin, end: end).chain( - CurveTween( - curve: curve, - ), - ); - - return SlideTransition( - position: animation.drive(tween), - child: child, - ); - }, - ), - ); - }, - child: const Icon(Icons.settings), - ), + floatingActionButton: const FloatingActions(), body: SafeArea( child: BlocBuilder( builder: (context, state) { diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 9202ae26..c8f7aba7 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -21,6 +21,21 @@ class _NotificationScreenState extends CustomWidgetState { @override Widget build(BuildContext context) { return Scaffold( + floatingActionButton: FloatingActionButton.extended( + heroTag: null, + onPressed: () { + Navigator.of(context).pop(); + }, + label: Wrap( + spacing: 10, + children: [ + Text( + MaterialLocalizations.of(context).closeButtonLabel, + ), + const Icon(Icons.close), + ], + ), + ), appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.primaryContainer, leading: const Icon(Icons.notifications_active_outlined), diff --git a/lib/widgets/floating_actions_widget.dart b/lib/widgets/floating_actions_widget.dart new file mode 100644 index 00000000..7abd1655 --- /dev/null +++ b/lib/widgets/floating_actions_widget.dart @@ -0,0 +1,231 @@ +import 'dart:ui'; + +import 'package:chabo/bloc/floating_actions_cubit.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/dialogs/chabo_about_dialog.dart'; +import 'package:chabo/screens/notification_screen.dart'; +import 'package:chabo/widgets/theme_switcher_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class FloatingActions extends StatefulWidget { + const FloatingActions({Key? key}) : super(key: key); + + @override + State createState() { + return _FloatingActionsState(); + } +} + +class _FloatingActionsState extends State + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + AnimatedSwitcher( + duration: const Duration( + milliseconds: 200, + ), + reverseDuration: const Duration( + milliseconds: 200, + ), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: + CurvedAnimation(parent: animation, curve: Curves.easeIn), + child: SlideTransition( + position: Tween( + begin: const Offset(0.0, 1.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: child, + ), + ); + }, + child: state + ? BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), + child: Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.end, + spacing: 10, + children: [ + FloatingActionButton.extended( + heroTag: null, + onPressed: () { + showModalBottomSheet( + useSafeArea: true, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(25.0), + ), + ), + builder: (context) { + return const TheSwitcherWidget(); + }, + ); + context + .read() + .openActions(); + }, + label: Wrap( + spacing: 10, + children: [ + Text( + AppLocalizations.of(context)!.themeSetting, + ), + const Icon(Icons.format_paint_rounded), + ], + ), + ), + FloatingActionButton.extended( + heroTag: null, + onPressed: () async { + Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: + (context, animation1, animation2) => + const NotificationScreen(), + transitionsBuilder: (context, animation, + secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); + const end = Offset.zero; + const curve = Curves.ease; + + var tween = + Tween(begin: begin, end: end).chain( + CurveTween( + curve: curve, + ), + ); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + ), + ); + context + .read() + .openActions(); + }, + label: Wrap( + spacing: 10, + children: [ + Text( + AppLocalizations.of(context)! + .notificationsTitle, + ), + const Icon( + Icons.notifications_active_outlined, + ), + ], + ), + ), + FloatingActionButton.extended( + heroTag: null, + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return ChaboAboutDialog(); + }, + ); + context + .read() + .openActions(); + }, + label: Wrap( + spacing: 10, + children: [ + Text( + AppLocalizations.of(context)!.about, + ), + const Icon( + Icons.info_outline, + ), + ], + ), + ), + ], + ), + ) + : const SizedBox.shrink(), + ), + const SizedBox( + height: 25, + ), + FloatingActionButton.extended( + heroTag: null, + onPressed: () { + HapticFeedback.lightImpact(); + context.read().openActions(); + }, + label: Wrap( + alignment: WrapAlignment.center, + children: [ + AnimatedSize( + curve: Curves.easeIn, + duration: const Duration(milliseconds: 200), + reverseDuration: const Duration( + milliseconds: 200, + ), + child: AnimatedSwitcher( + duration: const Duration( + milliseconds: 200, + ), + reverseDuration: const Duration( + milliseconds: 200, + ), + transitionBuilder: + (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0.5, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: FadeTransition( + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: child), + ); + }, + child: state + ? Text( + AppLocalizations.of(context)!.settingsTitle, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.start, + ) + : const SizedBox.shrink(), + ), + ), + state + ? const Icon( + Icons.close, + ) + : const Icon( + Icons.settings, + ), + ], + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/widgets/theme_switcher_widget.dart b/lib/widgets/theme_switcher_widget.dart new file mode 100644 index 00000000..f904b1bf --- /dev/null +++ b/lib/widgets/theme_switcher_widget.dart @@ -0,0 +1,88 @@ +import 'package:animated_toggle_switch/animated_toggle_switch.dart'; +import 'package:chabo/bloc/theme/theme_bloc.dart'; +import 'package:chabo/models/enums/theme_state_status.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class TheSwitcherWidget extends StatelessWidget { + const TheSwitcherWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.center, + runAlignment: WrapAlignment.center, + spacing: 15, + children: [ + Text( + AppLocalizations.of(context)!.themeSettingSubtitle, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + AnimatedToggleSwitch.size( + current: state.status, + values: const [ + ThemeStateStatus.light, + ThemeStateStatus.dark, + ThemeStateStatus.system + ], + indicatorColor: Theme.of(context).colorScheme.tertiary, + innerColor: Theme.of(context).colorScheme.primaryContainer, + borderColor: Theme.of(context).colorScheme.primary, + indicatorSize: const Size.fromWidth(65), + iconBuilder: (value, size) { + return Icon( + value.icon, + color: state.status == value + ? Theme.of(context).colorScheme.onTertiary + : Theme.of(context).colorScheme.onPrimaryContainer, + ); + }, + onChanged: (value) => BlocProvider.of(context).add( + ThemeChanged( + status: value, + ), + ), + ), + AnimatedSwitcher( + duration: const Duration( + milliseconds: 200, + ), + reverseDuration: const Duration( + milliseconds: 200, + ), + transitionBuilder: (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, 1.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: FadeTransition( + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: child), + ); + }, + child: Text( + key: ValueKey( + state.status.text(context), + ), + state.status.text(context), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9b643d68..7546f1b6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + animated_toggle_switch: + dependency: "direct main" + description: + name: animated_toggle_switch + sha256: "3c6d98dcd53cb93253a9418b888b3fcd9452a55fe1a7a390bd48c0a410a41634" + url: "https://pub.dev" + source: hosted + version: "0.6.2" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 57bad34b..936c3ab5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ environment: flutter: '3.7.7' dependencies: + animated_toggle_switch: ^0.6.2 bloc_concurrency: ^0.2.0 cupertino_icons: ^1.0.2 equatable: ^2.0.3 From 828256c2b12779d8f6857e4da449c74ac418fddd Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 2 Apr 2023 16:28:23 +0200 Subject: [PATCH 32/65] fix(ui): set back the backdrop filter for the `about` dialog --- lib/widgets/floating_actions_widget.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/widgets/floating_actions_widget.dart b/lib/widgets/floating_actions_widget.dart index 7abd1655..d1ee135d 100644 --- a/lib/widgets/floating_actions_widget.dart +++ b/lib/widgets/floating_actions_widget.dart @@ -140,7 +140,13 @@ class _FloatingActionsState extends State showDialog( context: context, builder: (BuildContext context) { - return ChaboAboutDialog(); + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), + child: ChaboAboutDialog(), + ); }, ); context From e73b17b607d76de224bc02ea3a76b48513fbc631 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 2 Apr 2023 16:33:22 +0200 Subject: [PATCH 33/65] fix(ui): app icon has now a white background on licenses view --- lib/dialogs/chabo_about_dialog.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart index b4e0f450..ac2fac02 100644 --- a/lib/dialogs/chabo_about_dialog.dart +++ b/lib/dialogs/chabo_about_dialog.dart @@ -200,7 +200,23 @@ class ChaboAboutDialog extends StatelessWidget { applicationName: snapshot.data!.appName, applicationVersion: 'v${snapshot.data!.version}+${snapshot.data!.buildNumber}', - applicationIcon: _iconWidget, + applicationIcon: Padding( + padding: const EdgeInsets.all(8.0), + child: IconTheme( + data: Theme.of(context).iconTheme, + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + ), + child: _iconWidget, + ), + ), + ), applicationLegalese: Const.legalLease, ); }, From b4b5f97ce3698d65c47a614b2179a2b19124f30a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:29:13 +0000 Subject: [PATCH 34/65] chore(deps): update com.android.tools:desugar_jdk_libs to v2.0.3 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 86d3904c..2ea8d1b1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -79,6 +79,6 @@ dependencies { implementation 'androidx.window:window:1.0.0' // https://github.com/flutter/flutter/issues/110658 implementation 'androidx.window:window-java:1.0.0' // https://github.com/flutter/flutter/issues/110658 - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' // https://pub.dev/packages/flutter_local_notifications/ + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' // https://pub.dev/packages/flutter_local_notifications/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } From 56f5c325ff3219c1820e8f994de51f1ca19a6fb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:27:53 +0000 Subject: [PATCH 35/65] chore(deps): update ruby/setup-ruby to v1.145.0 --- .github/workflows/fastlane.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 470fb0d0..c56b288d 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -34,7 +34,7 @@ jobs: - name: 'Generate changelog' run: ./.github/scripts/generate_changelog.sh - name: 'Setup Ruby' - uses: ruby/setup-ruby@v1.144.2 + uses: ruby/setup-ruby@v1.145.0 with: ruby-version: '3.0' bundler-cache: true From 907612cf21fe069088ad5990a78dca1df243cf2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 03:09:35 +0000 Subject: [PATCH 36/65] chore(deps): update kotlin_version to v1.8.20 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index bf34a5d9..6b10a38f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.10' + ext.kotlin_version = '1.8.20' repositories { google() mavenCentral() From 64def750961f1d1df1bad4085d5c9f2b8cfc5001 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:17:14 +0000 Subject: [PATCH 37/65] chore(deps): update flutter_launcher_icons to ^0.13.0 --- pubspec.lock | 10 +++++----- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 7546f1b6..d07d37c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: cli_util - sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.4.0" clock: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "02dcaf49d405f652b7160e882bacfc02cb497041bb2eab2a49b1c393cf9aac12" + sha256: "8546a9b9510e1a260b8d55fb2d07096e8a8552c6a2c2bf529100344894b2b41a" url: "https://pub.dev" source: hosted - version: "0.12.0" + version: "0.13.0" flutter_lints: dependency: "direct dev" description: @@ -731,5 +731,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.7" diff --git a/pubspec.yaml b/pubspec.yaml index 936c3ab5..524f2e87 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - flutter_launcher_icons: ^0.12.0 + flutter_launcher_icons: ^0.13.0 flutter: generate: true From fce1cbc42e979f617b772ccd495d65b428b0050d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:20:41 +0000 Subject: [PATCH 38/65] chore(deps): update ruby/setup-ruby to v1.146.0 --- .github/workflows/fastlane.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index c56b288d..8efcd223 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -34,7 +34,7 @@ jobs: - name: 'Generate changelog' run: ./.github/scripts/generate_changelog.sh - name: 'Setup Ruby' - uses: ruby/setup-ruby@v1.145.0 + uses: ruby/setup-ruby@v1.146.0 with: ruby-version: '3.0' bundler-cache: true From 26e6e54ef9eaf0d93bae9778a3fec553af8a0988 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Wed, 12 Apr 2023 21:43:38 +0200 Subject: [PATCH 39/65] WIP --- .github/scripts/decrypt_secret.ps1 | 10 +- .../chaban_bridge_forecast_bloc.dart | 85 +++++- .../chaban_bridge_forecast_event.dart | 4 + .../chaban_bridge_forecast_state.dart | 92 +++++- .../scroll_status/scroll_status_bloc.dart | 4 + lib/extensions/duration_extension.dart | 14 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/main.dart | 2 +- .../abstract_chaban_bridge_forecast.dart | 6 +- lib/models/chaban_bridge_boat_forecast.dart | 12 +- .../chaban_bridge_maintenance_forecast.dart | 12 +- lib/models/chaban_bridge_status.dart | 22 +- .../chaban_bridge_forecast_screen.dart | 22 +- lib/screens/notification_screen.dart | 4 +- lib/service/notification_service.dart | 2 +- .../chaban_bridge_forecast_list_item.dart | 2 +- lib/widgets/chaban_bridge_status_widget.dart | 262 ++++++++++-------- .../ephemeral/Flutter-Generated.xcconfig | 8 +- .../ephemeral/flutter_export_environment.sh | 8 +- 21 files changed, 399 insertions(+), 184 deletions(-) diff --git a/.github/scripts/decrypt_secret.ps1 b/.github/scripts/decrypt_secret.ps1 index a4bd55d4..091ca27a 100644 --- a/.github/scripts/decrypt_secret.ps1 +++ b/.github/scripts/decrypt_secret.ps1 @@ -1,4 +1,4 @@ -Set-Item -Path Env:PASSPHRASE -Value "MY_SECRET" +#Set-Item -Path Env:PASSPHRASE -Value "MY_SECRET" # Decrypt keystore.jks.gpg & gpg --batch --yes --decrypt --pinentry-mode loopback --passphrase="$env:PASSPHRASE" --output "android/app/keystore.jks" "encrypted_config/keystore.jks.gpg" # Decrypt fastlane-key.json.gpg @@ -7,7 +7,7 @@ Set-Item -Path Env:PASSPHRASE -Value "MY_SECRET" $tmp_key_password = (& gpg --quiet --batch --yes --decrypt --passphrase="$env:PASSPHRASE" "encrypted_config/key_password.txt.gpg") -join "`n" # Inject -echo "storePassword=$tmp_key_password" > "android/key.properties" -echo "keyPassword=$tmp_key_password" >> "android/key.properties" -echo "keyAlias=upload" >> "android/key.properties" -echo "storeFile=./keystore.jks" >> "android/key.properties" +"storePassword=$tmp_key_password" > "android/key.properties" +"keyPassword=$tmp_key_password" >> "android/key.properties" +"keyAlias=upload" >> "android/key.properties" +"storeFile=./keystore.jks" >> "android/key.properties" diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index eb80eb43..b69f19c7 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -1,14 +1,20 @@ import 'dart:convert'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/chaban_bridge_boat_forecast.dart'; import 'package:chabo/models/chaban_bridge_maintenance_forecast.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; part 'chaban_bridge_forecast_event.dart'; + part 'chaban_bridge_forecast_state.dart'; const _chabanBridgeForecastLimit = 1000; @@ -23,6 +29,9 @@ class ChabanBridgeForecastBloc on( _onChabanBridgeForecastFetched, ); + on( + _onChabanBridgeForecastRefreshCurrentStatus, + ); } Future> _fetchChabanBridgeForecasts( @@ -83,6 +92,17 @@ class ChabanBridgeForecastBloc } } + AbstractChabanBridgeForecast? _setPreviousStatus( + List chabanBridgeForecasts, + AbstractChabanBridgeForecast currentStatus) { + if (chabanBridgeForecasts.indexOf(currentStatus) == 0) { + return null; + } else { + return chabanBridgeForecasts + .elementAt(chabanBridgeForecasts.indexOf(currentStatus) - 1); + } + } + Future _onChabanBridgeForecastFetched(ChabanBridgeForecastFetched event, Emitter emit) async { if (state.hasReachedMax) return; @@ -90,11 +110,13 @@ class ChabanBridgeForecastBloc if (state.status == ChabanBridgeForecastStatus.initial) { final chabanBridgeForecasts = await _fetchChabanBridgeForecasts(state.offset); + final currentStatus = _setCurrentStatus(chabanBridgeForecasts); emit(state.copyWith( status: ChabanBridgeForecastStatus.success, chabanBridgeForecasts: chabanBridgeForecasts, - currentChabanBridgeForecast: - _setCurrentStatus(chabanBridgeForecasts), + currentChabanBridgeForecast: currentStatus, + previousChabanBridgeForecast: + _setPreviousStatus(chabanBridgeForecasts, currentStatus), hasReachedMax: false, offset: state.offset + _chabanBridgeForecastLimit)); } @@ -105,6 +127,10 @@ class ChabanBridgeForecastBloc : state.copyWith( currentChabanBridgeForecast: state.currentChabanBridgeForecast ?? _setCurrentStatus(chabanBridgeForecasts), + previousChabanBridgeForecast: + state.previousChabanBridgeForecast ?? + _setPreviousStatus(chabanBridgeForecasts, + _setCurrentStatus(chabanBridgeForecasts)), status: ChabanBridgeForecastStatus.success, chabanBridgeForecasts: List.of(state.chabanBridgeForecasts) ..addAll(chabanBridgeForecasts), @@ -115,4 +141,59 @@ class ChabanBridgeForecastBloc status: ChabanBridgeForecastStatus.failure, message: _.toString())); } } + + Future _onChabanBridgeForecastRefreshCurrentStatus( + ChabanBridgeForecastRefreshCurrentStatus event, + Emitter emit) async { + try { + if (state.status == ChabanBridgeForecastStatus.success) { + final currentStatus = _setCurrentStatus(state.chabanBridgeForecasts); + emit( + state.copyWith( + currentChabanBridgeForecast: currentStatus, + durationUntilNextEvent: _getDurationForNextEvent(), + durationBetweenPreviousAndNextEvent: _getDurationBetweenPreviousAndNextEvent(), + previousChabanBridgeForecast: + _setPreviousStatus(state.chabanBridgeForecasts, currentStatus), + ), + ); + } + } catch (_) { + emit(state.copyWith( + status: ChabanBridgeForecastStatus.failure, message: _.toString())); + } + } + + Duration? _getDurationForNextEvent() { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + final DateTime now = DateTime.now(); + if (currentChabanBridgeForecast != null) { + if (currentChabanBridgeForecast.isCurrentlyClosed()) { + return currentChabanBridgeForecast.circulationReOpeningDate + .difference(now); + } else { + return currentChabanBridgeForecast.circulationClosingDate + .difference(now); + } + } else { + return null; + } + } + + Duration? _getDurationBetweenPreviousAndNextEvent() { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + final previousChabanBridgeForecast = state.previousChabanBridgeForecast; + if (currentChabanBridgeForecast != null && + previousChabanBridgeForecast != null) { + if (currentChabanBridgeForecast.isCurrentlyClosed()) { + return currentChabanBridgeForecast.closedDuration; + } + else { + return currentChabanBridgeForecast.circulationClosingDate + .difference(previousChabanBridgeForecast.circulationReOpeningDate); + } + } else { + return null; + } + } } diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart index 34ae8233..76ddc8d4 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart @@ -3,3 +3,7 @@ part of 'chaban_bridge_forecast_bloc.dart'; abstract class ChabanBridgeForecastEvent extends ChaboEvent {} class ChabanBridgeForecastFetched extends ChabanBridgeForecastEvent {} + +class ChabanBridgeForecastRefreshCurrentStatus extends ChabanBridgeForecastEvent {} + + diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart index 58b60cb7..9f24e841 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart @@ -6,6 +6,9 @@ class ChabanBridgeForecastState extends Equatable { final ChabanBridgeForecastStatus status; final List chabanBridgeForecasts; final AbstractChabanBridgeForecast? currentChabanBridgeForecast; + final AbstractChabanBridgeForecast? previousChabanBridgeForecast; + final Duration? durationUntilNextEvent; + final Duration? durationBetweenPreviousAndNextEvent; final bool hasReachedMax; final int offset; final String message; @@ -14,6 +17,9 @@ class ChabanBridgeForecastState extends Equatable { {this.status = ChabanBridgeForecastStatus.initial, this.chabanBridgeForecasts = const [], this.currentChabanBridgeForecast, + this.durationUntilNextEvent, + this.previousChabanBridgeForecast, + this.durationBetweenPreviousAndNextEvent, this.hasReachedMax = false, this.offset = 0, this.message = 'OK'}); @@ -22,26 +28,108 @@ class ChabanBridgeForecastState extends Equatable { {ChabanBridgeForecastStatus? status, List? chabanBridgeForecasts, AbstractChabanBridgeForecast? currentChabanBridgeForecast, + AbstractChabanBridgeForecast? previousChabanBridgeForecast, + Duration? durationUntilNextEvent, + Duration? durationBetweenPreviousAndNextEvent, bool? hasReachedMax, int? offset, String? message}) { return ChabanBridgeForecastState( status: status ?? this.status, + durationUntilNextEvent: + durationUntilNextEvent ?? this.durationUntilNextEvent, + durationBetweenPreviousAndNextEvent: + durationBetweenPreviousAndNextEvent ?? + this.durationBetweenPreviousAndNextEvent, chabanBridgeForecasts: chabanBridgeForecasts ?? this.chabanBridgeForecasts, currentChabanBridgeForecast: currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, + previousChabanBridgeForecast: + previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, hasReachedMax: hasReachedMax ?? this.hasReachedMax, offset: offset ?? this.offset, message: message ?? this.message); } + Color getBackgroundColor(BuildContext context) { + final isOpen = !currentChabanBridgeForecast!.isCurrentlyClosed(); + final differenceStartingPoint = currentChabanBridgeForecast! + .circulationReOpeningDate + .difference(DateTime.now()); + if (isOpen && differenceStartingPoint.inMinutes <= 120) { + return Theme.of(context).colorScheme.tertiaryContainer; + } else if (isOpen) { + return Colors.green; + } else { + return Theme.of(context).colorScheme.errorContainer; + } + } + + Color getForegroundColor(BuildContext context) { + final isOpen = !currentChabanBridgeForecast!.isCurrentlyClosed(); + final differenceStartingPoint = currentChabanBridgeForecast! + .circulationReOpeningDate + .difference(DateTime.now()); + if (isOpen && differenceStartingPoint.inMinutes <= 120) { + return Theme.of(context).colorScheme.onTertiaryContainer; + } else if (isOpen) { + return Theme.of(context).colorScheme.background; + } else { + return Theme.of(context).colorScheme.onErrorContainer; + } + } + + String nextStatusMessagePrefix(BuildContext context) { + if (currentChabanBridgeForecast!.isCurrentlyClosed()) { + return '${AppLocalizations.of(context)!.scheduledToOpen.capitalize()} '; + } else { + return '${AppLocalizations.of(context)!.nextClosingScheduled.capitalize()} '; + } + } + + String getCurrentStatus(BuildContext context) { + if (currentChabanBridgeForecast!.isCurrentlyClosed()) { + return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; + } else { + return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently}'; + } + } + + String getCurrentStatusShort(BuildContext context) { + if (currentChabanBridgeForecast!.isCurrentlyClosed()) { + return AppLocalizations.of(context)!.closed; + } else { + return AppLocalizations.of(context)!.open; + } + } + + String _getGreetings(BuildContext context) { + final now = DateTime.now(); + int hours = int.parse(DateFormat('HH').format(now)); + if (hours >= 6 && hours <= 12) { + return AppLocalizations.of(context)!.goodMorning.capitalize(); + } else if (hours > 12 && hours <= 18) { + return AppLocalizations.of(context)!.goodAfternoon.capitalize(); + } else { + return AppLocalizations.of(context)!.goodEvening.capitalize(); + } + } + @override String toString() { return 'ChabanBridgeForecastState{status: $status, chabanBridgeForecasts: $chabanBridgeForecasts, currentChabanBridgeForecast: $currentChabanBridgeForecast, hasReachedMax: $hasReachedMax, offset: $offset, message: $message}'; } @override - List get props => - [status, chabanBridgeForecasts, hasReachedMax, offset, message]; + List get props => [ + status, + chabanBridgeForecasts, + hasReachedMax, + offset, + message, + currentChabanBridgeForecast ?? Object(), + previousChabanBridgeForecast ?? Object(), + durationUntilNextEvent ?? Object() + ]; } diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index 7af2b12a..0d3e2a3c 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -23,6 +23,10 @@ class ScrollStatusBloc extends Bloc { ); } + + + + Future _onScrollChanged( ScrollStatusChanged event, Emitter emit) async { emit( diff --git a/lib/extensions/duration_extension.dart b/lib/extensions/duration_extension.dart index 70f268ad..a630fd2b 100644 --- a/lib/extensions/duration_extension.dart +++ b/lib/extensions/duration_extension.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; extension DurationExtention on Duration { - String durationToString() { - if (inMinutes % 60 == 0) { - return '${inHours.toString()}h'; - } else if (inHours == 0) { - return '${inMinutes.toString()}mins'; - } - return '${inHours.toString()}h ${(inMinutes % 60).toString()}mins'; + String durationToString(BuildContext context) { + final dayToken = inDays == 0 ? '' : '${inDays.toString()}${AppLocalizations.of(context)!.daySmall} '; + final hourToken = inHours.remainder(24) == 0 ? '' : '${inHours.remainder(24).toString()}h '; + final minToken = inMinutes.remainder(60) == 0 ? '' : '${inMinutes.remainder(60).toString()}m '; + final secsToken = inSeconds.remainder(60) == 0 ? '' : '${inSeconds.remainder(60).toString()}s '; + return '$dayToken$hourToken$minToken$secsToken'; } TimeOfDay durationToTimeOfDay() { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b3a9f840..6412dc08 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -43,13 +43,13 @@ "systemTheme": "System theme", "notifications": "Notifications", "notificationsSubtitle": "Manage the notifications of the app", - "durationNotificationTitle": "{duration} before", + "durationNotificationTitle": "{duration}before", "@durationNotificationTitle": { "placeholders": { "duration": {} } }, - "durationNotificationExplanation": "Receive a notification {duration} before the next closing", + "durationNotificationExplanation": "Receive a notification {duration}before the next closing", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 33e84c7c..38767e5b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -43,13 +43,13 @@ "systemTheme": "Tema del sistema", "notifications": "Notificaciones", "notificationsSubtitle": "Administre las notificaciones de la aplicación", - "durationNotificationTitle": "{duration} antes", + "durationNotificationTitle": "{duration}antes", "@durationNotificationTitle": { "placeholders": { "duration": {} } }, - "durationNotificationExplanation": "Reciba una notificación {duration} antes del próximo cierre", + "durationNotificationExplanation": "Reciba una notificación {duration}antes del próximo cierre", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ec1c8d52..66ea41ba 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -43,13 +43,13 @@ "systemTheme": "Thème système", "notifications": "Notifications", "notificationsSubtitle": "Gérer les notifications de l'application", - "durationNotificationTitle": "{duration} avant", + "durationNotificationTitle": "{duration}avant", "@durationNotificationTitle": { "placeholders": { "duration": {} } }, - "durationNotificationExplanation": "Recevoir une notification {duration} avant la prochaine fermeture", + "durationNotificationExplanation": "Recevoir une notification {duration}avant la prochaine fermeture", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/main.dart b/lib/main.dart index d322d42b..ce277661 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,7 @@ void main() async { ); MobileAds.instance.initialize(); - Bloc.observer = SimpleBlocObserver(); + //Bloc.observer = SimpleBlocObserver(); runApp( Chabo( diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index bb1d1fdc..5b7d36fc 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -7,7 +7,7 @@ import 'package:intl/intl.dart'; abstract class AbstractChabanBridgeForecast extends Equatable { final bool totalClosing; final ChabanBridgeForecastClosingReason closingReason; - late final Duration duration; + late final Duration closedDuration; late final DateTime _circulationClosingDate; late final DateTime _circulationReOpeningDate; final ChabanBridgeForecastClosingType closingType; @@ -31,7 +31,7 @@ abstract class AbstractChabanBridgeForecast extends Equatable { tmpCirculationReOpeningDate.difference(_circulationClosingDate); } _circulationReOpeningDate = tmpCirculationReOpeningDate; - duration = tmpDuration; + closedDuration = tmpDuration; } DateTime get circulationReOpeningDate => _circulationReOpeningDate.toLocal(); @@ -102,7 +102,7 @@ abstract class AbstractChabanBridgeForecast extends Equatable { List get props => [ totalClosing, closingReason, - duration, + closedDuration, circulationClosingDate, circulationReOpeningDate, closingType, diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index 4e250621..02b95c5e 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -70,7 +70,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { List get props => [ totalClosing, closingReason, - duration, + closedDuration, boats, circulationClosingDate, circulationReOpeningDate, @@ -80,7 +80,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { @override Widget getInformationWidget(BuildContext context) { var schedule = circulationClosingDate - .add(Duration(microseconds: duration.inMicroseconds ~/ 2)); + .add(Duration(microseconds: closedDuration.inMicroseconds ~/ 2)); var scheduleString = DateFormat.jm(Localizations.localeOf(context).languageCode) .format(schedule); @@ -142,7 +142,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { text: '\n\n${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : '), TextSpan( - text: '${duration.durationToString()}\n', + text: '${closedDuration.durationToString(context)}\n', style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.timeColor, @@ -169,7 +169,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { return AppLocalizations.of(context)!.notificationDurationBoatMessage( boats.toLocalizedString(context), pickedDuration, - duration.durationToString(), + closedDuration.durationToString(context), ); } @@ -178,7 +178,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { return AppLocalizations.of(context)!.notificationTimeBoatMessage( boats.toLocalizedString(context), DateFormat.Hm().format(circulationClosingDate), - duration.durationToString(), + closedDuration.durationToString(context), ); } @@ -186,7 +186,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { String getNotificationClosingMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationClosingBoatMessage( boats.toLocalizedString(context), - duration.durationToString(), + closedDuration.durationToString(context), ); } diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index 1a53fe60..d4f5ae5e 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -47,7 +47,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { List get props => [ totalClosing, closingReason, - duration, + closedDuration, circulationClosingDate, circulationReOpeningDate, closingType @@ -58,7 +58,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { BuildContext context, String pickedDuration) { return AppLocalizations.of(context)!.notificationDurationMaintenanceMessage( pickedDuration, - duration.durationToString(), + closedDuration.durationToString(context), ); } @@ -66,14 +66,14 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { String getNotificationTimeMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationTimeMaintenanceMessage( DateFormat.Hm().format(circulationClosingDate), - duration.durationToString(), + closedDuration.durationToString(context), ); } @override String getNotificationClosingMessage(BuildContext context) { - return AppLocalizations.of(context)! - .notificationClosingMaintenanceMessage(duration.durationToString()); + return AppLocalizations.of(context)!.notificationClosingMaintenanceMessage( + closedDuration.durationToString(context)); } @override @@ -144,7 +144,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { '${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : ', ), TextSpan( - text: duration.durationToString(), + text: closedDuration.durationToString(context), style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.timeColor, diff --git a/lib/models/chaban_bridge_status.dart b/lib/models/chaban_bridge_status.dart index 2fd806f5..df4db9b8 100644 --- a/lib/models/chaban_bridge_status.dart +++ b/lib/models/chaban_bridge_status.dart @@ -6,16 +6,17 @@ import 'package:intl/intl.dart'; class ChabanBridgeStatus { final AbstractChabanBridgeForecast currentChabanBridgeForecast; + final AbstractChabanBridgeForecast? previousChabanBridgeForecast; final DateTime now = DateTime.now(); late String currentStatus; late String currentStatusShort; late String nextStatusMessagePrefix; late Duration differenceStartingPoint; - late String remainingTime; late bool isOpen; ChabanBridgeStatus( {required this.currentChabanBridgeForecast, + required this.previousChabanBridgeForecast, required BuildContext context}) { if (currentChabanBridgeForecast.isCurrentlyClosed()) { differenceStartingPoint = @@ -33,30 +34,11 @@ class ChabanBridgeStatus { currentStatusShort = AppLocalizations.of(context)!.open; } nextStatusMessagePrefix += ' '; - remainingTime = _formatRemainingTime( - differenceStartingPoint.inDays, - differenceStartingPoint.inHours.remainder(24), - differenceStartingPoint.inMinutes.remainder(60), - context); currentStatus = '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently}'; isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); } - String _formatRemainingTime( - int days, int hours, int mins, BuildContext context) { - String minSuffix = mins > 1 ? 'mins' : 'min'; - if (days == 0) { - return '${hours}h $mins$minSuffix'; - } else if (hours == 0) { - return '$days${AppLocalizations.of(context)!.daySmall} $mins$minSuffix'; - } else if (mins == 0) { - return '$days${AppLocalizations.of(context)!.daySmall} ${hours}h'; - } else { - return '$days${AppLocalizations.of(context)!.daySmall} ${hours}h $mins$minSuffix'; - } - } - Color getBackgroundColor(BuildContext context) { if (isOpen && differenceStartingPoint.inMinutes <= 120) { return Theme.of(context).colorScheme.tertiaryContainer; diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index ca9192ac..f47a3290 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; @@ -22,6 +24,17 @@ class ChabanBridgeForecastScreen extends StatefulWidget { class _ChabanBridgeForecastScreenState extends CustomWidgetState { + @override + void initState() { + Timer.periodic( + const Duration(seconds: 1), + (Timer t) => BlocProvider.of(context).add( + ChabanBridgeForecastRefreshCurrentStatus(), + ), + ); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -37,9 +50,12 @@ class _ChabanBridgeForecastScreenState return const ErrorScreen(errorMessage: 'Empty return'); } var bridgeStatus = ChabanBridgeStatus( - currentChabanBridgeForecast: - state.currentChabanBridgeForecast!, - context: context); + currentChabanBridgeForecast: + state.currentChabanBridgeForecast!, + previousChabanBridgeForecast: + state.previousChabanBridgeForecast, + context: context, + ); return MultiBlocListener( listeners: [ diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index c8f7aba7..94bc7db5 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -122,11 +122,11 @@ class _NotificationScreenState extends CustomWidgetState { enabled: state.durationNotificationEnabled, title: AppLocalizations.of(context)!.durationNotificationTitle( - state.durationNotificationValue.durationToString(), + state.durationNotificationValue.durationToString(context), ), subtitle: AppLocalizations.of(context)! .durationNotificationExplanation( - state.durationNotificationValue.durationToString(), + state.durationNotificationValue.durationToString(context), ), leadingIcon: Icons.timer_outlined, ), diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 2b6de495..d517c19b 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -127,7 +127,7 @@ class NotificationService { chabanBridgeForecast, context, notificationSate.durationNotificationValue, - notificationSate.durationNotificationValue.durationToString(), + notificationSate.durationNotificationValue.durationToString(context), ); } } diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/chaban_bridge_forecast_list_item.dart index 46df7bc7..b58e25e0 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/chaban_bridge_forecast_list_item.dart @@ -117,7 +117,7 @@ class ChabanBridgeForecastListItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - chabanBridgeForecast.duration.durationToString(), + chabanBridgeForecast.closedDuration.durationToString(context), style: TextStyle( color: Theme.of(context).colorScheme.timeColor, fontWeight: FontWeight.bold, diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index 9f8ec0a5..06d3741e 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -1,6 +1,10 @@ +import 'dart:async'; + +import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/models/chaban_bridge_status.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; import 'package:flutter/material.dart'; @@ -34,133 +38,169 @@ class ChabanBridgeStatusWidgetState super.initState(); } + double _getDiffPercentage(ChabanBridgeForecastState state) { + if (state.durationBetweenPreviousAndNextEvent != null && + state.durationUntilNextEvent != null) { + return 1 - + (state.durationUntilNextEvent!.inSeconds / + state.durationBetweenPreviousAndNextEvent!.inSeconds); + } else { + return 1; + } + } + @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 10, - ), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: widget.bridgeStatus.getBackgroundColor(context), - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 10, + ), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: widget.bridgeStatus.getBackgroundColor(context), + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), ), ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${widget.bridgeStatus.currentStatus} ${widget.bridgeStatus.currentStatusShort}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 40, + color: widget.bridgeStatus.getForegroundColor(context), + ), + ), + ], + ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${widget.bridgeStatus.currentStatus} ${widget.bridgeStatus.currentStatusShort}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 40, - color: widget.bridgeStatus.getForegroundColor(context), + ), + const SizedBox( + height: 20, + ), + Container( + color: Theme.of(context).colorScheme.inversePrimary, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Column( + children: [ + Text( + widget.bridgeStatus.nextStatusMessagePrefix, + style: const TextStyle( + fontSize: 20, + ), ), - ), - ], + Text( + state.durationUntilNextEvent == null + ? '' + : state.durationUntilNextEvent! + .durationToString(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 5), + child: SizedBox( + height: 10, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + child: LinearProgressIndicator( + value: _getDiffPercentage(state), + valueColor: AlwaysStoppedAnimation( + widget.bridgeStatus.getBackgroundColor(context), + ), + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + Flexible( + child: BlocBuilder( + builder: (context, state) { + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 800), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.showCurrentStatus + ? Padding( + padding: const EdgeInsets.only( + left: 10.0, + right: 10.0, + bottom: 15.0, + ), + child: ChabanBridgeForecastListItem( + onTap: () => + BlocProvider.of(context) + .add( + GoTo( + goTo: widget + .bridgeStatus.currentChabanBridgeForecast, + ), + ), + hasPassed: false, + isCurrent: true, + chabanBridgeForecast: widget + .bridgeStatus.currentChabanBridgeForecast, + index: -1, + ), + ) + : const SizedBox.shrink(), + ), + ); + }, ), ), - ), - const SizedBox( - height: 20, - ), - Container( - color: Theme.of(context).colorScheme.inversePrimary, - width: double.infinity, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( - widget.bridgeStatus.nextStatusMessagePrefix, + AppLocalizations.of(context)!.lisOfUpcomingClosures, style: const TextStyle( fontSize: 20, ), ), - Text( - widget.bridgeStatus.remainingTime, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), + const Icon(Icons.arrow_circle_down), ], ), ), - ), - const SizedBox( - height: 20, - ), - Flexible( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.showCurrentStatus - ? Padding( - padding: const EdgeInsets.only( - left: 10.0, - right: 10.0, - bottom: 15.0, - ), - child: ChabanBridgeForecastListItem( - onTap: () => - BlocProvider.of(context).add( - GoTo( - goTo: widget - .bridgeStatus.currentChabanBridgeForecast, - ), - ), - hasPassed: false, - isCurrent: true, - chabanBridgeForecast: - widget.bridgeStatus.currentChabanBridgeForecast, - index: -1, - ), - ) - : const SizedBox.shrink(), - ), - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)!.lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, - ), - ), - const Icon(Icons.arrow_circle_down), - ], - ), - ), - ], - ); + ], + ); + },); } } diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index faf5a4f0..987dc6a7 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,10 +1,10 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=D:\src\Flutter -FLUTTER_APPLICATION_PATH=D:\Code\chabo +FLUTTER_ROOT=C:\src\flutter +FLUTTER_APPLICATION_PATH=C:\Users\Valentin\Documents\Projets\chabo COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=1.0.0 -FLUTTER_BUILD_NUMBER=1.0.0 +FLUTTER_BUILD_NAME=1.1.0 +FLUTTER_BUILD_NUMBER=1.1.0 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index 23d30c66..fb71f946 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,11 +1,11 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=D:\src\Flutter" -export "FLUTTER_APPLICATION_PATH=D:\Code\chabo" +export "FLUTTER_ROOT=C:\src\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Users\Valentin\Documents\Projets\chabo" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1.0.0" +export "FLUTTER_BUILD_NAME=1.1.0" +export "FLUTTER_BUILD_NUMBER=1.1.0" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" From 295a53e7e0cca345467aa202966761615a719b4d Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 15 Apr 2023 00:11:20 +0200 Subject: [PATCH 40/65] chore(ui-backend): whole rewrite of the Bloc logic --- .../chaban_bridge_forecast_bloc.dart | 140 ++++------- .../chaban_bridge_forecast_event.dart | 5 +- .../chaban_bridge_forecast_state.dart | 82 +------ .../chaban_bridge_status_bloc.dart | 171 +++++++++++++ .../chaban_bridge_status_event.dart | 21 ++ .../chaban_bridge_status_state.dart | 87 +++++++ .../scroll_status/scroll_status_bloc.dart | 16 +- .../scroll_status/scroll_status_state.dart | 14 +- lib/chabo.dart | 6 + lib/const.dart | 3 + lib/extensions/duration_extension.dart | 16 +- lib/models/chaban_bridge_status.dart | 72 ------ .../chaban_bridge_forecast_screen.dart | 55 ++--- lib/widgets/chaban_bridge_forecast_list.dart | 86 ++++--- .../chaban_bridge_forecast_list_item.dart | 3 +- lib/widgets/chaban_bridge_status_widget.dart | 227 ++++++++---------- 16 files changed, 547 insertions(+), 457 deletions(-) create mode 100644 lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart create mode 100644 lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart create mode 100644 lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart delete mode 100644 lib/models/chaban_bridge_status.dart diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index b69f19c7..3cb71f5f 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -1,37 +1,52 @@ +// ignore_for_file: invalid_use_of_visible_for_testing_member + +import 'dart:async'; import 'dart:convert'; -import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/extensions/string_extension.dart'; +import 'package:chabo/const.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/chaban_bridge_boat_forecast.dart'; import 'package:chabo/models/chaban_bridge_maintenance_forecast.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; -import 'package:intl/intl.dart'; part 'chaban_bridge_forecast_event.dart'; part 'chaban_bridge_forecast_state.dart'; -const _chabanBridgeForecastLimit = 1000; -const throttleDuration = Duration(milliseconds: 1000); - class ChabanBridgeForecastBloc extends Bloc { final http.Client httpClient; ChabanBridgeForecastBloc({required this.httpClient}) : super(const ChabanBridgeForecastState()) { + Timer.periodic(const Duration(seconds: 1), _onRefreshCurrentStatus); on( _onChabanBridgeForecastFetched, ); - on( - _onChabanBridgeForecastRefreshCurrentStatus, - ); + } + + void _onRefreshCurrentStatus(Timer timer) { + try { + if (state.status == ChabanBridgeForecastStatus.success) { + final currentStatus = _getCurrentStatus(state.chabanBridgeForecasts); + final previousStatus = + _getPreviousStatus(state.chabanBridgeForecasts, currentStatus); + if (currentStatus != state.currentChabanBridgeForecast) { + emit( + state.copyWith( + currentChabanBridgeForecast: currentStatus, + previousChabanBridgeForecast: previousStatus, + ), + ); + } + } + } catch (_) { + emit(state.copyWith( + status: ChabanBridgeForecastStatus.failure, message: _.toString())); + } } Future> _fetchChabanBridgeForecasts( @@ -41,7 +56,7 @@ class ChabanBridgeForecastBloc '/api/records/1.0/search', { 'dataset': 'previsions_pont_chaban', - 'rows': '$_chabanBridgeForecastLimit', + 'rows': '${Const.chabanBridgeForecastLimit}', 'sort': '-date_passage', 'start': '$offset', 'timezone': 'Europe/Paris' @@ -66,7 +81,7 @@ class ChabanBridgeForecastBloc return []; } - AbstractChabanBridgeForecast _setCurrentStatus( + AbstractChabanBridgeForecast _getCurrentStatus( List chabanBridgeForecast) { int middle = chabanBridgeForecast.length ~/ 2; if ((chabanBridgeForecast[middle] @@ -86,13 +101,13 @@ class ChabanBridgeForecastBloc } else if (chabanBridgeForecast[middle] .circulationClosingDate .isAfter(DateTime.now())) { - return _setCurrentStatus(chabanBridgeForecast.sublist(0, middle + 1)); + return _getCurrentStatus(chabanBridgeForecast.sublist(0, middle + 1)); } else { - return _setCurrentStatus(chabanBridgeForecast.sublist(middle)); + return _getCurrentStatus(chabanBridgeForecast.sublist(middle)); } } - AbstractChabanBridgeForecast? _setPreviousStatus( + AbstractChabanBridgeForecast? _getPreviousStatus( List chabanBridgeForecasts, AbstractChabanBridgeForecast currentStatus) { if (chabanBridgeForecasts.indexOf(currentStatus) == 0) { @@ -110,90 +125,39 @@ class ChabanBridgeForecastBloc if (state.status == ChabanBridgeForecastStatus.initial) { final chabanBridgeForecasts = await _fetchChabanBridgeForecasts(state.offset); - final currentStatus = _setCurrentStatus(chabanBridgeForecasts); + final currentStatus = _getCurrentStatus(chabanBridgeForecasts); emit(state.copyWith( status: ChabanBridgeForecastStatus.success, chabanBridgeForecasts: chabanBridgeForecasts, currentChabanBridgeForecast: currentStatus, previousChabanBridgeForecast: - _setPreviousStatus(chabanBridgeForecasts, currentStatus), + _getPreviousStatus(chabanBridgeForecasts, currentStatus), hasReachedMax: false, - offset: state.offset + _chabanBridgeForecastLimit)); + offset: state.offset + Const.chabanBridgeForecastLimit)); } final chabanBridgeForecasts = await _fetchChabanBridgeForecasts(state.chabanBridgeForecasts.length); - emit(chabanBridgeForecasts.isEmpty - ? state.copyWith(hasReachedMax: true) - : state.copyWith( - currentChabanBridgeForecast: state.currentChabanBridgeForecast ?? - _setCurrentStatus(chabanBridgeForecasts), - previousChabanBridgeForecast: - state.previousChabanBridgeForecast ?? - _setPreviousStatus(chabanBridgeForecasts, - _setCurrentStatus(chabanBridgeForecasts)), - status: ChabanBridgeForecastStatus.success, - chabanBridgeForecasts: List.of(state.chabanBridgeForecasts) - ..addAll(chabanBridgeForecasts), - hasReachedMax: false, - offset: state.offset + _chabanBridgeForecastLimit)); - } catch (_) { - emit(state.copyWith( - status: ChabanBridgeForecastStatus.failure, message: _.toString())); - } - } - - Future _onChabanBridgeForecastRefreshCurrentStatus( - ChabanBridgeForecastRefreshCurrentStatus event, - Emitter emit) async { - try { - if (state.status == ChabanBridgeForecastStatus.success) { - final currentStatus = _setCurrentStatus(state.chabanBridgeForecasts); - emit( - state.copyWith( - currentChabanBridgeForecast: currentStatus, - durationUntilNextEvent: _getDurationForNextEvent(), - durationBetweenPreviousAndNextEvent: _getDurationBetweenPreviousAndNextEvent(), - previousChabanBridgeForecast: - _setPreviousStatus(state.chabanBridgeForecasts, currentStatus), - ), - ); - } + emit( + chabanBridgeForecasts.isEmpty + ? state.copyWith(hasReachedMax: true) + : state.copyWith( + currentChabanBridgeForecast: + state.currentChabanBridgeForecast ?? + _getCurrentStatus(chabanBridgeForecasts), + previousChabanBridgeForecast: + state.previousChabanBridgeForecast ?? + _getPreviousStatus(chabanBridgeForecasts, + _getCurrentStatus(chabanBridgeForecasts)), + status: ChabanBridgeForecastStatus.success, + chabanBridgeForecasts: List.of(state.chabanBridgeForecasts) + ..addAll(chabanBridgeForecasts), + hasReachedMax: false, + offset: state.offset + Const.chabanBridgeForecastLimit, + ), + ); } catch (_) { emit(state.copyWith( status: ChabanBridgeForecastStatus.failure, message: _.toString())); } } - - Duration? _getDurationForNextEvent() { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - final DateTime now = DateTime.now(); - if (currentChabanBridgeForecast != null) { - if (currentChabanBridgeForecast.isCurrentlyClosed()) { - return currentChabanBridgeForecast.circulationReOpeningDate - .difference(now); - } else { - return currentChabanBridgeForecast.circulationClosingDate - .difference(now); - } - } else { - return null; - } - } - - Duration? _getDurationBetweenPreviousAndNextEvent() { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - final previousChabanBridgeForecast = state.previousChabanBridgeForecast; - if (currentChabanBridgeForecast != null && - previousChabanBridgeForecast != null) { - if (currentChabanBridgeForecast.isCurrentlyClosed()) { - return currentChabanBridgeForecast.closedDuration; - } - else { - return currentChabanBridgeForecast.circulationClosingDate - .difference(previousChabanBridgeForecast.circulationReOpeningDate); - } - } else { - return null; - } - } } diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart index 76ddc8d4..020cc3d3 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart @@ -4,6 +4,5 @@ abstract class ChabanBridgeForecastEvent extends ChaboEvent {} class ChabanBridgeForecastFetched extends ChabanBridgeForecastEvent {} -class ChabanBridgeForecastRefreshCurrentStatus extends ChabanBridgeForecastEvent {} - - +class ChabanBridgeForecastRefreshCurrentStatus + extends ChabanBridgeForecastEvent {} diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart index 9f24e841..67feb542 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart @@ -7,8 +7,6 @@ class ChabanBridgeForecastState extends Equatable { final List chabanBridgeForecasts; final AbstractChabanBridgeForecast? currentChabanBridgeForecast; final AbstractChabanBridgeForecast? previousChabanBridgeForecast; - final Duration? durationUntilNextEvent; - final Duration? durationBetweenPreviousAndNextEvent; final bool hasReachedMax; final int offset; final String message; @@ -17,9 +15,7 @@ class ChabanBridgeForecastState extends Equatable { {this.status = ChabanBridgeForecastStatus.initial, this.chabanBridgeForecasts = const [], this.currentChabanBridgeForecast, - this.durationUntilNextEvent, this.previousChabanBridgeForecast, - this.durationBetweenPreviousAndNextEvent, this.hasReachedMax = false, this.offset = 0, this.message = 'OK'}); @@ -29,18 +25,11 @@ class ChabanBridgeForecastState extends Equatable { List? chabanBridgeForecasts, AbstractChabanBridgeForecast? currentChabanBridgeForecast, AbstractChabanBridgeForecast? previousChabanBridgeForecast, - Duration? durationUntilNextEvent, - Duration? durationBetweenPreviousAndNextEvent, bool? hasReachedMax, int? offset, String? message}) { return ChabanBridgeForecastState( status: status ?? this.status, - durationUntilNextEvent: - durationUntilNextEvent ?? this.durationUntilNextEvent, - durationBetweenPreviousAndNextEvent: - durationBetweenPreviousAndNextEvent ?? - this.durationBetweenPreviousAndNextEvent, chabanBridgeForecasts: chabanBridgeForecasts ?? this.chabanBridgeForecasts, currentChabanBridgeForecast: @@ -52,84 +41,19 @@ class ChabanBridgeForecastState extends Equatable { message: message ?? this.message); } - Color getBackgroundColor(BuildContext context) { - final isOpen = !currentChabanBridgeForecast!.isCurrentlyClosed(); - final differenceStartingPoint = currentChabanBridgeForecast! - .circulationReOpeningDate - .difference(DateTime.now()); - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.tertiaryContainer; - } else if (isOpen) { - return Colors.green; - } else { - return Theme.of(context).colorScheme.errorContainer; - } - } - - Color getForegroundColor(BuildContext context) { - final isOpen = !currentChabanBridgeForecast!.isCurrentlyClosed(); - final differenceStartingPoint = currentChabanBridgeForecast! - .circulationReOpeningDate - .difference(DateTime.now()); - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.onTertiaryContainer; - } else if (isOpen) { - return Theme.of(context).colorScheme.background; - } else { - return Theme.of(context).colorScheme.onErrorContainer; - } - } - - String nextStatusMessagePrefix(BuildContext context) { - if (currentChabanBridgeForecast!.isCurrentlyClosed()) { - return '${AppLocalizations.of(context)!.scheduledToOpen.capitalize()} '; - } else { - return '${AppLocalizations.of(context)!.nextClosingScheduled.capitalize()} '; - } - } - - String getCurrentStatus(BuildContext context) { - if (currentChabanBridgeForecast!.isCurrentlyClosed()) { - return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; - } else { - return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently}'; - } - } - - String getCurrentStatusShort(BuildContext context) { - if (currentChabanBridgeForecast!.isCurrentlyClosed()) { - return AppLocalizations.of(context)!.closed; - } else { - return AppLocalizations.of(context)!.open; - } - } - - String _getGreetings(BuildContext context) { - final now = DateTime.now(); - int hours = int.parse(DateFormat('HH').format(now)); - if (hours >= 6 && hours <= 12) { - return AppLocalizations.of(context)!.goodMorning.capitalize(); - } else if (hours > 12 && hours <= 18) { - return AppLocalizations.of(context)!.goodAfternoon.capitalize(); - } else { - return AppLocalizations.of(context)!.goodEvening.capitalize(); - } - } - @override String toString() { return 'ChabanBridgeForecastState{status: $status, chabanBridgeForecasts: $chabanBridgeForecasts, currentChabanBridgeForecast: $currentChabanBridgeForecast, hasReachedMax: $hasReachedMax, offset: $offset, message: $message}'; } @override - List get props => [ + List get props => [ status, chabanBridgeForecasts, hasReachedMax, offset, message, - currentChabanBridgeForecast ?? Object(), - previousChabanBridgeForecast ?? Object(), - durationUntilNextEvent ?? Object() + currentChabanBridgeForecast, + previousChabanBridgeForecast, ]; } diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart new file mode 100644 index 00000000..637949e5 --- /dev/null +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -0,0 +1,171 @@ +import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/extensions/string_extension.dart'; +import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; + +part 'chaban_bridge_status_event.dart'; +part 'chaban_bridge_status_state.dart'; + +class ChabanBridgeStatusBloc + extends Bloc { + ChabanBridgeStatusBloc() : super(const ChabanBridgeStatusStateInitial()) { + on( + _onChabanBridgeStatusChanged, + ); + on( + _onRefresh, + ); + } + + void _onChabanBridgeStatusChanged( + ChabanBridgeStatusChanged event, Emitter emit) { + emit( + state.copyWith( + currentChabanBridgeForecast: event.currentChabanBridgeForecast, + previousChabanBridgeForecast: event.previousChabanBridgeForecast), + ); + } + + void _onRefresh( + ChabanBridgeStatusRefresh event, Emitter emit) { + final Duration? durationUntilNextEvent = _getDurationUntilNextEvent(); + final Duration? durationBetweenPreviousAndNextEvent = + _getDurationBetweenPreviousAndNextEvent(); + final double completionPercentage = _getDiffPercentage( + durationBetweenPreviousAndNextEvent, durationUntilNextEvent); + final String mainMessageStatus = _getMainStatus(event.context); + final String timeMessagePrefix = _getTimeMessagePrefix(event.context); + final Color foregroundColor = _getForegroundColor(event.context); + final Color backgroundColor = _getBackgroundColor(event.context); + + emit(state.copyWith( + durationUntilNextEvent: durationUntilNextEvent, + durationBetweenPreviousAndNextEvent: + durationBetweenPreviousAndNextEvent, + completionPercentage: completionPercentage, + mainMessageStatus: mainMessageStatus, + timeMessagePrefix: timeMessagePrefix, + foregroundColor: foregroundColor, + backgroundColor: backgroundColor)); + } + + Color _getBackgroundColor(BuildContext context) { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + if (currentChabanBridgeForecast != null) { + final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); + final differenceStartingPoint = currentChabanBridgeForecast + .circulationReOpeningDate + .difference(DateTime.now()); + if (isOpen && differenceStartingPoint.inMinutes <= 120) { + return Theme.of(context).colorScheme.tertiaryContainer; + } else if (isOpen) { + return Colors.green; + } else { + return Theme.of(context).colorScheme.errorContainer; + } + } else { + return Colors.yellow; + } + } + + Color _getForegroundColor(BuildContext context) { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + if (currentChabanBridgeForecast != null) { + final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); + final differenceStartingPoint = currentChabanBridgeForecast + .circulationReOpeningDate + .difference(DateTime.now()); + if (isOpen && differenceStartingPoint.inMinutes <= 120) { + return Theme.of(context).colorScheme.onTertiaryContainer; + } else if (isOpen) { + return Theme.of(context).colorScheme.background; + } else { + return Theme.of(context).colorScheme.onErrorContainer; + } + } else { + return Colors.purple; + } + } + + String _getTimeMessagePrefix(BuildContext context) { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + if (currentChabanBridgeForecast != null) { + if (currentChabanBridgeForecast.isCurrentlyClosed()) { + return '${AppLocalizations.of(context)!.scheduledToOpen.capitalize()} '; + } else { + return '${AppLocalizations.of(context)!.nextClosingScheduled.capitalize()} '; + } + } else { + return 'NO_TIME'; + } + } + + String _getMainStatus(BuildContext context) { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + if (currentChabanBridgeForecast != null && + !currentChabanBridgeForecast.isCurrentlyClosed()) { + return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}'; + } else { + return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; + } + } + + Duration? _getDurationUntilNextEvent() { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + final DateTime now = DateTime.now(); + if (currentChabanBridgeForecast != null) { + if (currentChabanBridgeForecast.isCurrentlyClosed()) { + return currentChabanBridgeForecast.circulationReOpeningDate + .difference(now); + } else { + return currentChabanBridgeForecast.circulationClosingDate + .difference(now); + } + } else { + return null; + } + } + + Duration? _getDurationBetweenPreviousAndNextEvent() { + final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + final previousChabanBridgeForecast = state.previousChabanBridgeForecast; + if (currentChabanBridgeForecast != null && + previousChabanBridgeForecast != null) { + if (currentChabanBridgeForecast.isCurrentlyClosed()) { + return currentChabanBridgeForecast.closedDuration; + } else { + return currentChabanBridgeForecast.circulationClosingDate + .difference(previousChabanBridgeForecast.circulationReOpeningDate); + } + } else { + return null; + } + } + + double _getDiffPercentage(Duration? durationBetweenPreviousAndNextEvent, + Duration? durationUntilNextEvent) { + if (durationBetweenPreviousAndNextEvent != null && + durationUntilNextEvent != null) { + return 1 - + (durationUntilNextEvent.inSeconds / + durationBetweenPreviousAndNextEvent.inSeconds); + } else { + return 1; + } + } + + String _getGreetings(BuildContext context) { + int hours = int.parse(DateFormat('HH').format(DateTime.now())); + if (hours >= 6 && hours <= 12) { + return AppLocalizations.of(context)!.goodMorning.capitalize(); + } else if (hours > 12 && hours <= 18) { + return AppLocalizations.of(context)!.goodAfternoon.capitalize(); + } else { + return AppLocalizations.of(context)!.goodEvening.capitalize(); + } + } +} diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart new file mode 100644 index 00000000..f5b76ef9 --- /dev/null +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart @@ -0,0 +1,21 @@ +part of 'chaban_bridge_status_bloc.dart'; + +class ChabanBridgeStatusEvent extends ChaboEvent {} + +class ChabanBridgeStatusChanged extends ChabanBridgeStatusEvent { + final AbstractChabanBridgeForecast? currentChabanBridgeForecast; + final AbstractChabanBridgeForecast? previousChabanBridgeForecast; + + ChabanBridgeStatusChanged( + {required this.currentChabanBridgeForecast, + required this.previousChabanBridgeForecast}) + : super(); +} + +class ChabanBridgeStatusRefresh extends ChabanBridgeStatusEvent { + final BuildContext context; + + ChabanBridgeStatusRefresh({ + required this.context, + }) : super(); +} diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart new file mode 100644 index 00000000..dcc5f089 --- /dev/null +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart @@ -0,0 +1,87 @@ +part of 'chaban_bridge_status_bloc.dart'; + +enum ChabanBridgeStatusLifespan { empty, populated } + +class ChabanBridgeStatusState extends Equatable { + final ChabanBridgeStatusLifespan chabanBridgeStatusLifespan; + final AbstractChabanBridgeForecast? currentChabanBridgeForecast; + final AbstractChabanBridgeForecast? previousChabanBridgeForecast; + final Duration durationUntilNextEvent; + final Duration? durationBetweenPreviousAndNextEvent; + final double completionPercentage; + final String mainMessageStatus; + final String timeMessagePrefix; + final Color foregroundColor; + final Color backgroundColor; + + const ChabanBridgeStatusState( + {required this.chabanBridgeStatusLifespan, + required this.currentChabanBridgeForecast, + required this.previousChabanBridgeForecast, + required this.durationUntilNextEvent, + required this.durationBetweenPreviousAndNextEvent, + required this.completionPercentage, + required this.mainMessageStatus, + required this.timeMessagePrefix, + required this.foregroundColor, + required this.backgroundColor}); + + ChabanBridgeStatusState copyWith( + {ChabanBridgeStatusLifespan? chabanBridgeStatusLifespan, + AbstractChabanBridgeForecast? currentChabanBridgeForecast, + AbstractChabanBridgeForecast? previousChabanBridgeForecast, + Duration? durationUntilNextEvent, + Duration? durationBetweenPreviousAndNextEvent, + double? completionPercentage, + String? mainMessageStatus, + String? timeMessagePrefix, + Color? foregroundColor, + Color? backgroundColor}) { + return ChabanBridgeStatusState( + chabanBridgeStatusLifespan: + chabanBridgeStatusLifespan ?? this.chabanBridgeStatusLifespan, + currentChabanBridgeForecast: + currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, + previousChabanBridgeForecast: + previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, + durationUntilNextEvent: + durationUntilNextEvent ?? this.durationUntilNextEvent, + durationBetweenPreviousAndNextEvent: + durationBetweenPreviousAndNextEvent ?? + this.durationBetweenPreviousAndNextEvent, + completionPercentage: completionPercentage ?? this.completionPercentage, + mainMessageStatus: mainMessageStatus ?? this.mainMessageStatus, + timeMessagePrefix: timeMessagePrefix ?? this.timeMessagePrefix, + foregroundColor: foregroundColor ?? this.foregroundColor, + backgroundColor: backgroundColor ?? this.backgroundColor); + } + + @override + List get props => [ + chabanBridgeStatusLifespan, + currentChabanBridgeForecast, + previousChabanBridgeForecast, + durationUntilNextEvent, + durationBetweenPreviousAndNextEvent, + completionPercentage, + mainMessageStatus, + timeMessagePrefix, + foregroundColor, + backgroundColor + ]; +} + +class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { + const ChabanBridgeStatusStateInitial() + : super( + previousChabanBridgeForecast: null, + currentChabanBridgeForecast: null, + durationUntilNextEvent: Duration.zero, + durationBetweenPreviousAndNextEvent: null, + chabanBridgeStatusLifespan: ChabanBridgeStatusLifespan.empty, + completionPercentage: 0, + mainMessageStatus: '', + timeMessagePrefix: '', + foregroundColor: Colors.white, + backgroundColor: Colors.white); +} diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index 0d3e2a3c..27fb2b50 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -12,7 +12,9 @@ class ScrollStatusBloc extends Bloc { ScrollStatusBloc({required this.scrollController}) : super(ScrollStatusState( - showCurrentStatus: true, status: ScrollStatus.ok)) { + showCurrentStatus: true, + status: ScrollStatus.ok, + currentTarget: null)) { on( _onScrollChanged, transformer: droppable(), @@ -31,9 +33,9 @@ class ScrollStatusBloc extends Bloc { ScrollStatusChanged event, Emitter emit) async { emit( state.copyWith( - showCurrentStatus: true, - status: ScrollStatus.ok, - ), + showCurrentStatus: true, + status: ScrollStatus.ok, + currentTarget: state.currentTarget), ); } @@ -61,9 +63,9 @@ class ScrollStatusBloc extends Bloc { duration: const Duration(seconds: 1), curve: Curves.fastOutSlowIn); emit( state.copyWith( - showCurrentStatus: false, - status: ScrollStatus.ok, - ), + showCurrentStatus: false, + status: ScrollStatus.ok, + currentTarget: event.goTo), ); } } diff --git a/lib/bloc/scroll_status/scroll_status_state.dart b/lib/bloc/scroll_status/scroll_status_state.dart index 15e3fc94..9cdc1c2e 100644 --- a/lib/bloc/scroll_status/scroll_status_state.dart +++ b/lib/bloc/scroll_status/scroll_status_state.dart @@ -3,14 +3,22 @@ part of 'scroll_status_bloc.dart'; enum ScrollStatus { ok, error } class ScrollStatusState { + final AbstractChabanBridgeForecast? currentTarget; final bool showCurrentStatus; final ScrollStatus status; - ScrollStatusState({required this.status, required this.showCurrentStatus}); + ScrollStatusState( + {required this.status, + required this.showCurrentStatus, + required this.currentTarget}); - ScrollStatusState copyWith({bool? showCurrentStatus, ScrollStatus? status}) { + ScrollStatusState copyWith( + {bool? showCurrentStatus, + ScrollStatus? status, + AbstractChabanBridgeForecast? currentTarget}) { return ScrollStatusState( status: status ?? this.status, - showCurrentStatus: showCurrentStatus ?? this.showCurrentStatus); + showCurrentStatus: showCurrentStatus ?? this.showCurrentStatus, + currentTarget: currentTarget ?? this.currentTarget); } } diff --git a/lib/chabo.dart b/lib/chabo.dart index 83337226..30b588c7 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; import 'package:chabo/bloc/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; @@ -59,6 +60,11 @@ class Chabo extends StatelessWidget { ), ), + /// Bloc intended to manage the status + BlocProvider( + create: (_) => ChabanBridgeStatusBloc(), + ), + /// Bloc intended to manage scroll to status to display (or not) the current status BlocProvider( create: (_) => ScrollStatusBloc( diff --git a/lib/const.dart b/lib/const.dart index 124a0d5a..e2d27f64 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -8,6 +8,9 @@ class Const { static const String appName = 'Chabo'; static String legalLease = '© ${DateTime.now().year} - Valentin REVERSAT'; + /// List + static const int chabanBridgeForecastLimit = 1000; + /// Paths static const String changelogPlaceholder = ':lang:'; static const String changelogPath = 'CHANGELOG_$changelogPlaceholder.md'; diff --git a/lib/extensions/duration_extension.dart b/lib/extensions/duration_extension.dart index a630fd2b..35bb91b2 100644 --- a/lib/extensions/duration_extension.dart +++ b/lib/extensions/duration_extension.dart @@ -3,10 +3,18 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; extension DurationExtention on Duration { String durationToString(BuildContext context) { - final dayToken = inDays == 0 ? '' : '${inDays.toString()}${AppLocalizations.of(context)!.daySmall} '; - final hourToken = inHours.remainder(24) == 0 ? '' : '${inHours.remainder(24).toString()}h '; - final minToken = inMinutes.remainder(60) == 0 ? '' : '${inMinutes.remainder(60).toString()}m '; - final secsToken = inSeconds.remainder(60) == 0 ? '' : '${inSeconds.remainder(60).toString()}s '; + final dayToken = inDays == 0 + ? '' + : '${inDays.toString()}${AppLocalizations.of(context)!.daySmall} '; + final hourToken = inHours.remainder(24) == 0 + ? '' + : '${inHours.remainder(24).toString()}h '; + final minToken = inMinutes.remainder(60) == 0 + ? '' + : '${inMinutes.remainder(60).toString()}m '; + final secsToken = inSeconds.remainder(60) == 0 + ? '' + : '${inSeconds.remainder(60).toString()}s '; return '$dayToken$hourToken$minToken$secsToken'; } diff --git a/lib/models/chaban_bridge_status.dart b/lib/models/chaban_bridge_status.dart deleted file mode 100644 index df4db9b8..00000000 --- a/lib/models/chaban_bridge_status.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:chabo/extensions/string_extension.dart'; -import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:intl/intl.dart'; - -class ChabanBridgeStatus { - final AbstractChabanBridgeForecast currentChabanBridgeForecast; - final AbstractChabanBridgeForecast? previousChabanBridgeForecast; - final DateTime now = DateTime.now(); - late String currentStatus; - late String currentStatusShort; - late String nextStatusMessagePrefix; - late Duration differenceStartingPoint; - late bool isOpen; - - ChabanBridgeStatus( - {required this.currentChabanBridgeForecast, - required this.previousChabanBridgeForecast, - required BuildContext context}) { - if (currentChabanBridgeForecast.isCurrentlyClosed()) { - differenceStartingPoint = - currentChabanBridgeForecast.circulationReOpeningDate.difference(now); - nextStatusMessagePrefix = - AppLocalizations.of(context)!.scheduledToOpen.capitalize(); - currentStatusShort = AppLocalizations.of(context)!.closed; - currentStatus = - '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; - } else { - differenceStartingPoint = - currentChabanBridgeForecast.circulationClosingDate.difference(now); - nextStatusMessagePrefix = - AppLocalizations.of(context)!.nextClosingScheduled.capitalize(); - currentStatusShort = AppLocalizations.of(context)!.open; - } - nextStatusMessagePrefix += ' '; - currentStatus = - '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently}'; - isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - } - - Color getBackgroundColor(BuildContext context) { - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.tertiaryContainer; - } else if (isOpen) { - return Colors.green; - } else { - return Theme.of(context).colorScheme.errorContainer; - } - } - - Color getForegroundColor(BuildContext context) { - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.onTertiaryContainer; - } else if (isOpen) { - return Theme.of(context).colorScheme.background; - } else { - return Theme.of(context).colorScheme.onErrorContainer; - } - } - - String _getGreetings(BuildContext context) { - int hours = int.parse(DateFormat('HH').format(now)); - if (hours >= 6 && hours <= 12) { - return AppLocalizations.of(context)!.goodMorning.capitalize(); - } else if (hours > 12 && hours <= 18) { - return AppLocalizations.of(context)!.goodAfternoon.capitalize(); - } else { - return AppLocalizations.of(context)!.goodEvening.capitalize(); - } - } -} diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index f47a3290..26e36d6d 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -1,10 +1,9 @@ -import 'dart:async'; - import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; +import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_widgets_state.dart'; -import 'package:chabo/models/chaban_bridge_status.dart'; import 'package:chabo/screens/error_screen.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; @@ -24,23 +23,15 @@ class ChabanBridgeForecastScreen extends StatefulWidget { class _ChabanBridgeForecastScreenState extends CustomWidgetState { - @override - void initState() { - Timer.periodic( - const Duration(seconds: 1), - (Timer t) => BlocProvider.of(context).add( - ChabanBridgeForecastRefreshCurrentStatus(), - ), - ); - super.initState(); - } - @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: const FloatingActions(), body: SafeArea( child: BlocBuilder( + buildWhen: (previous, current) => + previous.status == ChabanBridgeForecastStatus.initial && + current.status == ChabanBridgeForecastStatus.success, builder: (context, state) { switch (state.status) { case ChabanBridgeForecastStatus.failure: @@ -49,16 +40,25 @@ class _ChabanBridgeForecastScreenState if (state.chabanBridgeForecasts.isEmpty) { return const ErrorScreen(errorMessage: 'Empty return'); } - var bridgeStatus = ChabanBridgeStatus( - currentChabanBridgeForecast: - state.currentChabanBridgeForecast!, - previousChabanBridgeForecast: - state.previousChabanBridgeForecast, - context: context, - ); return MultiBlocListener( listeners: [ + BlocListener( + listener: (context, state) async { + BlocProvider.of(context).add( + ChabanBridgeStatusChanged( + currentChabanBridgeForecast: + state.currentChabanBridgeForecast, + previousChabanBridgeForecast: + state.previousChabanBridgeForecast, + ), + ); + BlocProvider.of(context).add( + GoTo(goTo: state.currentChabanBridgeForecast), + ); + }, + ), BlocListener( listener: (context, state) async { ScaffoldMessenger.of(context).showSnackBar( @@ -134,18 +134,11 @@ class _ChabanBridgeForecastScreenState ) ], child: Column( - children: [ - ChabanBridgeStatusWidget( - bridgeStatus: bridgeStatus, - ), + children: const [ + ChabanBridgeStatusWidget(), Expanded( flex: 11, - child: ChabanBridgeForecastList( - currentChabanBridgeForecast: - state.currentChabanBridgeForecast, - chabanBridgeForecasts: state.chabanBridgeForecasts, - hasReachedMax: state.hasReachedMax, - ), + child: ChabanBridgeForecastList(), ), ], ), diff --git a/lib/widgets/chaban_bridge_forecast_list.dart b/lib/widgets/chaban_bridge_forecast_list.dart index 9760d289..97596bbc 100644 --- a/lib/widgets/chaban_bridge_forecast_list.dart +++ b/lib/widgets/chaban_bridge_forecast_list.dart @@ -1,3 +1,4 @@ +import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; @@ -8,16 +9,7 @@ import 'package:intl/intl.dart'; import 'ad_banner_widget.dart'; class ChabanBridgeForecastList extends StatefulWidget { - final AbstractChabanBridgeForecast? currentChabanBridgeForecast; - final List chabanBridgeForecasts; - final bool hasReachedMax; - - const ChabanBridgeForecastList({ - Key? key, - required this.chabanBridgeForecasts, - required this.hasReachedMax, - required this.currentChabanBridgeForecast, - }) : super(key: key); + const ChabanBridgeForecastList({super.key}); @override State createState() { @@ -37,40 +29,46 @@ class _ChabanBridgeForecastListState extends State { } return true; }, - child: ListView.separated( - cacheExtent: 5000, - padding: const EdgeInsets.all(0), - itemBuilder: (BuildContext context, int index) { - return ChabanBridgeForecastListItem( - key: - GlobalObjectKey(widget.chabanBridgeForecasts[index].hashCode), - isCurrent: widget.chabanBridgeForecasts[index] == - widget.currentChabanBridgeForecast, - hasPassed: widget - .chabanBridgeForecasts[index].circulationReOpeningDate - .isBefore(DateTime.now()), - chabanBridgeForecast: widget.chabanBridgeForecasts[index], - index: index); - }, - itemCount: widget.chabanBridgeForecasts.length, - controller: BlocProvider.of(context).scrollController, - separatorBuilder: (BuildContext context, int index) { - if ((index + 1 <= widget.chabanBridgeForecasts.length && - widget.chabanBridgeForecasts[index].circulationClosingDate - .month != - widget.chabanBridgeForecasts[index + 1].circulationClosingDate - .month)) { - return _MonthWidget( - chabanBridgeForecast: widget.chabanBridgeForecasts[index + 1]); - } - if ((index % 10 == 0 || - index == - widget.chabanBridgeForecasts - .indexOf(widget.currentChabanBridgeForecast!)) && - index != 0) { - return const AdBannerWidget(); - } - return const SizedBox.shrink(); + child: BlocBuilder( + builder: (context, state) { + return ListView.separated( + cacheExtent: 5000, + padding: const EdgeInsets.all(0), + itemBuilder: (BuildContext context, int index) { + return ChabanBridgeForecastListItem( + key: GlobalObjectKey( + state.chabanBridgeForecasts[index].hashCode), + isCurrent: state.chabanBridgeForecasts[index] == + state.currentChabanBridgeForecast, + hasPassed: state + .chabanBridgeForecasts[index].circulationReOpeningDate + .isBefore(DateTime.now()), + chabanBridgeForecast: state.chabanBridgeForecasts[index], + index: index); + }, + itemCount: state.chabanBridgeForecasts.length, + controller: + BlocProvider.of(context).scrollController, + separatorBuilder: (BuildContext context, int index) { + if ((index + 1 <= state.chabanBridgeForecasts.length && + state.chabanBridgeForecasts[index].circulationClosingDate + .month != + state.chabanBridgeForecasts[index + 1] + .circulationClosingDate.month)) { + return _MonthWidget( + chabanBridgeForecast: state.chabanBridgeForecasts[index + 1], + ); + } + if ((index % 10 == 0 || + index == + state.chabanBridgeForecasts + .indexOf(state.currentChabanBridgeForecast!)) && + index != 0) { + return const AdBannerWidget(); + } + return const SizedBox.shrink(); + }, + ); }, ), ); diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/chaban_bridge_forecast_list_item.dart index b58e25e0..b493a873 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/chaban_bridge_forecast_list_item.dart @@ -117,7 +117,8 @@ class ChabanBridgeForecastListItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - chabanBridgeForecast.closedDuration.durationToString(context), + chabanBridgeForecast.closedDuration + .durationToString(context), style: TextStyle( color: Theme.of(context).colorScheme.timeColor, fontWeight: FontWeight.bold, diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index 06d3741e..c4c2dd0a 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -1,11 +1,10 @@ import 'dart:async'; -import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; -import 'package:chabo/models/chaban_bridge_status.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -13,10 +12,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ChabanBridgeStatusWidget extends StatefulWidget { - final ChabanBridgeStatus bridgeStatus; - - const ChabanBridgeStatusWidget({Key? key, required this.bridgeStatus}) - : super(key: key); + const ChabanBridgeStatusWidget({super.key}); @override State createState() { @@ -30,84 +26,69 @@ class ChabanBridgeStatusWidgetState void initState() { SchedulerBinding.instance.addPostFrameCallback( (_) { - BlocProvider.of(context).add( - GoTo(goTo: widget.bridgeStatus.currentChabanBridgeForecast), + Timer.periodic( + const Duration(seconds: 1), + (Timer t) => BlocProvider.of(context).add( + ChabanBridgeStatusRefresh( + context: context, + ), + ), ); }, ); super.initState(); } - double _getDiffPercentage(ChabanBridgeForecastState state) { - if (state.durationBetweenPreviousAndNextEvent != null && - state.durationUntilNextEvent != null) { - return 1 - - (state.durationUntilNextEvent!.inSeconds / - state.durationBetweenPreviousAndNextEvent!.inSeconds); - } else { - return 1; - } - } - @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 10, - ), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: widget.bridgeStatus.getBackgroundColor(context), - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), - ), + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 10, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${widget.bridgeStatus.currentStatus} ${widget.bridgeStatus.currentStatusShort}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 40, - color: widget.bridgeStatus.getForegroundColor(context), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: state.backgroundColor, + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, ), ), - ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + state.mainMessageStatus, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 40, + color: state.foregroundColor, + ), + ), + ], + ), ), ), - ), - const SizedBox( - height: 20, - ), - Container( - color: Theme.of(context).colorScheme.inversePrimary, - width: double.infinity, - child: Padding( + Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: Column( children: [ Text( - widget.bridgeStatus.nextStatusMessagePrefix, + state.timeMessagePrefix, style: const TextStyle( fontSize: 20, ), ), Text( - state.durationUntilNextEvent == null - ? '' - : state.durationUntilNextEvent! - .durationToString(context), + state.durationUntilNextEvent.durationToString(context), style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, @@ -125,9 +106,9 @@ class ChabanBridgeStatusWidgetState ), ), child: LinearProgressIndicator( - value: _getDiffPercentage(state), + value: state.completionPercentage, valueColor: AlwaysStoppedAnimation( - widget.bridgeStatus.getBackgroundColor(context), + state.backgroundColor, ), ), ), @@ -136,71 +117,67 @@ class ChabanBridgeStatusWidgetState ], ), ), - ), - const SizedBox( - height: 20, - ), - Flexible( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.showCurrentStatus - ? Padding( - padding: const EdgeInsets.only( - left: 10.0, - right: 10.0, - bottom: 15.0, - ), - child: ChabanBridgeForecastListItem( - onTap: () => - BlocProvider.of(context) - .add( - GoTo( - goTo: widget - .bridgeStatus.currentChabanBridgeForecast, + Flexible( + child: BlocBuilder( + builder: (context, state) { + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 800), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.showCurrentStatus && + state.currentTarget != null + ? Padding( + padding: const EdgeInsets.only( + left: 10.0, + right: 10.0, + bottom: 15.0, + ), + child: ChabanBridgeForecastListItem( + onTap: () => + BlocProvider.of(context) + .add( + GoTo( + goTo: state.currentTarget, + ), ), + hasPassed: false, + isCurrent: true, + chabanBridgeForecast: state.currentTarget!, + index: -1, ), - hasPassed: false, - isCurrent: true, - chabanBridgeForecast: widget - .bridgeStatus.currentChabanBridgeForecast, - index: -1, - ), - ) - : const SizedBox.shrink(), - ), - ); - }, + ) + : const SizedBox.shrink(), + ), + ); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)!.lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + AppLocalizations.of(context)!.lisOfUpcomingClosures, + style: const TextStyle( + fontSize: 20, + ), ), - ), - const Icon(Icons.arrow_circle_down), - ], + const Icon(Icons.arrow_circle_down), + ], + ), ), - ), - ], - ); - },); + ], + ); + }, + ); } } From f8861d5423ca080e73117404ce4ff5016830021c Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 15 Apr 2023 12:45:37 +0200 Subject: [PATCH 41/65] feat(ui): add left handed support for menu --- lib/bloc/floating_actions_cubit.dart | 53 ++- lib/chabo.dart | 5 +- lib/const.dart | 4 + lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/misc/no_scaling_animation.dart | 19 + .../chaban_bridge_forecast_screen.dart | 252 ++++++------ lib/screens/notification_screen.dart | 381 +++++++++--------- .../floating_action_item.dart | 28 ++ lib/widgets/floating_actions_widget.dart | 208 +++++----- 11 files changed, 554 insertions(+), 408 deletions(-) create mode 100644 lib/misc/no_scaling_animation.dart create mode 100644 lib/widgets/floating_actions/floating_action_item.dart diff --git a/lib/bloc/floating_actions_cubit.dart b/lib/bloc/floating_actions_cubit.dart index 758f14c7..f3c24c73 100644 --- a/lib/bloc/floating_actions_cubit.dart +++ b/lib/bloc/floating_actions_cubit.dart @@ -1,9 +1,54 @@ +import 'package:chabo/const.dart'; +import 'package:chabo/service/storage_service.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class FloatingActionsCubit extends Cubit { - FloatingActionsCubit(super.initialState); +class FloatingActionsCubit extends Cubit { + final StorageService storageService; - void openActions() { - emit(!state); + FloatingActionsCubit(this.storageService, super.initialState); + + void openFloatingActions() { + emit( + state.copyWith( + isMenuOpen: !state.isMenuOpen, + ), + ); + } + + void changeFloatingActionsSide() { + storageService.saveBool(Const.isRightHandedKey, !state.isRightHanded); + emit( + state.copyWith( + isRightHanded: !state.isRightHanded, + ), + ); } + + void init() async { + final isRightHanded = storageService.readBool(Const.isRightHandedKey) ?? + Const.isRightHandedDefaultValue; + emit( + state.copyWith( + isRightHanded: isRightHanded, + ), + ); + } +} + +class FloatingActionsState extends Equatable { + final bool isMenuOpen; + final bool isRightHanded; + + const FloatingActionsState( + {required this.isMenuOpen, required this.isRightHanded}); + + FloatingActionsState copyWith({bool? isMenuOpen, bool? isRightHanded}) { + return FloatingActionsState( + isMenuOpen: isMenuOpen ?? this.isMenuOpen, + isRightHanded: isRightHanded ?? this.isRightHanded); + } + + @override + List get props => [isMenuOpen, isRightHanded]; } diff --git a/lib/chabo.dart b/lib/chabo.dart index 30b588c7..ecf23a64 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -47,8 +47,9 @@ class Chabo extends StatelessWidget { /// Bloc intended to manage the FloatingActions BlocProvider( create: (_) => FloatingActionsCubit( - false, - ), + storageService, + const FloatingActionsState(isMenuOpen: false, isRightHanded: true), + )..init(), ), /// Bloc intended to manage the forecast displayed diff --git a/lib/const.dart b/lib/const.dart index e2d27f64..0a16e187 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -54,6 +54,7 @@ class Const { 'NOTIFICATION_OPENING_SETTINGS_ENABLED'; static const String notificationClosingEnabledKey = 'NOTIFICATION_CLOSING_SETTINGS_ENABLED'; + static const String isRightHandedKey = 'RIGHT_HANDED'; /// Notifications static const String androidAppLogoPath = @@ -71,6 +72,9 @@ class Const { static const bool notificationOpeningEnabledDefaultValue = false; static const bool notificationClosingEnabledDefaultValue = false; + /// UI + static const bool isRightHandedDefaultValue = true; + /// Android Notifications static const String androidTicket = 'ticker'; static const String notificationDurationChannelId = 'imminent_closures'; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6412dc08..34b8aa3a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -160,5 +160,7 @@ "count": {} } }, - "notificationDayChannelName": "Planned closures" + "notificationDayChannelName": "Planned closures", + "leftHanded": "Left handed", + "rightHanded": "Right handed" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 38767e5b..8ad3b893 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -160,5 +160,7 @@ "count": {} } }, - "notificationDayChannelName": "Cierres planificados" + "notificationDayChannelName": "Cierres planificados", + "leftHanded": "Zurdo.a", + "rightHanded": "Diestro.a" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 66ea41ba..ff570593 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -160,5 +160,7 @@ "count": {} } }, - "notificationDayChannelName": "Fermetures prévues" + "notificationDayChannelName": "Fermetures prévues", + "leftHanded": "Gaucher.ère", + "rightHanded": "Droitier.ère" } \ No newline at end of file diff --git a/lib/misc/no_scaling_animation.dart b/lib/misc/no_scaling_animation.dart new file mode 100644 index 00000000..4b9cf364 --- /dev/null +++ b/lib/misc/no_scaling_animation.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class NoScalingAnimation extends FloatingActionButtonAnimator { + @override + Offset getOffset( + {required Offset begin, required Offset end, required double progress}) { + return end; + } + + @override + Animation getRotationAnimation({required Animation parent}) { + return Tween(begin: 1.0, end: 1.0).animate(parent); + } + + @override + Animation getScaleAnimation({required Animation parent}) { + return Tween(begin: 1.0, end: 1.0).animate(parent); + } +} diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 26e36d6d..b31dbd40 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -1,9 +1,11 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; +import 'package:chabo/bloc/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/notification_service_cubit.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; @@ -25,132 +27,144 @@ class _ChabanBridgeForecastScreenState extends CustomWidgetState { @override Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: const FloatingActions(), - body: SafeArea( - child: BlocBuilder( - buildWhen: (previous, current) => - previous.status == ChabanBridgeForecastStatus.initial && - current.status == ChabanBridgeForecastStatus.success, - builder: (context, state) { - switch (state.status) { - case ChabanBridgeForecastStatus.failure: - return ErrorScreen(errorMessage: state.message); - case ChabanBridgeForecastStatus.success: - if (state.chabanBridgeForecasts.isEmpty) { - return const ErrorScreen(errorMessage: 'Empty return'); - } + return BlocBuilder( + builder: (context, state) { + return Scaffold( + floatingActionButton: const FloatingActionsWidget(), + floatingActionButtonLocation: state.isRightHanded + ? FloatingActionButtonLocation.endFloat + : FloatingActionButtonLocation.startFloat, + floatingActionButtonAnimator: NoScalingAnimation(), + body: SafeArea( + child: BlocBuilder( + buildWhen: (previous, current) => + previous.status == ChabanBridgeForecastStatus.initial && + current.status == ChabanBridgeForecastStatus.success, + builder: (context, state) { + switch (state.status) { + case ChabanBridgeForecastStatus.failure: + return ErrorScreen(errorMessage: state.message); + case ChabanBridgeForecastStatus.success: + if (state.chabanBridgeForecasts.isEmpty) { + return const ErrorScreen(errorMessage: 'Empty return'); + } - return MultiBlocListener( - listeners: [ - BlocListener( - listener: (context, state) async { - BlocProvider.of(context).add( - ChabanBridgeStatusChanged( - currentChabanBridgeForecast: - state.currentChabanBridgeForecast, - previousChabanBridgeForecast: - state.previousChabanBridgeForecast, - ), - ); - BlocProvider.of(context).add( - GoTo(goTo: state.currentChabanBridgeForecast), - ); - }, - ), - BlocListener( - listener: (context, state) async { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: CircularProgressIndicator( - color: Theme.of(context) - .colorScheme - .inversePrimary, - strokeWidth: 5, - ), - ), - Expanded( - flex: 5, - child: Text( - AppLocalizations.of(context)! - .refreshingNotifications, - style: const TextStyle( - fontWeight: FontWeight.bold, + return MultiBlocListener( + listeners: [ + BlocListener( + listener: (context, state) async { + BlocProvider.of(context) + .add( + ChabanBridgeStatusChanged( + currentChabanBridgeForecast: + state.currentChabanBridgeForecast, + previousChabanBridgeForecast: + state.previousChabanBridgeForecast, + ), + ); + BlocProvider.of(context).add( + GoTo(goTo: state.currentChabanBridgeForecast), + ); + }, + ), + BlocListener( + listener: (context, state) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: CircularProgressIndicator( + color: Theme.of(context) + .colorScheme + .inversePrimary, + strokeWidth: 5, + ), + ), + Expanded( + flex: 5, + child: Text( + AppLocalizations.of(context)! + .refreshingNotifications, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), ), - ), + ], ), - ], - ), - ), - ); - await context - .read() - .state - .computeNotifications( - BlocProvider.of( - context) - .state - .chabanBridgeForecasts, - state, - context); - ScaffoldMessenger.of(context).removeCurrentSnackBar( - reason: SnackBarClosedReason.remove, - ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration( - milliseconds: 1000, - ), - content: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Icon( - Icons.check, - color: Theme.of(context) - .colorScheme - .inversePrimary, - )), - Expanded( - flex: 8, - child: Text( - AppLocalizations.of(context)! - .refreshingNotificationsDone, - style: const TextStyle( - fontWeight: FontWeight.bold, + ), + ); + await context + .read() + .state + .computeNotifications( + BlocProvider.of( + context) + .state + .chabanBridgeForecasts, + state, + context); + ScaffoldMessenger.of(context).removeCurrentSnackBar( + reason: SnackBarClosedReason.remove, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration( + milliseconds: 1000, + ), + content: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Icon( + Icons.check, + color: Theme.of(context) + .colorScheme + .inversePrimary, + )), + Expanded( + flex: 8, + child: Text( + AppLocalizations.of(context)! + .refreshingNotificationsDone, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), ), - ), + ], ), - ], - ), + ), + ); + }, + ) + ], + child: Column( + children: const [ + ChabanBridgeStatusWidget(), + Expanded( + flex: 11, + child: ChabanBridgeForecastList(), ), - ); - }, - ) - ], - child: Column( - children: const [ - ChabanBridgeStatusWidget(), - Expanded( - flex: 11, - child: ChabanBridgeForecastList(), + ], ), - ], - ), - ); - default: - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ), - ), + ); + default: + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + ), + ), + ); + }, ); } } diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 94bc7db5..1155db68 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -1,10 +1,12 @@ import 'dart:ui'; +import 'package:chabo/bloc/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; import 'package:chabo/extensions/duration_extension.dart'; +import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,202 +22,215 @@ class NotificationScreen extends StatefulWidget { class _NotificationScreenState extends CustomWidgetState { @override Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton.extended( - heroTag: null, - onPressed: () { - Navigator.of(context).pop(); - }, - label: Wrap( - spacing: 10, - children: [ - Text( - MaterialLocalizations.of(context).closeButtonLabel, - ), - const Icon(Icons.close), - ], - ), - ), - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - leading: const Icon(Icons.notifications_active_outlined), - title: Text( - AppLocalizations.of(context)!.notificationsTitle, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.only( - top: 20, - ), - child: BlocBuilder( - builder: (context, state) { - return Column( + return BlocBuilder( + builder: (context, state) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + heroTag: null, + onPressed: () { + Navigator.of(context).pop(); + }, + label: Wrap( + spacing: 10, children: [ - _CustomListTile( - onChanged: (bool value) => - BlocProvider.of(context).add( - OpeningNotificationStateEvent( - enabled: value, - ), - ), - enabled: state.openingNotificationEnabled, - title: AppLocalizations.of(context)!.openingNotificationTitle, - subtitle: AppLocalizations.of(context)! - .openingNotificationExplanation, - leadingIcon: Icons.check_circle, - iconColor: Colors.green, + Text( + MaterialLocalizations.of(context).closeButtonLabel, ), - _CustomListTile( - onChanged: (bool value) => - BlocProvider.of(context).add( - ClosingNotificationStateEvent( - enabled: value, - ), + const Icon(Icons.close), + ], + ), + ), + floatingActionButtonLocation: state.isRightHanded + ? FloatingActionButtonLocation.endFloat + : FloatingActionButtonLocation.startFloat, + floatingActionButtonAnimator: NoScalingAnimation(), + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + leading: const Icon(Icons.notifications_active_outlined), + title: Text( + AppLocalizations.of(context)!.notificationsTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, ), - enabled: state.closingNotificationEnabled, - title: AppLocalizations.of(context)!.closingNotificationTitle, - subtitle: AppLocalizations.of(context)! - .closingNotificationExplanation, - leadingIcon: Icons.block_rounded, - iconColor: Colors.red, - ), - const SizedBox( - height: 20, - ), - _CustomListTile( - onTap: () async { - var time = await showTimePicker( - initialEntryMode: TimePickerEntryMode.dialOnly, - context: context, - initialTime: - state.durationNotificationValue.durationToTimeOfDay(), - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: true, - ), - child: child!, + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.only( + top: 20, + ), + child: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + _CustomListTile( + onChanged: (bool value) => + BlocProvider.of(context).add( + OpeningNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.openingNotificationEnabled, + title: AppLocalizations.of(context)! + .openingNotificationTitle, + subtitle: AppLocalizations.of(context)! + .openingNotificationExplanation, + leadingIcon: Icons.check_circle, + iconColor: Colors.green, + ), + _CustomListTile( + onChanged: (bool value) => + BlocProvider.of(context).add( + ClosingNotificationStateEvent( + enabled: value, + ), + ), + enabled: state.closingNotificationEnabled, + title: AppLocalizations.of(context)! + .closingNotificationTitle, + subtitle: AppLocalizations.of(context)! + .closingNotificationExplanation, + leadingIcon: Icons.block_rounded, + iconColor: Colors.red, + ), + const SizedBox( + height: 20, + ), + _CustomListTile( + onTap: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state.durationNotificationValue + .durationToTimeOfDay(), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: true, + ), + child: child!, + ); + }, ); + if (time != null) { + // ignore: use_build_context_synchronously + BlocProvider.of(context).add( + DurationNotificationValueEvent( + duration: Duration( + hours: time.hour, + minutes: time.minute, + ), + ), + ); + } }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - DurationNotificationValueEvent( - duration: Duration( - hours: time.hour, - minutes: time.minute, - ), + onChanged: (bool value) => + BlocProvider.of(context).add( + DurationNotificationStateEvent( + enabled: value, ), - ); - } - }, - onChanged: (bool value) => - BlocProvider.of(context).add( - DurationNotificationStateEvent( - enabled: value, + ), + enabled: state.durationNotificationEnabled, + title: AppLocalizations.of(context)! + .durationNotificationTitle( + state.durationNotificationValue + .durationToString(context), + ), + subtitle: AppLocalizations.of(context)! + .durationNotificationExplanation( + state.durationNotificationValue + .durationToString(context), + ), + leadingIcon: Icons.timer_outlined, ), - ), - enabled: state.durationNotificationEnabled, - title: - AppLocalizations.of(context)!.durationNotificationTitle( - state.durationNotificationValue.durationToString(context), - ), - subtitle: AppLocalizations.of(context)! - .durationNotificationExplanation( - state.durationNotificationValue.durationToString(context), - ), - leadingIcon: Icons.timer_outlined, - ), - _CustomListTile( - onTap: () async { - var time = await showTimePicker( - initialEntryMode: TimePickerEntryMode.dialOnly, - context: context, - initialTime: state.timeNotificationValue, - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: false, - ), - child: child!, + _CustomListTile( + onTap: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state.timeNotificationValue, + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: false, + ), + child: child!, + ); + }, ); + if (time != null) { + // ignore: use_build_context_synchronously + BlocProvider.of(context).add( + TimeNotificationValueEvent( + time: TimeOfDay( + hour: time.hour, + minute: time.minute, + ), + ), + ); + } }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - TimeNotificationValueEvent( - time: TimeOfDay( - hour: time.hour, - minute: time.minute, - ), + onChanged: (bool value) => + BlocProvider.of(context).add( + TimeNotificationStateEvent( + enabled: value, ), - ); - } - }, - onChanged: (bool value) => - BlocProvider.of(context).add( - TimeNotificationStateEvent( - enabled: value, + ), + enabled: state.timeNotificationEnabled, + title: + AppLocalizations.of(context)!.timeNotificationTitle( + state.timeNotificationValue.format(context), + ), + subtitle: AppLocalizations.of(context)! + .timeNotificationExplanation( + state.timeNotificationValue.format(context), + ), + leadingIcon: Icons.plus_one_outlined, ), - ), - enabled: state.timeNotificationEnabled, - title: AppLocalizations.of(context)!.timeNotificationTitle( - state.timeNotificationValue.format(context), - ), - subtitle: - AppLocalizations.of(context)!.timeNotificationExplanation( - state.timeNotificationValue.format(context), - ), - leadingIcon: Icons.plus_one_outlined, - ), - _CustomListTile( - onTap: () async { - final day = await showDialog( - context: context, - builder: ( - BuildContext context, - ) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: DaysOfTheWeekDialog( - selectedDay: state.dayNotificationValue), + _CustomListTile( + onTap: () async { + final day = await showDialog( + context: context, + builder: ( + BuildContext context, + ) { + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: DaysOfTheWeekDialog( + selectedDay: state.dayNotificationValue), + ); + }, ); + if (day != null) { + BlocProvider.of(context).add( + DayNotificationValueEvent(day: day), + ); + } }, - ); - if (day != null) { - BlocProvider.of(context).add( - DayNotificationValueEvent(day: day), - ); - } - }, - enabled: state.dayNotificationEnabled, - title: AppLocalizations.of(context)!.dayNotificationTitle( - state.dayNotificationValue.localizedName(context), - ), - subtitle: AppLocalizations.of(context)! - .dayNotificationExplanation( - state.dayNotificationValue.localizedName(context), - state.dayNotificationTimeValue.format(context)), - leadingIcon: Icons.calendar_month_outlined, - onChanged: (bool value) => - BlocProvider.of(context).add( - DayNotificationStateEvent( - enabled: value, - ), - ), - ) - ], - ); - }, - ), - ), + enabled: state.dayNotificationEnabled, + title: AppLocalizations.of(context)!.dayNotificationTitle( + state.dayNotificationValue.localizedName(context), + ), + subtitle: AppLocalizations.of(context)! + .dayNotificationExplanation( + state.dayNotificationValue.localizedName(context), + state.dayNotificationTimeValue.format(context)), + leadingIcon: Icons.calendar_month_outlined, + onChanged: (bool value) => + BlocProvider.of(context).add( + DayNotificationStateEvent( + enabled: value, + ), + ), + ) + ], + ); + }, + ), + ), + ); + }, ); } } diff --git a/lib/widgets/floating_actions/floating_action_item.dart b/lib/widgets/floating_actions/floating_action_item.dart new file mode 100644 index 00000000..d1b2cddf --- /dev/null +++ b/lib/widgets/floating_actions/floating_action_item.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class FloatingActionsItem extends StatelessWidget { + final bool isSpaced; + final List content; + final bool isRightHanded; + final Function()? onPressed; + + const FloatingActionsItem( + {Key? key, + required this.isRightHanded, + this.onPressed, + required this.content, + required this.isSpaced}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return FloatingActionButton.extended( + heroTag: null, + onPressed: onPressed, + label: Wrap( + spacing: isSpaced ? 10 : 0, + children: isRightHanded ? content : content.reversed.toList(), + ), + ); + } +} diff --git a/lib/widgets/floating_actions_widget.dart b/lib/widgets/floating_actions_widget.dart index d1ee135d..b2a5ad9a 100644 --- a/lib/widgets/floating_actions_widget.dart +++ b/lib/widgets/floating_actions_widget.dart @@ -4,30 +4,33 @@ import 'package:chabo/bloc/floating_actions_cubit.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/dialogs/chabo_about_dialog.dart'; import 'package:chabo/screens/notification_screen.dart'; +import 'package:chabo/widgets/floating_actions/floating_action_item.dart'; import 'package:chabo/widgets/theme_switcher_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class FloatingActions extends StatefulWidget { - const FloatingActions({Key? key}) : super(key: key); +class FloatingActionsWidget extends StatefulWidget { + const FloatingActionsWidget({Key? key}) : super(key: key); @override State createState() { - return _FloatingActionsState(); + return _FloatingActionsWidgetState(); } } -class _FloatingActionsState extends State +class _FloatingActionsWidgetState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { return Column( mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: state.isRightHanded + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ AnimatedSwitcher( duration: const Duration( @@ -49,7 +52,7 @@ class _FloatingActionsState extends State ), ); }, - child: state + child: state.isMenuOpen ? BackdropFilter( filter: ImageFilter.blur( sigmaX: CustomProperties.blurSigmaX, @@ -57,11 +60,31 @@ class _FloatingActionsState extends State ), child: Wrap( direction: Axis.vertical, - crossAxisAlignment: WrapCrossAlignment.end, + crossAxisAlignment: state.isRightHanded + ? WrapCrossAlignment.end + : WrapCrossAlignment.start, spacing: 10, children: [ - FloatingActionButton.extended( - heroTag: null, + FloatingActionsItem( + onPressed: () { + context + .read() + .changeFloatingActionsSide(); + }, + content: [ + Text( + state.isRightHanded + ? AppLocalizations.of(context)!.rightHanded + : AppLocalizations.of(context)!.leftHanded, + ), + Icon(state.isRightHanded + ? Icons.back_hand + : Icons.front_hand), + ], + isRightHanded: state.isRightHanded, + isSpaced: true, + ), + FloatingActionsItem( onPressed: () { showModalBottomSheet( useSafeArea: true, @@ -77,20 +100,16 @@ class _FloatingActionsState extends State ); context .read() - .openActions(); + .openFloatingActions(); }, - label: Wrap( - spacing: 10, - children: [ - Text( - AppLocalizations.of(context)!.themeSetting, - ), - const Icon(Icons.format_paint_rounded), - ], - ), + content: [ + Text(AppLocalizations.of(context)!.themeSetting), + const Icon(Icons.format_paint_rounded) + ], + isRightHanded: state.isRightHanded, + isSpaced: true, ), - FloatingActionButton.extended( - heroTag: null, + FloatingActionsItem( onPressed: () async { Navigator.of(context).push( PageRouteBuilder( @@ -119,23 +138,21 @@ class _FloatingActionsState extends State ); context .read() - .openActions(); + .openFloatingActions(); }, - label: Wrap( - spacing: 10, - children: [ - Text( - AppLocalizations.of(context)! - .notificationsTitle, - ), - const Icon( - Icons.notifications_active_outlined, - ), - ], - ), + content: [ + Text( + AppLocalizations.of(context)! + .notificationsTitle, + ), + const Icon( + Icons.notifications_active_outlined, + ), + ], + isRightHanded: state.isRightHanded, + isSpaced: true, ), - FloatingActionButton.extended( - heroTag: null, + FloatingActionsItem( onPressed: () { showDialog( context: context, @@ -151,19 +168,18 @@ class _FloatingActionsState extends State ); context .read() - .openActions(); + .openFloatingActions(); }, - label: Wrap( - spacing: 10, - children: [ - Text( - AppLocalizations.of(context)!.about, - ), - const Icon( - Icons.info_outline, - ), - ], - ), + content: [ + Text( + AppLocalizations.of(context)!.about, + ), + const Icon( + Icons.info_outline, + ), + ], + isRightHanded: state.isRightHanded, + isSpaced: true, ), ], ), @@ -173,61 +189,59 @@ class _FloatingActionsState extends State const SizedBox( height: 25, ), - FloatingActionButton.extended( - heroTag: null, + FloatingActionsItem( onPressed: () { HapticFeedback.lightImpact(); - context.read().openActions(); + context.read().openFloatingActions(); }, - label: Wrap( - alignment: WrapAlignment.center, - children: [ - AnimatedSize( - curve: Curves.easeIn, - duration: const Duration(milliseconds: 200), - reverseDuration: const Duration( + isRightHanded: state.isRightHanded, + content: [ + AnimatedSize( + curve: Curves.easeIn, + duration: const Duration(milliseconds: 200), + reverseDuration: const Duration( + milliseconds: 200, + ), + child: AnimatedSwitcher( + duration: const Duration( milliseconds: 200, ), - child: AnimatedSwitcher( - duration: const Duration( - milliseconds: 200, - ), - reverseDuration: const Duration( - milliseconds: 200, - ), - transitionBuilder: - (Widget child, Animation animation) { - return SlideTransition( - position: Tween( - begin: const Offset(0.5, 0.0), - end: const Offset(0.0, 0.0), - ).animate(animation), - child: FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeIn, - ), - child: child), - ); - }, - child: state - ? Text( - AppLocalizations.of(context)!.settingsTitle, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.start, - ) - : const SizedBox.shrink(), + reverseDuration: const Duration( + milliseconds: 200, ), + transitionBuilder: + (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: Offset(state.isRightHanded ? .5 : -.5, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: FadeTransition( + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: child), + ); + }, + child: state.isMenuOpen + ? Text( + AppLocalizations.of(context)!.settingsTitle, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.start, + ) + : const SizedBox.shrink(), ), - state - ? const Icon( - Icons.close, - ) - : const Icon( - Icons.settings, - ), - ], - ), + ), + state.isMenuOpen + ? const Icon( + Icons.close, + ) + : const Icon( + Icons.settings, + ), + ], + isSpaced: state.isMenuOpen, ), ], ); From 0eeed73c87f92ce9404fb5b6b1b3e2420cbb828a Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sat, 15 Apr 2023 12:46:07 +0200 Subject: [PATCH 42/65] chore: remove unused files --- .../opening_notification_bloc.dart | 41 ------------------- .../opening_notification_event.dart | 13 ------ .../opening_notification_state.dart | 11 ----- 3 files changed, 65 deletions(-) delete mode 100644 lib/bloc/opening_notification/opening_notification_bloc.dart delete mode 100644 lib/bloc/opening_notification/opening_notification_event.dart delete mode 100644 lib/bloc/opening_notification/opening_notification_state.dart diff --git a/lib/bloc/opening_notification/opening_notification_bloc.dart b/lib/bloc/opening_notification/opening_notification_bloc.dart deleted file mode 100644 index 946137cc..00000000 --- a/lib/bloc/opening_notification/opening_notification_bloc.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'opening_notification_event.dart'; -part 'opening_notification_state.dart'; - -class OpeningNotificationBloc - extends Bloc { - final StorageService storageService; - - OpeningNotificationBloc({required this.storageService}) - : super(OpeningNotificationState(enabled: false)) { - on( - _onAppStateChanged, - ); - on( - _onStateChanged, - ); - } - - Future _onStateChanged(event, emit) async { - await storageService.saveBool( - Const.notificationOpeningEnabledKey, event.enabled); - HapticFeedback.lightImpact(); - emit( - state.copyWith(enabled: event.enabled), - ); - } - - Future _onAppStateChanged(event, emit) async { - final enabledValue = - storageService.readBool(Const.notificationOpeningEnabledKey) ?? - Const.notificationOpeningEnabledDefaultValue; - emit( - state.copyWith(enabled: enabledValue), - ); - } -} diff --git a/lib/bloc/opening_notification/opening_notification_event.dart b/lib/bloc/opening_notification/opening_notification_event.dart deleted file mode 100644 index 1395add6..00000000 --- a/lib/bloc/opening_notification/opening_notification_event.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of 'opening_notification_bloc.dart'; - -class OpeningNotificationEvent extends ChaboEvent {} - -class OpeningNotificationChanged extends OpeningNotificationEvent { - final bool enabled; - - OpeningNotificationChanged({required this.enabled}) : super(); -} - -class OpeningAppStateChanged extends OpeningNotificationEvent { - OpeningAppStateChanged() : super(); -} diff --git a/lib/bloc/opening_notification/opening_notification_state.dart b/lib/bloc/opening_notification/opening_notification_state.dart deleted file mode 100644 index b1fa3254..00000000 --- a/lib/bloc/opening_notification/opening_notification_state.dart +++ /dev/null @@ -1,11 +0,0 @@ -part of 'opening_notification_bloc.dart'; - -class OpeningNotificationState { - final bool enabled; - - OpeningNotificationState({required this.enabled}); - - OpeningNotificationState copyWith({bool? enabled}) { - return OpeningNotificationState(enabled: enabled ?? this.enabled); - } -} From c57d08e2e453572cde7e70a50e9fed7305faa617 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 16 Apr 2023 15:37:51 +0200 Subject: [PATCH 43/65] feat(ui): animate the status color transition & add a new text when the bridge is about to close --- .../chaban_bridge_forecast_bloc.dart | 17 ++-- .../chaban_bridge_status_bloc.dart | 30 ++++--- lib/extensions/color_scheme_extension.dart | 4 + lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/widgets/chaban_bridge_status_widget.dart | 83 ++++++++++--------- .../custom_progress_bar_indicator.dart | 47 +++++++++++ 8 files changed, 124 insertions(+), 60 deletions(-) create mode 100644 lib/widgets/custom_progress_bar_indicator.dart diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart index 3cb71f5f..4570e5ad 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -13,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:http/http.dart' as http; part 'chaban_bridge_forecast_event.dart'; - part 'chaban_bridge_forecast_state.dart'; class ChabanBridgeForecastBloc @@ -34,7 +33,8 @@ class ChabanBridgeForecastBloc final currentStatus = _getCurrentStatus(state.chabanBridgeForecasts); final previousStatus = _getPreviousStatus(state.chabanBridgeForecasts, currentStatus); - if (currentStatus != state.currentChabanBridgeForecast) { + if (currentStatus != state.currentChabanBridgeForecast && + currentStatus != previousStatus) { emit( state.copyWith( currentChabanBridgeForecast: currentStatus, @@ -93,11 +93,14 @@ class ChabanBridgeForecastBloc return chabanBridgeForecast[middle]; } if (chabanBridgeForecast.length == 2) { - return chabanBridgeForecast[0] - .circulationClosingDate - .isAfter(DateTime.now()) - ? chabanBridgeForecast[0] - : chabanBridgeForecast[1]; + return chabanBridgeForecast[1] + .circulationClosingDate + .isAfter(DateTime.now()) && + chabanBridgeForecast[0] + .circulationReOpeningDate + .isBefore(DateTime.now()) + ? chabanBridgeForecast[1] + : chabanBridgeForecast[0]; } else if (chabanBridgeForecast[middle] .circulationClosingDate .isAfter(DateTime.now())) { diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart index 637949e5..2ab3a11f 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:equatable/equatable.dart'; @@ -57,18 +58,15 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null) { final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - final differenceStartingPoint = currentChabanBridgeForecast - .circulationReOpeningDate - .difference(DateTime.now()); - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.tertiaryContainer; + if (isOpen && state.durationUntilNextEvent.inMinutes < 120) { + return Theme.of(context).colorScheme.warningColor; } else if (isOpen) { return Colors.green; } else { - return Theme.of(context).colorScheme.errorContainer; + return Theme.of(context).colorScheme.error; } } else { - return Colors.yellow; + return Colors.purple; } } @@ -76,15 +74,10 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null) { final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - final differenceStartingPoint = currentChabanBridgeForecast - .circulationReOpeningDate - .difference(DateTime.now()); - if (isOpen && differenceStartingPoint.inMinutes <= 120) { - return Theme.of(context).colorScheme.onTertiaryContainer; - } else if (isOpen) { + if (isOpen || state.durationUntilNextEvent.inMinutes < 120) { return Theme.of(context).colorScheme.background; } else { - return Theme.of(context).colorScheme.onErrorContainer; + return Theme.of(context).colorScheme.onError; } } else { return Colors.purple; @@ -107,8 +100,13 @@ class ChabanBridgeStatusBloc String _getMainStatus(BuildContext context) { final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null && - !currentChabanBridgeForecast.isCurrentlyClosed()) { + !currentChabanBridgeForecast.isCurrentlyClosed() && + state.durationUntilNextEvent.inMinutes >= 120) { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}'; + } else if (currentChabanBridgeForecast != null && + !currentChabanBridgeForecast.isCurrentlyClosed() && + state.durationUntilNextEvent.inMinutes < 120) { + return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.aboutToClose}'; } else { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; } @@ -154,7 +152,7 @@ class ChabanBridgeStatusBloc (durationUntilNextEvent.inSeconds / durationBetweenPreviousAndNextEvent.inSeconds); } else { - return 1; + return -1; } } diff --git a/lib/extensions/color_scheme_extension.dart b/lib/extensions/color_scheme_extension.dart index 771dca95..6e59678c 100644 --- a/lib/extensions/color_scheme_extension.dart +++ b/lib/extensions/color_scheme_extension.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; extension ColorSchemeExtension on ColorScheme { + MaterialColor get warningColor { + return brightness == Brightness.light ? Colors.orange : Colors.amber; + } + MaterialColor get timeColor { return brightness == Brightness.light ? Colors.orange : Colors.amber; } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 34b8aa3a..4a5cf523 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -12,6 +12,7 @@ "open": "open", "scheduledToOpen": "scheduled to open in", "theBridgeIsCurrently": "the Chaban bridge is", + "aboutToClose": "about to close", "settingsTitle": "Settings", "notificationsTitle": "Notifications", "information": "Information", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 8ad3b893..f9dbf339 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -12,6 +12,7 @@ "open": "abierto", "scheduledToOpen": "programado para abrir en", "theBridgeIsCurrently": "el puente Chaban está", + "aboutToClose": "a punto de cerrarse", "settingsTitle": "Ajustes", "notificationsTitle": "Notificaciónes", "information": "Información", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ff570593..57fb4f12 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -12,6 +12,7 @@ "open": "ouvert", "scheduledToOpen": "ouverture prévue dans", "theBridgeIsCurrently": "le pont Chaban est", + "aboutToClose": "sur le point de fermer", "settingsTitle": "Paramètres", "notificationsTitle": "Notifications", "information": "Information", diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index c4c2dd0a..b5af11c1 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -6,6 +6,7 @@ import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; +import 'package:chabo/widgets/custom_progress_bar_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -52,7 +53,8 @@ class ChabanBridgeStatusWidgetState horizontal: 30, vertical: 10, ), - child: Container( + child: AnimatedContainer( + duration: const Duration(milliseconds: 500), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: state.backgroundColor, @@ -62,18 +64,21 @@ class ChabanBridgeStatusWidgetState ), ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - state.mainMessageStatus, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 40, - color: state.foregroundColor, - ), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + transitionBuilder: + (Widget child, Animation animation) { + return FadeTransition(opacity: animation, child: child); + }, + child: Text( + state.mainMessageStatus, + key: ValueKey(state.mainMessageStatus), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 40, + color: state.foregroundColor, ), - ], + ), ), ), ), @@ -87,33 +92,37 @@ class ChabanBridgeStatusWidgetState fontSize: 20, ), ), - Text( - state.durationUntilNextEvent.durationToString(context), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20.0, vertical: 5), - child: SizedBox( - height: 10, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, + !state.durationUntilNextEvent.isNegative + ? Text( + state.durationUntilNextEvent + .durationToString(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, ), - ), - child: LinearProgressIndicator( - value: state.completionPercentage, - valueColor: AlwaysStoppedAnimation( - state.backgroundColor, + ) + : const SizedBox.shrink(), + state.completionPercentage != -1 + ? Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 5), + child: SizedBox( + height: 10, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + child: CustomProgressBarIndicator( + max: 1, + current: state.completionPercentage, + color: state.backgroundColor, + ), + ), ), - ), - ), - ), - ), + ) + : const SizedBox.shrink(), ], ), ), diff --git a/lib/widgets/custom_progress_bar_indicator.dart b/lib/widgets/custom_progress_bar_indicator.dart new file mode 100644 index 00000000..18060336 --- /dev/null +++ b/lib/widgets/custom_progress_bar_indicator.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class CustomProgressBarIndicator extends StatelessWidget { + final double max; + final double current; + final Color color; + + const CustomProgressBarIndicator( + {Key? key, required this.max, required this.current, required this.color}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (_, boxConstraints) { + var x = boxConstraints.maxWidth; + var percent = (current / max) * x; + return Stack( + alignment: Alignment.centerLeft, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: x, + height: 15, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.inverseSurface, + width: 2, + ), + borderRadius: BorderRadius.circular(35), + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: percent, + height: 10, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(35), + ), + ), + ], + ); + }, + ); + } +} From 715261bd4cf6780d0be59c13d0f768c3891670c4 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 16 Apr 2023 15:45:49 +0200 Subject: [PATCH 44/65] fix(ci): run `dart format lib` && `flutter analyze lib` --- .../flutter.analyze-test.action.yaml | 124 +++++++++--------- .../scroll_status/scroll_status_bloc.dart | 4 - lib/main.dart | 2 +- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index da0ead3d..25391f2d 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -1,63 +1,63 @@ -name: Flutter - Analyze & Test - -on: - workflow_call: - inputs: - flutter_version: - description: 'The Flutter used (ex: 2.5.1)' - required: true - type: string - secrets: - passphrase: - description: 'The passphrase to decrypt the configuration' - required: true - - -jobs: - analyze: - name: 'Analyze' - runs-on: ubuntu-latest - steps: - - name: 'Checkout source code' - uses: actions/checkout@v3 - - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.10.0 - with: - flutter-version: ${{ inputs.flutter_version }} - - name: 'Flutter analyze' - run: flutter analyze - format: - name: 'Format' - runs-on: ubuntu-latest - steps: - - name: 'Checkout source code' - uses: actions/checkout@v3 - - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.10.0 - with: - flutter-version: ${{ inputs.flutter_version }} - - name: 'Flutter format' - run: flutter format lib --set-exit-if-changed - test: - name: 'Test' - runs-on: ubuntu-latest - steps: - - name: 'Checkout source code' - uses: actions/checkout@v3 - - name: 'Decrypt secret configuration' - run: ./.github/scripts/decrypt_secret.sh - env: - PASSPHRASE: ${{ secrets.passphrase }} - - name: 'Check secret configuration' - run: ./.github/scripts/check_secrets_decryption.sh - - name: 'Setup flutter action' - uses: subosito/flutter-action@v2.10.0 - with: - flutter-version: ${{ inputs.flutter_version }} - - name: 'Flutter test (with coverage)' - run: flutter test --coverage --test-randomize-ordering-seed random - - name: 'Upload coverage report' - uses: codecov/codecov-action@v3.1.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} +name: Flutter - Analyze & Test + +on: + workflow_call: + inputs: + flutter_version: + description: 'The Flutter used (ex: 2.5.1)' + required: true + type: string + secrets: + passphrase: + description: 'The passphrase to decrypt the configuration' + required: true + + +jobs: + analyze: + name: 'Analyze' + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v3 + - name: 'Setup flutter action' + uses: subosito/flutter-action@v2.10.0 + with: + flutter-version: ${{ inputs.flutter_version }} + - name: 'Flutter analyze' + run: flutter analyze + format: + name: 'Format' + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v3 + - name: 'Setup flutter action' + uses: subosito/flutter-action@v2.10.0 + with: + flutter-version: ${{ inputs.flutter_version }} + - name: 'Flutter format' + run: dart format lib --set-exit-if-changed + test: + name: 'Test' + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v3 + - name: 'Decrypt secret configuration' + run: ./.github/scripts/decrypt_secret.sh + env: + PASSPHRASE: ${{ secrets.passphrase }} + - name: 'Check secret configuration' + run: ./.github/scripts/check_secrets_decryption.sh + - name: 'Setup flutter action' + uses: subosito/flutter-action@v2.10.0 + with: + flutter-version: ${{ inputs.flutter_version }} + - name: 'Flutter test (with coverage)' + run: flutter test --coverage --test-randomize-ordering-seed random + - name: 'Upload coverage report' + uses: codecov/codecov-action@v3.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} directory: ./coverage/ \ No newline at end of file diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index 27fb2b50..035a871b 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -25,10 +25,6 @@ class ScrollStatusBloc extends Bloc { ); } - - - - Future _onScrollChanged( ScrollStatusChanged event, Emitter emit) async { emit( diff --git a/lib/main.dart b/lib/main.dart index ce277661..d322d42b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,7 @@ void main() async { ); MobileAds.instance.initialize(); - //Bloc.observer = SimpleBlocObserver(); + Bloc.observer = SimpleBlocObserver(); runApp( Chabo( From 6fc0812f88a9108c4d6f415e82018585b7983fdf Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 16 Apr 2023 16:16:15 +0200 Subject: [PATCH 45/65] feat(ui): the color of the current status is managed by the Duration notification value --- .../chaban_bridge_status_bloc.dart | 28 ++++++++++++++++--- .../chaban_bridge_status_event.dart | 8 ++++++ .../chaban_bridge_status_state.dart | 8 ++++++ lib/l10n/app_en.arb | 2 +- lib/l10n/app_es.arb | 2 +- lib/l10n/app_fr.arb | 2 +- lib/main.dart | 4 --- .../chaban_bridge_forecast_screen.dart | 6 ++++ 8 files changed, 49 insertions(+), 11 deletions(-) diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart index 2ab3a11f..cb424ae2 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/const.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; @@ -9,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; part 'chaban_bridge_status_event.dart'; + part 'chaban_bridge_status_state.dart'; class ChabanBridgeStatusBloc @@ -20,6 +22,18 @@ class ChabanBridgeStatusBloc on( _onRefresh, ); + on( + _onDurationChanged, + ); + } + + void _onDurationChanged(ChabanBridgeStatusDurationChanged event, + Emitter emit) { + emit( + state.copyWith( + durationForCloseClosing: event.duration, + ), + ); } void _onChabanBridgeStatusChanged( @@ -58,7 +72,9 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null) { final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - if (isOpen && state.durationUntilNextEvent.inMinutes < 120) { + if (isOpen && + state.durationUntilNextEvent.inMinutes < + state.durationForCloseClosing.inMinutes) { return Theme.of(context).colorScheme.warningColor; } else if (isOpen) { return Colors.green; @@ -74,7 +90,9 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null) { final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - if (isOpen || state.durationUntilNextEvent.inMinutes < 120) { + if (isOpen || + state.durationUntilNextEvent.inMinutes < + state.durationForCloseClosing.inMinutes) { return Theme.of(context).colorScheme.background; } else { return Theme.of(context).colorScheme.onError; @@ -101,11 +119,13 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null && !currentChabanBridgeForecast.isCurrentlyClosed() && - state.durationUntilNextEvent.inMinutes >= 120) { + state.durationUntilNextEvent.inMinutes >= + state.durationForCloseClosing.inMinutes) { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}'; } else if (currentChabanBridgeForecast != null && !currentChabanBridgeForecast.isCurrentlyClosed() && - state.durationUntilNextEvent.inMinutes < 120) { + state.durationUntilNextEvent.inMinutes < + state.durationForCloseClosing.inMinutes) { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.aboutToClose}'; } else { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart index f5b76ef9..3f0ab772 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart @@ -19,3 +19,11 @@ class ChabanBridgeStatusRefresh extends ChabanBridgeStatusEvent { required this.context, }) : super(); } + +class ChabanBridgeStatusDurationChanged extends ChabanBridgeStatusEvent { + final Duration duration; + + ChabanBridgeStatusDurationChanged({ + required this.duration, + }) : super(); +} diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart index dcc5f089..4ff6129e 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart @@ -7,6 +7,7 @@ class ChabanBridgeStatusState extends Equatable { final AbstractChabanBridgeForecast? currentChabanBridgeForecast; final AbstractChabanBridgeForecast? previousChabanBridgeForecast; final Duration durationUntilNextEvent; + final Duration durationForCloseClosing; final Duration? durationBetweenPreviousAndNextEvent; final double completionPercentage; final String mainMessageStatus; @@ -19,6 +20,7 @@ class ChabanBridgeStatusState extends Equatable { required this.currentChabanBridgeForecast, required this.previousChabanBridgeForecast, required this.durationUntilNextEvent, + required this.durationForCloseClosing, required this.durationBetweenPreviousAndNextEvent, required this.completionPercentage, required this.mainMessageStatus, @@ -31,6 +33,7 @@ class ChabanBridgeStatusState extends Equatable { AbstractChabanBridgeForecast? currentChabanBridgeForecast, AbstractChabanBridgeForecast? previousChabanBridgeForecast, Duration? durationUntilNextEvent, + Duration? durationForCloseClosing, Duration? durationBetweenPreviousAndNextEvent, double? completionPercentage, String? mainMessageStatus, @@ -46,6 +49,8 @@ class ChabanBridgeStatusState extends Equatable { previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, durationUntilNextEvent: durationUntilNextEvent ?? this.durationUntilNextEvent, + durationForCloseClosing: + durationForCloseClosing ?? this.durationForCloseClosing, durationBetweenPreviousAndNextEvent: durationBetweenPreviousAndNextEvent ?? this.durationBetweenPreviousAndNextEvent, @@ -62,6 +67,7 @@ class ChabanBridgeStatusState extends Equatable { currentChabanBridgeForecast, previousChabanBridgeForecast, durationUntilNextEvent, + durationForCloseClosing, durationBetweenPreviousAndNextEvent, completionPercentage, mainMessageStatus, @@ -78,6 +84,8 @@ class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { currentChabanBridgeForecast: null, durationUntilNextEvent: Duration.zero, durationBetweenPreviousAndNextEvent: null, + durationForCloseClosing: + Const.notificationDurationValueDefaultValue, chabanBridgeStatusLifespan: ChabanBridgeStatusLifespan.empty, completionPercentage: 0, mainMessageStatus: '', diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4a5cf523..d439ba90 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -50,7 +50,7 @@ "duration": {} } }, - "durationNotificationExplanation": "Receive a notification {duration}before the next closing", + "durationNotificationExplanation": "Receive a notification {duration}before the next closing. This value also manages the color change of the current status", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f9dbf339..74822f77 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -50,7 +50,7 @@ "duration": {} } }, - "durationNotificationExplanation": "Reciba una notificación {duration}antes del próximo cierre", + "durationNotificationExplanation": "Reciba una notificación {duration}antes del próximo cierre. Este valor también gestiona el cambio de color del estado actual", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 57fb4f12..c4cbd3b4 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -50,7 +50,7 @@ "duration": {} } }, - "durationNotificationExplanation": "Recevoir une notification {duration}avant la prochaine fermeture", + "durationNotificationExplanation": "Recevoir une notification {duration}avant la prochaine fermeture. Cette valeur gère aussi le changement de couleur du statur actuel", "@durationNotificationExplanation": { "placeholders": { "duration": {} diff --git a/lib/main.dart b/lib/main.dart index d322d42b..2596d76e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,7 @@ import 'package:chabo/chabo.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; -import 'package:chabo/simple_bloc_observer.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,8 +16,6 @@ void main() async { ); MobileAds.instance.initialize(); - Bloc.observer = SimpleBlocObserver(); - runApp( Chabo( storageService: storageService, diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index b31dbd40..f429ee75 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -71,6 +71,12 @@ class _ChabanBridgeForecastScreenState ), BlocListener( listener: (context, state) async { + BlocProvider.of(context) + .add( + ChabanBridgeStatusDurationChanged( + duration: state.durationNotificationValue, + ), + ); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( From 1c13e656bf70744a1445f13b5128bbf1cb315654 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 16 Apr 2023 16:47:24 +0200 Subject: [PATCH 46/65] feat(ui): add animation when the bridge status is loading --- .../chaban_bridge_status_bloc.dart | 1 + .../chaban_bridge_status_state.dart | 16 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- .../chaban_bridge_forecast_screen.dart | 5 +- lib/widgets/bottom_loader_widget.dart | 16 - lib/widgets/chaban_bridge_status_widget.dart | 295 ++++++++++-------- .../custom_circular_progress_indicator.dart | 36 +++ 9 files changed, 219 insertions(+), 162 deletions(-) delete mode 100644 lib/widgets/bottom_loader_widget.dart create mode 100644 lib/widgets/custom_circular_progress_indicator.dart diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart index cb424ae2..9e3a1aaa 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -65,6 +65,7 @@ class ChabanBridgeStatusBloc mainMessageStatus: mainMessageStatus, timeMessagePrefix: timeMessagePrefix, foregroundColor: foregroundColor, + chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.populated, backgroundColor: backgroundColor)); } diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart index 4ff6129e..74c48b3f 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart @@ -1,9 +1,9 @@ part of 'chaban_bridge_status_bloc.dart'; -enum ChabanBridgeStatusLifespan { empty, populated } +enum ChabanBridgeStatusLifecycle { empty, populated } class ChabanBridgeStatusState extends Equatable { - final ChabanBridgeStatusLifespan chabanBridgeStatusLifespan; + final ChabanBridgeStatusLifecycle chabanBridgeStatusLifecycle; final AbstractChabanBridgeForecast? currentChabanBridgeForecast; final AbstractChabanBridgeForecast? previousChabanBridgeForecast; final Duration durationUntilNextEvent; @@ -16,7 +16,7 @@ class ChabanBridgeStatusState extends Equatable { final Color backgroundColor; const ChabanBridgeStatusState( - {required this.chabanBridgeStatusLifespan, + {required this.chabanBridgeStatusLifecycle, required this.currentChabanBridgeForecast, required this.previousChabanBridgeForecast, required this.durationUntilNextEvent, @@ -29,7 +29,7 @@ class ChabanBridgeStatusState extends Equatable { required this.backgroundColor}); ChabanBridgeStatusState copyWith( - {ChabanBridgeStatusLifespan? chabanBridgeStatusLifespan, + {ChabanBridgeStatusLifecycle? chabanBridgeStatusLifecycle, AbstractChabanBridgeForecast? currentChabanBridgeForecast, AbstractChabanBridgeForecast? previousChabanBridgeForecast, Duration? durationUntilNextEvent, @@ -41,8 +41,8 @@ class ChabanBridgeStatusState extends Equatable { Color? foregroundColor, Color? backgroundColor}) { return ChabanBridgeStatusState( - chabanBridgeStatusLifespan: - chabanBridgeStatusLifespan ?? this.chabanBridgeStatusLifespan, + chabanBridgeStatusLifecycle: + chabanBridgeStatusLifecycle ?? this.chabanBridgeStatusLifecycle, currentChabanBridgeForecast: currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, previousChabanBridgeForecast: @@ -63,7 +63,7 @@ class ChabanBridgeStatusState extends Equatable { @override List get props => [ - chabanBridgeStatusLifespan, + chabanBridgeStatusLifecycle, currentChabanBridgeForecast, previousChabanBridgeForecast, durationUntilNextEvent, @@ -86,7 +86,7 @@ class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { durationBetweenPreviousAndNextEvent: null, durationForCloseClosing: Const.notificationDurationValueDefaultValue, - chabanBridgeStatusLifespan: ChabanBridgeStatusLifespan.empty, + chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.empty, completionPercentage: 0, mainMessageStatus: '', timeMessagePrefix: '', diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d439ba90..f2a2b5d6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -163,5 +163,7 @@ }, "notificationDayChannelName": "Planned closures", "leftHanded": "Left handed", - "rightHanded": "Right handed" + "rightHanded": "Right handed", + "statusLoadMessage": "Loading of the bridge's current status", + "loading": "Loading..." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 74822f77..461ce7ac 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -163,5 +163,7 @@ }, "notificationDayChannelName": "Cierres planificados", "leftHanded": "Zurdo.a", - "rightHanded": "Diestro.a" + "rightHanded": "Diestro.a", + "statusLoadMessage": "Carga del estado actual del puente", + "loading": "Cargando..." } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index c4cbd3b4..5b81818e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -163,5 +163,7 @@ }, "notificationDayChannelName": "Fermetures prévues", "leftHanded": "Gaucher.ère", - "rightHanded": "Droitier.ère" + "rightHanded": "Droitier.ère", + "statusLoadMessage": "Chargement de l'état actuel du pont", + "loading": "Chargement..." } \ No newline at end of file diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index f429ee75..9fa301a4 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -9,6 +9,7 @@ import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; +import 'package:chabo/widgets/custom_circular_progress_indicator.dart'; import 'package:chabo/widgets/floating_actions_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -162,8 +163,8 @@ class _ChabanBridgeForecastScreenState ), ); default: - return const Center( - child: CircularProgressIndicator(), + return CustomCircularProgressIndicator( + message: AppLocalizations.of(context)!.loading, ); } }, diff --git a/lib/widgets/bottom_loader_widget.dart b/lib/widgets/bottom_loader_widget.dart deleted file mode 100644 index 7b94466b..00000000 --- a/lib/widgets/bottom_loader_widget.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class BottomLoaderWidget extends StatelessWidget { - const BottomLoaderWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const Center( - child: SizedBox( - height: 24, - width: 24, - child: CircularProgressIndicator(strokeWidth: 1.5), - ), - ); - } -} diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/chaban_bridge_status_widget.dart index b5af11c1..c744a17d 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/chaban_bridge_status_widget.dart @@ -6,6 +6,7 @@ import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; +import 'package:chabo/widgets/custom_circular_progress_indicator.dart'; import 'package:chabo/widgets/custom_progress_bar_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -44,147 +45,175 @@ class ChabanBridgeStatusWidgetState Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 10, - ), - child: AnimatedContainer( - duration: const Duration(milliseconds: 500), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: state.backgroundColor, - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 800), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.chabanBridgeStatusLifecycle == + ChabanBridgeStatusLifecycle.empty + ? Padding( + padding: EdgeInsets.symmetric( + vertical: MediaQuery.of(context).size.height / 5), + child: CustomCircularProgressIndicator( + message: AppLocalizations.of(context)!.statusLoadMessage, ), - ), - ), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - transitionBuilder: - (Widget child, Animation animation) { - return FadeTransition(opacity: animation, child: child); - }, - child: Text( - state.mainMessageStatus, - key: ValueKey(state.mainMessageStatus), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 40, - color: state.foregroundColor, - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Column( - children: [ - Text( - state.timeMessagePrefix, - style: const TextStyle( - fontSize: 20, - ), - ), - !state.durationUntilNextEvent.isNegative - ? Text( - state.durationUntilNextEvent - .durationToString(context), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ) - : const SizedBox.shrink(), - state.completionPercentage != -1 - ? Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20.0, vertical: 5), - child: SizedBox( - height: 10, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), + ) + : Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 10, + ), + child: AnimatedContainer( + duration: const Duration(milliseconds: 500), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: state.backgroundColor, + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, ), - child: CustomProgressBarIndicator( - max: 1, - current: state.completionPercentage, - color: state.backgroundColor, + ), + ), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + transitionBuilder: + (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, child: child); + }, + child: Text( + state.mainMessageStatus, + key: ValueKey(state.mainMessageStatus), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 40, + color: state.foregroundColor, ), ), ), - ) - : const SizedBox.shrink(), - ], - ), - ), - Flexible( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.showCurrentStatus && - state.currentTarget != null - ? Padding( - padding: const EdgeInsets.only( - left: 10.0, - right: 10.0, - bottom: 15.0, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Column( + children: [ + Text( + state.timeMessagePrefix, + style: const TextStyle( + fontSize: 20, ), - child: ChabanBridgeForecastListItem( - onTap: () => - BlocProvider.of(context) - .add( - GoTo( - goTo: state.currentTarget, - ), - ), - hasPassed: false, - isCurrent: true, - chabanBridgeForecast: state.currentTarget!, - index: -1, + ), + !state.durationUntilNextEvent.isNegative + ? Text( + state.durationUntilNextEvent + .durationToString(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ) + : const SizedBox.shrink(), + state.completionPercentage != -1 + ? Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 5), + child: SizedBox( + height: 10, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + child: CustomProgressBarIndicator( + max: 1, + current: state.completionPercentage, + color: state.backgroundColor, + ), + ), + ), + ) + : const SizedBox.shrink(), + ], + ), + ), + Flexible( + child: BlocBuilder( + builder: (context, state) { + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 800), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: + const Duration(milliseconds: 200), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.showCurrentStatus && + state.currentTarget != null + ? Padding( + padding: const EdgeInsets.only( + left: 10.0, + right: 10.0, + bottom: 15.0, + ), + child: ChabanBridgeForecastListItem( + onTap: () => + BlocProvider.of( + context) + .add( + GoTo( + goTo: state.currentTarget, + ), + ), + hasPassed: false, + isCurrent: true, + chabanBridgeForecast: + state.currentTarget!, + index: -1, + ), + ) + : const SizedBox.shrink(), ), - ) - : const SizedBox.shrink(), - ), - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)!.lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, - ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + AppLocalizations.of(context)! + .lisOfUpcomingClosures, + style: const TextStyle( + fontSize: 20, + ), + ), + const Icon(Icons.arrow_circle_down), + ], + ), + ), + ], ), - const Icon(Icons.arrow_circle_down), - ], - ), - ), - ], + ), ); }, ); diff --git a/lib/widgets/custom_circular_progress_indicator.dart b/lib/widgets/custom_circular_progress_indicator.dart new file mode 100644 index 00000000..23cf3277 --- /dev/null +++ b/lib/widgets/custom_circular_progress_indicator.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class CustomCircularProgressIndicator extends StatelessWidget { + final String message; + + const CustomCircularProgressIndicator({Key? key, required this.message}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + strokeWidth: 5, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + message, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + } +} From eea0b25ddcc790a9700eda2d16f39be68838089b Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Sun, 16 Apr 2023 18:19:16 +0200 Subject: [PATCH 47/65] chore(version): increment version to `v1.3.0` --- CHANGELOG_en.md | 12 ++++++++++++ CHANGELOG_es.md | 12 ++++++++++++ CHANGELOG_fr.md | 12 ++++++++++++ pubspec.yaml | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index 35e5029e..0c05480b 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,15 @@ +# **v1.3.0** : + +- *Fixed*: + - App icon is now displayed correctly on notifications + - The application icon is now displayed on a white background in the "Licenses" window +- *Interface*: + - Disappearance of the settings window in favor of a floating button + - The interface now updates in real time + - Added new transitions / animations +- *Features*: + - Can be used by left-handers +*** # **v1.1.0** : - *Fixed*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 1770af24..c8c5c160 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,15 @@ +# **v1.3.0** : + +- *Fijado*: + - El ícono de la aplicación ahora se muestra correctamente en las notificaciones + - El ícono de la aplicación ahora se muestra sobre un fondo blanco en la ventana "Licencias" +- *Interfaz*: + - Desaparición de la ventana de configuración a favor de un botón flotante + - La interfaz ahora se actualiza en tiempo real + - Se agregaron nuevas transiciones / animaciones. +- *Características*: + - Puede ser utilizado por zurdos +*** # **v1.1.0** : - *Fijado*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 5d280a6e..efc7fe38 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,15 @@ +# **v1.3.0** : + +- *Fix*: + - L'icône de l'application s'affiche maintenant correctement sur les notifications + - L'icône de l'application s'affiche maintenant sur fond blanc dans la fenêtre "Licenses" +- *Interface*: + - Disparition de la fenêtre des réglages au profit d'un bouton flotant + - L'interface se met maintenant à jour en temps réel + - Ajout de nouvelles transitions / animations +- *Fonctionnalités*: + - Utilisable par des gaucher.ères +*** # **v1.1.0** : - *Fix*: diff --git a/pubspec.yaml b/pubspec.yaml index 524f2e87..1ca9e042 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.1.0 +version: 1.3.0 environment: sdk: '>=2.17.6 <3.0.0' From 7ba3939eed6530e51ff1bf9bd9989ae407116260 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 17:50:14 +0000 Subject: [PATCH 48/65] chore(deps): update actions/checkout to v3.5.2 --- .github/workflows/fastlane.action.yaml | 2 +- .github/workflows/flutter.build.action.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 8efcd223..3d0b6cf6 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.5.0 + uses: actions/checkout@v3.5.2 with: fetch-depth: 0 - name: 'Decrypt secret configuration' diff --git a/.github/workflows/flutter.build.action.yaml b/.github/workflows/flutter.build.action.yaml index 5d432630..7bbda5a6 100644 --- a/.github/workflows/flutter.build.action.yaml +++ b/.github/workflows/flutter.build.action.yaml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v3.5.0 + uses: actions/checkout@v3.5.2 with: fetch-depth: 0 - name: 'Decrypt secret configuration' From bebadbc23760821f2db618688d9fcafa5c3fe024 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:56:25 +0000 Subject: [PATCH 49/65] chore(deps): update codecov/codecov-action to v3.1.2 --- .github/workflows/flutter.analyze-test.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index 25391f2d..a12f57f3 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -57,7 +57,7 @@ jobs: - name: 'Flutter test (with coverage)' run: flutter test --coverage --test-randomize-ordering-seed random - name: 'Upload coverage report' - uses: codecov/codecov-action@v3.1.1 + uses: codecov/codecov-action@v3.1.2 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ./coverage/ \ No newline at end of file From 150f4f91c02dfa3f5fd24698e3ec4113481838fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 22:14:02 +0000 Subject: [PATCH 50/65] chore(deps): update fastlane to v2.212.2 --- android/Gemfile | 2 +- android/Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/Gemfile b/android/Gemfile index 6e2c2a1f..b99c1034 100644 --- a/android/Gemfile +++ b/android/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'fastlane', '2.212.1' \ No newline at end of file +gem 'fastlane', '2.212.2' \ No newline at end of file diff --git a/android/Gemfile.lock b/android/Gemfile.lock index b58a16d4..dc6d337a 100644 --- a/android/Gemfile.lock +++ b/android/Gemfile.lock @@ -3,13 +3,13 @@ GEM specs: CFPropertyList (3.0.6) rexml - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) aws-partitions (1.714.0) - aws-sdk-core (3.170.0) + aws-sdk-core (3.170.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -17,7 +17,7 @@ GEM aws-sdk-kms (1.62.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) + aws-sdk-s3 (1.119.2) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.212.1) + fastlane (2.212.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -128,7 +128,7 @@ GEM google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) + google-cloud-errors (1.3.1) google-cloud-storage (1.44.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -213,7 +213,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - fastlane (= 2.212.1) + fastlane (= 2.212.2) BUNDLED WITH 2.3.15 From 7bce914ecc699d7a21e1fbfbfa87d1ad122a2d26 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 21:56:30 +0200 Subject: [PATCH 51/65] feat(notification): change message for the recap notification --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_es.arb | 2 +- lib/l10n/app_fr.arb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f2a2b5d6..87e502d4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -155,7 +155,7 @@ "refreshingNotifications": "Refreshing your notifications", "refreshingNotificationsDone": "Done !", "notificationDayTitle": "\uD83D\uDD2E Closing scheduled", - "notificationDayMessage": "{count, plural, =0{No closures scheduled for next week} =1{1 closure scheduled for next week} other{{count} closures scheduled for next week}}", + "notificationDayMessage": "{count, plural, =0{No closures scheduled for next week} =1{Next week, the Chaban Delmas bridge will only close once} other{Next week, the Chaban Delmas bridge will close {count} times}}", "@notificationDayMessage": { "placeholders": { "count": {} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 461ce7ac..5b676386 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -155,7 +155,7 @@ "refreshingNotifications": "Refrescando tus notificaciones", "refreshingNotificationsDone": "Terminado !", "notificationDayTitle": "\uD83D\uDD2E Cierre programado", - "notificationDayMessage": "{count, plural, =0{No hay cierres programados para la próxima semana} =1{1 cierre programado para la próxima semana} other{{count} cierres programados para la próxima semana}}", + "notificationDayMessage": "{count, plural, =0{No hay cierres programados para la próxima semana} =1{La próxima semana, el puente Chaban Delmas solo cerrará una vez} other{La próxima semana, el puente Chaban Delmas cerrará {count} veces}}", "@notificationDayMessage": { "placeholders": { "count": {} diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5b81818e..5c4b5bad 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -155,7 +155,7 @@ "refreshingNotifications": "Actualisation de vos notifications", "refreshingNotificationsDone": "Terminé !", "notificationDayTitle": "\uD83D\uDD2E Fermetures prévues", - "notificationDayMessage": "{count, plural, =0{Aucune fermetures de prévue pour la semaine prochaine} =1{1 fermeture prévue pour la semaine prochaine} other{{count} fermetures prévues pour la semaine prochaine}}", + "notificationDayMessage": "{count, plural, =0{Aucune fermetures de prévue pour la semaine prochaine} =1{La semaine prochaine, le pont Chaban Delmas ne fermera qu'une seul fois} other{La semaine prochaine, le pont Chaban Delmas fermera a {count} reprises}}", "@notificationDayMessage": { "placeholders": { "count": {} From db7318bed0a93cc93aec0015e71a736980981ec3 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 21:57:55 +0200 Subject: [PATCH 52/65] fix(ui): fix the color of the status when the app is starting --- .../chaban_bridge_status_bloc.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart index 9e3a1aaa..a609182f 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -57,7 +57,8 @@ class ChabanBridgeStatusBloc final Color foregroundColor = _getForegroundColor(event.context); final Color backgroundColor = _getBackgroundColor(event.context); - emit(state.copyWith( + emit( + state.copyWith( durationUntilNextEvent: durationUntilNextEvent, durationBetweenPreviousAndNextEvent: durationBetweenPreviousAndNextEvent, @@ -65,8 +66,13 @@ class ChabanBridgeStatusBloc mainMessageStatus: mainMessageStatus, timeMessagePrefix: timeMessagePrefix, foregroundColor: foregroundColor, - chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.populated, - backgroundColor: backgroundColor)); + chabanBridgeStatusLifecycle: + state.durationUntilNextEvent != Duration.zero // Prevents from displaying the wrong status color + ? ChabanBridgeStatusLifecycle.populated + : ChabanBridgeStatusLifecycle.empty, + backgroundColor: backgroundColor, + ), + ); } Color _getBackgroundColor(BuildContext context) { @@ -83,7 +89,7 @@ class ChabanBridgeStatusBloc return Theme.of(context).colorScheme.error; } } else { - return Colors.purple; + return state.backgroundColor; } } @@ -99,7 +105,7 @@ class ChabanBridgeStatusBloc return Theme.of(context).colorScheme.onError; } } else { - return Colors.purple; + return state.foregroundColor; } } From 8e1346ea84ac2a6aba6a01bc69e748c33987f155 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 21:58:14 +0200 Subject: [PATCH 53/65] fix(notification): fix the recap notification --- lib/service/notification_service.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index d517c19b..4ee75364 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -81,7 +81,7 @@ class NotificationService { tz.initializeTimeZones(); int index = 0; await localNotifications.cancelAll(); - List weekSeparatedChabanBridgeForecast = []; + List weekSeparatedChabanBridgeForecast = []; for (final chabanBridgeForecast in chabanBridgeForecasts) { if (notificationSate.openingNotificationEnabled) { index += 1; @@ -102,22 +102,19 @@ class NotificationService { var last = chabanBridgeForecast.circulationClosingDate .previous(notificationSate.dayNotificationValue.weekPosition); if (weekSeparatedChabanBridgeForecast.isEmpty || - weekSeparatedChabanBridgeForecast.last.circulationClosingDate - .previous( - notificationSate.dayNotificationValue.weekPosition) - .day == - last.day) { - weekSeparatedChabanBridgeForecast.add(chabanBridgeForecast); + weekSeparatedChabanBridgeForecast.last == last) { + weekSeparatedChabanBridgeForecast.add(last); } else { index += 1; await _createDayScheduledNotifications( index, weekSeparatedChabanBridgeForecast.length, - last, + weekSeparatedChabanBridgeForecast.last, notificationSate.dayNotificationTimeValue, context, ); - weekSeparatedChabanBridgeForecast = []; + weekSeparatedChabanBridgeForecast.clear(); + weekSeparatedChabanBridgeForecast.add(last); } } if (notificationSate.durationNotificationEnabled) { From e766836e0039899bcf5c84368d436df8f64cf617 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 21:59:26 +0200 Subject: [PATCH 54/65] chore(format): run `dart format lib` --- .../chaban_bridge_status/chaban_bridge_status_bloc.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart index a609182f..5c069866 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -66,10 +66,10 @@ class ChabanBridgeStatusBloc mainMessageStatus: mainMessageStatus, timeMessagePrefix: timeMessagePrefix, foregroundColor: foregroundColor, - chabanBridgeStatusLifecycle: - state.durationUntilNextEvent != Duration.zero // Prevents from displaying the wrong status color - ? ChabanBridgeStatusLifecycle.populated - : ChabanBridgeStatusLifecycle.empty, + chabanBridgeStatusLifecycle: state.durationUntilNextEvent != + Duration.zero // Prevents from displaying the wrong status color + ? ChabanBridgeStatusLifecycle.populated + : ChabanBridgeStatusLifecycle.empty, backgroundColor: backgroundColor, ), ); From 6a180be9479593ed795f379e6884a1a74733d3bd Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 22:15:44 +0200 Subject: [PATCH 55/65] chore(project): rearrange files --- lib/bloc/app_state_event.dart | 5 ----- lib/{ => bloc}/simple_bloc_observer.dart | 0 lib/chabo.dart | 4 ++-- .../floating_actions_cubit.dart | 0 .../notification_service_cubit.dart | 0 lib/screens/chaban_bridge_forecast_screen.dart | 16 ++++++++-------- lib/screens/notification_screen.dart | 2 +- .../floating_actions_widget.dart | 2 +- .../forecast_list_item_widget.dart} | 4 ++-- .../forecast_list_widget.dart} | 15 +++++++-------- .../status_widget.dart} | 17 ++++++++--------- .../custom_circular_progress_indicator.dart | 0 .../custom_progress_bar_indicator.dart | 0 13 files changed, 29 insertions(+), 36 deletions(-) delete mode 100644 lib/bloc/app_state_event.dart rename lib/{ => bloc}/simple_bloc_observer.dart (100%) rename lib/{bloc => cubits}/floating_actions_cubit.dart (100%) rename lib/{bloc => cubits}/notification_service_cubit.dart (100%) rename lib/widgets/{ => floating_actions}/floating_actions_widget.dart (99%) rename lib/widgets/{chaban_bridge_forecast_list_item.dart => forecast/forecast_list_item_widget.dart} (98%) rename lib/widgets/{chaban_bridge_forecast_list.dart => forecast/forecast_list_widget.dart} (90%) rename lib/widgets/{chaban_bridge_status_widget.dart => forecast/status_widget.dart} (94%) rename lib/widgets/{ => progress_indicator}/custom_circular_progress_indicator.dart (100%) rename lib/widgets/{ => progress_indicator}/custom_progress_bar_indicator.dart (100%) diff --git a/lib/bloc/app_state_event.dart b/lib/bloc/app_state_event.dart deleted file mode 100644 index 3529a3b1..00000000 --- a/lib/bloc/app_state_event.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:chabo/bloc/chabo_event.dart'; - -class AppStateEvent extends ChaboEvent { - AppStateEvent() : super(); -} diff --git a/lib/simple_bloc_observer.dart b/lib/bloc/simple_bloc_observer.dart similarity index 100% rename from lib/simple_bloc_observer.dart rename to lib/bloc/simple_bloc_observer.dart diff --git a/lib/chabo.dart b/lib/chabo.dart index ecf23a64..4f568409 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,8 +1,8 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; -import 'package:chabo/bloc/floating_actions_cubit.dart'; +import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; -import 'package:chabo/bloc/notification_service_cubit.dart'; +import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; diff --git a/lib/bloc/floating_actions_cubit.dart b/lib/cubits/floating_actions_cubit.dart similarity index 100% rename from lib/bloc/floating_actions_cubit.dart rename to lib/cubits/floating_actions_cubit.dart diff --git a/lib/bloc/notification_service_cubit.dart b/lib/cubits/notification_service_cubit.dart similarity index 100% rename from lib/bloc/notification_service_cubit.dart rename to lib/cubits/notification_service_cubit.dart diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 9fa301a4..7963bf93 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -1,16 +1,16 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; -import 'package:chabo/bloc/floating_actions_cubit.dart'; +import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; -import 'package:chabo/bloc/notification_service_cubit.dart'; +import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; -import 'package:chabo/widgets/chaban_bridge_forecast_list.dart'; -import 'package:chabo/widgets/chaban_bridge_status_widget.dart'; -import 'package:chabo/widgets/custom_circular_progress_indicator.dart'; -import 'package:chabo/widgets/floating_actions_widget.dart'; +import 'package:chabo/widgets/forecast/forecast_list_widget.dart'; +import 'package:chabo/widgets/forecast/status_widget.dart'; +import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; +import 'package:chabo/widgets/floating_actions/floating_actions_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -154,10 +154,10 @@ class _ChabanBridgeForecastScreenState ], child: Column( children: const [ - ChabanBridgeStatusWidget(), + StatusWidget(), Expanded( flex: 11, - child: ChabanBridgeForecastList(), + child: ForecastListWidget(), ), ], ), diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 1155db68..2d31bdf0 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -1,6 +1,6 @@ import 'dart:ui'; -import 'package:chabo/bloc/floating_actions_cubit.dart'; +import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; diff --git a/lib/widgets/floating_actions_widget.dart b/lib/widgets/floating_actions/floating_actions_widget.dart similarity index 99% rename from lib/widgets/floating_actions_widget.dart rename to lib/widgets/floating_actions/floating_actions_widget.dart index b2a5ad9a..cbb84889 100644 --- a/lib/widgets/floating_actions_widget.dart +++ b/lib/widgets/floating_actions/floating_actions_widget.dart @@ -1,6 +1,6 @@ import 'dart:ui'; -import 'package:chabo/bloc/floating_actions_cubit.dart'; +import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/dialogs/chabo_about_dialog.dart'; import 'package:chabo/screens/notification_screen.dart'; diff --git a/lib/widgets/chaban_bridge_forecast_list_item.dart b/lib/widgets/forecast/forecast_list_item_widget.dart similarity index 98% rename from lib/widgets/chaban_bridge_forecast_list_item.dart rename to lib/widgets/forecast/forecast_list_item_widget.dart index b493a873..d5bf5c8c 100644 --- a/lib/widgets/chaban_bridge_forecast_list_item.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -9,14 +9,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -class ChabanBridgeForecastListItem extends StatelessWidget { +class ForecastListItemWidget extends StatelessWidget { final AbstractChabanBridgeForecast chabanBridgeForecast; final Function()? onTap; final bool hasPassed; final bool isCurrent; final int index; - const ChabanBridgeForecastListItem( + const ForecastListItemWidget( {Key? key, required this.chabanBridgeForecast, required this.index, diff --git a/lib/widgets/chaban_bridge_forecast_list.dart b/lib/widgets/forecast/forecast_list_widget.dart similarity index 90% rename from lib/widgets/chaban_bridge_forecast_list.dart rename to lib/widgets/forecast/forecast_list_widget.dart index 97596bbc..f0a55003 100644 --- a/lib/widgets/chaban_bridge_forecast_list.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -1,23 +1,22 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; -import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; +import 'package:chabo/widgets/ad_banner_widget.dart'; +import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; -import 'ad_banner_widget.dart'; - -class ChabanBridgeForecastList extends StatefulWidget { - const ChabanBridgeForecastList({super.key}); +class ForecastListWidget extends StatefulWidget { + const ForecastListWidget({super.key}); @override State createState() { - return _ChabanBridgeForecastListState(); + return _ForecastListWidgetState(); } } -class _ChabanBridgeForecastListState extends State { +class _ForecastListWidgetState extends State { @override Widget build(BuildContext context) { return NotificationListener( @@ -35,7 +34,7 @@ class _ChabanBridgeForecastListState extends State { cacheExtent: 5000, padding: const EdgeInsets.all(0), itemBuilder: (BuildContext context, int index) { - return ChabanBridgeForecastListItem( + return ForecastListItemWidget( key: GlobalObjectKey( state.chabanBridgeForecasts[index].hashCode), isCurrent: state.chabanBridgeForecasts[index] == diff --git a/lib/widgets/chaban_bridge_status_widget.dart b/lib/widgets/forecast/status_widget.dart similarity index 94% rename from lib/widgets/chaban_bridge_status_widget.dart rename to lib/widgets/forecast/status_widget.dart index c744a17d..ad741d06 100644 --- a/lib/widgets/chaban_bridge_status_widget.dart +++ b/lib/widgets/forecast/status_widget.dart @@ -5,25 +5,24 @@ import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widgets_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; -import 'package:chabo/widgets/chaban_bridge_forecast_list_item.dart'; -import 'package:chabo/widgets/custom_circular_progress_indicator.dart'; -import 'package:chabo/widgets/custom_progress_bar_indicator.dart'; +import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; +import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; +import 'package:chabo/widgets/progress_indicator/custom_progress_bar_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class ChabanBridgeStatusWidget extends StatefulWidget { - const ChabanBridgeStatusWidget({super.key}); +class StatusWidget extends StatefulWidget { + const StatusWidget({super.key}); @override State createState() { - return ChabanBridgeStatusWidgetState(); + return StatusWidgetState(); } } -class ChabanBridgeStatusWidgetState - extends CustomWidgetState { +class StatusWidgetState extends CustomWidgetState { @override void initState() { SchedulerBinding.instance.addPostFrameCallback( @@ -173,7 +172,7 @@ class ChabanBridgeStatusWidgetState right: 10.0, bottom: 15.0, ), - child: ChabanBridgeForecastListItem( + child: ForecastListItemWidget( onTap: () => BlocProvider.of( context) diff --git a/lib/widgets/custom_circular_progress_indicator.dart b/lib/widgets/progress_indicator/custom_circular_progress_indicator.dart similarity index 100% rename from lib/widgets/custom_circular_progress_indicator.dart rename to lib/widgets/progress_indicator/custom_circular_progress_indicator.dart diff --git a/lib/widgets/custom_progress_bar_indicator.dart b/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart similarity index 100% rename from lib/widgets/custom_progress_bar_indicator.dart rename to lib/widgets/progress_indicator/custom_progress_bar_indicator.dart From 14838fd54a759608900d205967fbda308aba2ddf Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 17 Apr 2023 22:51:09 +0200 Subject: [PATCH 56/65] fix(ad): prevent mobile ad from being load on web build --- lib/widgets/forecast/forecast_list_widget.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index f0a55003..2577fa42 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -3,6 +3,7 @@ import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/widgets/ad_banner_widget.dart'; import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; @@ -58,11 +59,12 @@ class _ForecastListWidgetState extends State { chabanBridgeForecast: state.chabanBridgeForecasts[index + 1], ); } - if ((index % 10 == 0 || - index == - state.chabanBridgeForecasts - .indexOf(state.currentChabanBridgeForecast!)) && - index != 0) { + if (((index % 10 == 0 || + index == + state.chabanBridgeForecasts.indexOf( + state.currentChabanBridgeForecast!)) && + index != 0) && + !kIsWeb) { return const AdBannerWidget(); } return const SizedBox.shrink(); From 056737d17a3f1b975d5a6ef404d94d3461527fd6 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 18 Apr 2023 23:16:26 +0200 Subject: [PATCH 57/65] WIP --- lib/dialogs/days_of_the_week_dialog.dart | 71 +++++++++++++----------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 96294153..7003420d 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -1,6 +1,7 @@ -import 'package:chabo/custom_properties.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class DaysOfTheWeekDialog extends StatelessWidget { final Day selectedDay; @@ -11,46 +12,52 @@ class DaysOfTheWeekDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - insetPadding: const EdgeInsets.symmetric( - horizontal: 20, - ), - titlePadding: const EdgeInsets.all(0), - contentPadding: const EdgeInsets.all(20), - actionsPadding: const EdgeInsets.fromLTRB( - 0, - 0, - 20, - 10, - ), + contentPadding: const EdgeInsets.all(15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 15, ), ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: Day.values - .map( - (day) => RadioListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), - ), - title: Text( - day.localizedName(context), - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - value: day, - groupValue: selectedDay, + content: BlocBuilder( + builder: (context, state) { + return Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 5, + runSpacing: 10, + children: [ + Text('Le '), + DropdownButton( + borderRadius: BorderRadius.circular(12.0), onChanged: (Day? value) { Navigator.pop(context, value); }, + value: Day.friday, + items: Day.values + .map( + (day) => DropdownMenuItem( + value: day, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + day.localizedName(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ) + .toList(), + ), + Text(' à '), + Text( + state.dayNotificationTimeValue.format(context), + style: Theme.of(context).textTheme.titleMedium, ), - ) - .toList(), + ], + ); + }, ), ); } From a7e851cb17a3dda30612516d550d7b05e8ecea05 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 18 Apr 2023 23:24:27 +0200 Subject: [PATCH 58/65] chore(version): increment version to `v1.3.1` --- CHANGELOG_en.md | 6 ++++++ CHANGELOG_es.md | 6 ++++++ CHANGELOG_fr.md | 6 ++++++ pubspec.yaml | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index 0c05480b..ea958a5e 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,9 @@ +# **v1.3.1** : + +- *Fixed*: + - Recap notification now works correctly + - The status color is correctly displayed when starting the application +*** # **v1.3.0** : - *Fixed*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index c8c5c160..8d16c376 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,9 @@ +# **v1.3.1** : + +- *Fijado*: + - La notificación de resumen ahora funciona correctamente + - El color de estado se muestra correctamente al iniciar la aplicación +*** # **v1.3.0** : - *Fijado*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index efc7fe38..97c6668d 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,9 @@ +# **v1.3.1** : + +- *Fix*: + - La notification de recap fonctionne maintenant correctement + - La couleur du status est correctement affichée au démarrage de l'application +*** # **v1.3.0** : - *Fix*: diff --git a/pubspec.yaml b/pubspec.yaml index 1ca9e042..62b17f7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.3.0 +version: 1.3.1 environment: sdk: '>=2.17.6 <3.0.0' From b0b4513a911c199f1fa377b46ab4bc980c8bfc9c Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 20 Apr 2023 13:47:54 +0200 Subject: [PATCH 59/65] fix(store-description): comply with Google Play policies for "misleading claims" --- .../metadata/android/en-GB/full_description.txt | 10 +++++++++- .../metadata/android/en-US/full_description.txt | 10 +++++++++- .../metadata/android/es-ES/full_description.txt | 10 +++++++++- .../metadata/android/fr-FR/full_description.txt | 10 +++++++++- page/privacy/index.html | 6 +++--- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/android/fastlane/metadata/android/en-GB/full_description.txt b/android/fastlane/metadata/android/en-GB/full_description.txt index 61c29ed6..f74bd872 100644 --- a/android/fastlane/metadata/android/en-GB/full_description.txt +++ b/android/fastlane/metadata/android/en-GB/full_description.txt @@ -1 +1,9 @@ -All closing times (for the passage of ships or due to maintenance maintenance) are entered in this application! You can also create custom alerts to receive notifications when the bridge is closed to traffic \ No newline at end of file +All closing schedules (for the passage of ships or for maintenance) are available in Chabo! The application also allows you to be notified of upcoming events up to a week in advance! +Several types of notifications are available: + - At the time of closing + - At the time of opening + - The day of closing at the time you want + - One day before closing at the time you want + - A summary of upcoming closures for the coming week + +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt index 61c29ed6..f74bd872 100644 --- a/android/fastlane/metadata/android/en-US/full_description.txt +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -1 +1,9 @@ -All closing times (for the passage of ships or due to maintenance maintenance) are entered in this application! You can also create custom alerts to receive notifications when the bridge is closed to traffic \ No newline at end of file +All closing schedules (for the passage of ships or for maintenance) are available in Chabo! The application also allows you to be notified of upcoming events up to a week in advance! +Several types of notifications are available: + - At the time of closing + - At the time of opening + - The day of closing at the time you want + - One day before closing at the time you want + - A summary of upcoming closures for the coming week + +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever. \ No newline at end of file diff --git a/android/fastlane/metadata/android/es-ES/full_description.txt b/android/fastlane/metadata/android/es-ES/full_description.txt index 79942cc7..393eb55b 100644 --- a/android/fastlane/metadata/android/es-ES/full_description.txt +++ b/android/fastlane/metadata/android/es-ES/full_description.txt @@ -1 +1,9 @@ -¡Todos los horarios de cierre (para el paso de barcos o para mantenimiento) se ingresan en esta aplicación! También puede crear alertas personalizadas para recibir notificaciones cuando el puente esté cerrado al tráfico \ No newline at end of file +¡Todos los horarios de cierre (por paso de naves o por mantenimiento) están disponibles en Chabo! ¡La aplicación también le permite recibir notificaciones de próximos eventos con hasta una semana de anticipación! +Hay varios tipos de notificaciones disponibles: + - En el momento del cierre + - En el momento de la apertura + - El día de cierre a la hora que quieras + - Un día antes del cierre a la hora que quieras + - Un resumen de los próximos cierres para la próxima semana. + +Descargo de responsabilidad: Chabo utiliza los servicios web proporcionados por Bordeaux Métropole, pero de ninguna manera está afiliado a este último ni a ninguna otra entidad pública. \ No newline at end of file diff --git a/android/fastlane/metadata/android/fr-FR/full_description.txt b/android/fastlane/metadata/android/fr-FR/full_description.txt index 08217790..a932af07 100644 --- a/android/fastlane/metadata/android/fr-FR/full_description.txt +++ b/android/fastlane/metadata/android/fr-FR/full_description.txt @@ -1 +1,9 @@ -Toutes les heures de fermetures (pour le passage de navires ou pour cause de maintenance) sont renseignées dans cette application ! Vous pouvez aussi créer des alertes personnalisées pour recevoir des notifications lorsque le pont est fermé à la circulation \ No newline at end of file +Tous les créneaux de fermeture (pour le passage de navires ou pour cause de maintenance) sont renseignées dans Chabo ! L'application vous permet aussi d'être notifié pour les évènements à venir jusqu'à une semaine à l'avance ! +Plusieurs types de notifications sont disponibles : + - Au moment de la fermeture + - Au moment de l'ouverture + - Le jour de la fermeture au moment où vous le souhaitez + - Un jour avant fermeture à l'heure que vous souhaitez + - Un récapitulatif des fermetures à venir pour la semaine qui arrive + +Avis de non-responsabilité : Chabo utilise les services web fournis par Bordeaux métropole mais n'est en aucun cas affilié à cette dernière ou à aucune autre entité publique que ce soit \ No newline at end of file diff --git a/page/privacy/index.html b/page/privacy/index.html index 2327361f..11eb016f 100644 --- a/page/privacy/index.html +++ b/page/privacy/index.html @@ -113,11 +113,11 @@

Links to Other Sites

sites or services.

Children’s Privacy

-

These Services do not address anyone under the age of 13. I do not knowingly collect +

These Services do not address anyone under the age of 3. I do not knowingly collect personally - identifiable information from children under 13 years of age. In the case I discover that a + identifiable information from children under 3 years of age. In the case I discover that a child - under 13 has provided me with personal information, I immediately delete this from our + under 3 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal From f9609813e14cc2eaa9c976e1752379355df6075a Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 20 Apr 2023 13:51:19 +0200 Subject: [PATCH 60/65] chore(version): increment version to `v1.3.2` --- CHANGELOG_en.md | 5 +++++ CHANGELOG_es.md | 5 +++++ CHANGELOG_fr.md | 5 +++++ pubspec.yaml | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index ea958a5e..fddacd14 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,8 @@ +# **v1.3.2** : + +- *Google play store*: + - Compliance of the description +*** # **v1.3.1** : - *Fixed*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 8d16c376..9f909d42 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,8 @@ +# **v1.3.2** : + +- *Google play store*: + - Cumplimiento de la descripción +*** # **v1.3.1** : - *Fijado*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 97c6668d..de07fa2f 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,8 @@ +# **v1.3.2** : + +- *Google play store*: + - Mise en conformité de la description +*** # **v1.3.1** : - *Fix*: diff --git a/pubspec.yaml b/pubspec.yaml index 62b17f7c..7e63c0a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.3.1 +version: 1.3.2 environment: sdk: '>=2.17.6 <3.0.0' From 71f2e38e111fe31a286a2d3dbb112976be5e3a39 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 20 Apr 2023 22:33:40 +0200 Subject: [PATCH 61/65] feat(recap-notifications): add the possibility to edit the notification scheduled hour --- lib/bloc/notification/notification_bloc.dart | 19 +++++ lib/bloc/notification/notification_event.dart | 6 ++ lib/const.dart | 2 + lib/dialogs/days_of_the_week_dialog.dart | 83 ++++++++++++++----- lib/l10n/app_en.arb | 3 +- lib/l10n/app_es.arb | 3 +- lib/l10n/app_fr.arb | 3 +- 7 files changed, 93 insertions(+), 26 deletions(-) diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index ee0c98ff..8c2639a7 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -43,6 +43,9 @@ class NotificationBloc extends Bloc { on( _onDayNotificationValueEvent, ); + on( + _onDayNotificationTimeValueEvent, + ); on( _onTimeNotificationStateEvent, ); @@ -101,6 +104,17 @@ class NotificationBloc extends Bloc { ); } + Future _onDayNotificationTimeValueEvent( + DayNotificationTimeValueEvent event, + Emitter emit) async { + await storageService.saveTimeOfDay( + Const.notificationDayTimeValueKey, event.time); + HapticFeedback.lightImpact(); + emit( + state.copyWith(dayNotificationTimeValue: event.time), + ); + } + Future _onTimeNotificationStateEvent( TimeNotificationStateEvent event, Emitter emit) async { await storageService.saveBool( @@ -167,6 +181,10 @@ class NotificationBloc extends Bloc { storageService.readDay(Const.notificationDayValueKey) ?? Const.notificationDayValueDefaultValue; + final dayNotificationTimeValue = + storageService.readTimeOfDay(Const.notificationDayTimeValueKey) ?? + Const.notificationDayValueDefaultTimeValue; + final openingNotificationEnabled = storageService.readBool(Const.notificationOpeningEnabledKey) ?? Const.notificationOpeningEnabledDefaultValue; @@ -183,6 +201,7 @@ class NotificationBloc extends Bloc { timeNotificationValue: timeNotificationValue, dayNotificationEnabled: dayNotificationEnabled, dayNotificationValue: dayNotificationValue, + dayNotificationTimeValue: dayNotificationTimeValue, openingNotificationEnabled: openingNotificationEnabled, closingNotificationEnabled: closingNotificationEnabled), ); diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart index 3ed5c2ce..6952ab3a 100644 --- a/lib/bloc/notification/notification_event.dart +++ b/lib/bloc/notification/notification_event.dart @@ -38,6 +38,12 @@ class DayNotificationValueEvent extends NotificationEvent { DayNotificationValueEvent({required this.day}) : super(); } +class DayNotificationTimeValueEvent extends NotificationEvent { + final TimeOfDay time; + + DayNotificationTimeValueEvent({required this.time}) : super(); +} + class DurationNotificationValueEvent extends NotificationEvent { final Duration duration; diff --git a/lib/const.dart b/lib/const.dart index 0a16e187..bb5a34c8 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -50,6 +50,8 @@ class Const { 'NOTIFICATION_DAY_SETTINGS_ENABLED'; static const String notificationDayValueKey = 'NOTIFICATION_DAY_SETTINGS_VALUE'; + static const String notificationDayTimeValueKey = + 'NOTIFICATION_DAY_TIME_SETTINGS_VALUE'; static const String notificationOpeningEnabledKey = 'NOTIFICATION_OPENING_SETTINGS_ENABLED'; static const String notificationClosingEnabledKey = diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 7003420d..8298ccad 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -2,6 +2,7 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class DaysOfTheWeekDialog extends StatelessWidget { final Day selectedDay; @@ -26,35 +27,71 @@ class DaysOfTheWeekDialog extends StatelessWidget { spacing: 5, runSpacing: 10, children: [ - Text('Le '), - DropdownButton( - borderRadius: BorderRadius.circular(12.0), - onChanged: (Day? value) { - Navigator.pop(context, value); - }, - value: Day.friday, - items: Day.values - .map( - (day) => DropdownMenuItem( - value: day, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - day.localizedName(context), - style: const TextStyle( - fontWeight: FontWeight.bold, + ElevatedButton( + onPressed: () {}, + child: DropdownButtonHideUnderline( + child: DropdownButton( + borderRadius: BorderRadius.circular(12.0), + onChanged: (Day? value) { + if (value != null) { + BlocProvider.of(context).add( + DayNotificationValueEvent( + day: value, + ), + ); + } + }, + value: state.dayNotificationValue, + items: Day.values + .map( + (day) => DropdownMenuItem( + value: day, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + day.localizedName(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), ), ), - ), - ), - ) - .toList(), + ) + .toList(), + ), + ), ), - Text(' à '), Text( - state.dayNotificationTimeValue.format(context), + ' ${AppLocalizations.of(context)!.dayNotificationAt} ', style: Theme.of(context).textTheme.titleMedium, ), + ElevatedButton( + onPressed: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state.dayNotificationTimeValue, + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQuery.of(context), + child: child!, + ); + }, + ); + if (time != null) { + // ignore: use_build_context_synchronously + BlocProvider.of(context).add( + DayNotificationTimeValueEvent( + time: time, + ), + ); + } + }, + child: Text( + state.dayNotificationTimeValue.format(context), + style: Theme.of(context).textTheme.titleMedium, + ), + ), ], ); }, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 87e502d4..1b8fc6f0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -165,5 +165,6 @@ "leftHanded": "Left handed", "rightHanded": "Right handed", "statusLoadMessage": "Loading of the bridge's current status", - "loading": "Loading..." + "loading": "Loading...", + "dayNotificationAt": "at" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5b676386..32eae21e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -165,5 +165,6 @@ "leftHanded": "Zurdo.a", "rightHanded": "Diestro.a", "statusLoadMessage": "Carga del estado actual del puente", - "loading": "Cargando..." + "loading": "Cargando...", + "dayNotificationAt": "en las" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5c4b5bad..e72edff6 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -165,5 +165,6 @@ "leftHanded": "Gaucher.ère", "rightHanded": "Droitier.ère", "statusLoadMessage": "Chargement de l'état actuel du pont", - "loading": "Chargement..." + "loading": "Chargement...", + "dayNotificationAt": "à" } \ No newline at end of file From f03e0341831d40569f3c7e2dcaadbdf734bcb17e Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 20 Apr 2023 23:21:38 +0200 Subject: [PATCH 62/65] chore(renovate): change label titles for deps updates --- renovate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renovate.json b/renovate.json index fff694e8..cbaae9c7 100644 --- a/renovate.json +++ b/renovate.json @@ -3,7 +3,7 @@ "packageRules": [ { "description": "Disables the creation of branches/PRs for any minor/patch updates etc. of Renovate bot", - "labels": ["[MINOR UPDATE]", "dependencies"], + "labels": ["minor", "dependencies"], "matchUpdateTypes": [ "minor", "patch", @@ -18,7 +18,7 @@ }, { "description": "Causes the bot to create a PR (and thus, an email notification), whenever there is a new major Renovate version", - "labels": ["[MAJOR UPDATE]", "dependencies"], + "labels": ["major", "dependencies"], "matchUpdateTypes": [ "major" ], From 971e4a2ce8613a751b590e89d4b64c55ca592057 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:21:41 +0000 Subject: [PATCH 63/65] chore(deps): update codecov/codecov-action to v3.1.3 --- .github/workflows/flutter.analyze-test.action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index a12f57f3..f644b952 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -57,7 +57,7 @@ jobs: - name: 'Flutter test (with coverage)' run: flutter test --coverage --test-randomize-ordering-seed random - name: 'Upload coverage report' - uses: codecov/codecov-action@v3.1.2 + uses: codecov/codecov-action@v3.1.3 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ./coverage/ \ No newline at end of file From 4723e62c7d071f192a452ef7cea38d87b32f6a9f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 27 Apr 2023 13:40:05 +0200 Subject: [PATCH 64/65] feat(playstore): edit full description to comply to Google policies --- android/fastlane/metadata/android/en-GB/full_description.txt | 2 +- android/fastlane/metadata/android/en-US/full_description.txt | 2 +- android/fastlane/metadata/android/es-ES/full_description.txt | 2 +- android/fastlane/metadata/android/fr-FR/full_description.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/fastlane/metadata/android/en-GB/full_description.txt b/android/fastlane/metadata/android/en-GB/full_description.txt index f74bd872..108d4bdc 100644 --- a/android/fastlane/metadata/android/en-GB/full_description.txt +++ b/android/fastlane/metadata/android/en-GB/full_description.txt @@ -6,4 +6,4 @@ Several types of notifications are available: - One day before closing at the time you want - A summary of upcoming closures for the coming week -Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever. \ No newline at end of file +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever and does not represent any government entity. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt index f74bd872..108d4bdc 100644 --- a/android/fastlane/metadata/android/en-US/full_description.txt +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -6,4 +6,4 @@ Several types of notifications are available: - One day before closing at the time you want - A summary of upcoming closures for the coming week -Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever. \ No newline at end of file +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever and does not represent any government entity. \ No newline at end of file diff --git a/android/fastlane/metadata/android/es-ES/full_description.txt b/android/fastlane/metadata/android/es-ES/full_description.txt index 393eb55b..17440558 100644 --- a/android/fastlane/metadata/android/es-ES/full_description.txt +++ b/android/fastlane/metadata/android/es-ES/full_description.txt @@ -6,4 +6,4 @@ Hay varios tipos de notificaciones disponibles: - Un día antes del cierre a la hora que quieras - Un resumen de los próximos cierres para la próxima semana. -Descargo de responsabilidad: Chabo utiliza los servicios web proporcionados por Bordeaux Métropole, pero de ninguna manera está afiliado a este último ni a ninguna otra entidad pública. \ No newline at end of file +Descargo de responsabilidad: Chabo utiliza los servicios web proporcionados por Bordeaux Métropole, pero de ninguna manera está afiliado a este último ni a ninguna otra entidad pública y no representa a ninguna entidad gubernamental. \ No newline at end of file diff --git a/android/fastlane/metadata/android/fr-FR/full_description.txt b/android/fastlane/metadata/android/fr-FR/full_description.txt index a932af07..1aed863c 100644 --- a/android/fastlane/metadata/android/fr-FR/full_description.txt +++ b/android/fastlane/metadata/android/fr-FR/full_description.txt @@ -6,4 +6,4 @@ Plusieurs types de notifications sont disponibles : - Un jour avant fermeture à l'heure que vous souhaitez - Un récapitulatif des fermetures à venir pour la semaine qui arrive -Avis de non-responsabilité : Chabo utilise les services web fournis par Bordeaux métropole mais n'est en aucun cas affilié à cette dernière ou à aucune autre entité publique que ce soit \ No newline at end of file +Avis de non-responsabilité : Chabo utilise les services web fournis par Bordeaux métropole mais n'est en aucun cas affilié à cette dernière ou à aucune autre entité publique que ce soit et ne représente aucune entité gouvernementale \ No newline at end of file From b2f3cbe02f13c0a68f85f2ddbad7aaaf2d96dbc4 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Thu, 27 Apr 2023 13:43:58 +0200 Subject: [PATCH 65/65] chore(version): increment version to `v1.4.0` --- CHANGELOG_en.md | 5 +++++ CHANGELOG_es.md | 5 +++++ CHANGELOG_fr.md | 5 +++++ pubspec.yaml | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index fddacd14..e280649e 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,8 @@ +# **v1.4.0** : + +- *Features*: + - It is now possible to choose the time at which weekend recap notifications are sent +*** # **v1.3.2** : - *Google play store*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 9f909d42..a1c663b6 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,8 @@ +# **v1.4.0** : + +- *Características*: + - Ahora es posible elegir la hora a la que se envían las notificaciones de resumen del fin de semana +*** # **v1.3.2** : - *Google play store*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index de07fa2f..f07c095f 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,8 @@ +# **v1.4.0** : + +- *Fonctionnalités*: + - Il est maintenant possible de choisir l'heure à laquelle les notifications de recap de fin de semaine sont envoyées +*** # **v1.3.2** : - *Google play store*: diff --git a/pubspec.yaml b/pubspec.yaml index 7e63c0a3..f05347ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.3.2 +version: 1.4.0 environment: sdk: '>=2.17.6 <3.0.0'