From a029f01e90fcde5ba566c5bfe4eb8b9e59d72c7d Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Fri, 21 Apr 2023 00:24:25 +0200 Subject: [PATCH 01/34] WIP --- .../abstract_chaban_bridge_forecast.dart | 6 +- .../forecast/forecast_list_item_widget.dart | 149 +++++++++++------- .../forecast/forecast_list_widget.dart | 3 +- lib/widgets/forecast/status_widget.dart | 1 + 4 files changed, 101 insertions(+), 58 deletions(-) diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index 5b7d36fc..ac0e02ac 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; abstract class AbstractChabanBridgeForecast extends Equatable { final bool totalClosing; + late bool isDuringTwoDays = false; final ChabanBridgeForecastClosingReason closingReason; late final Duration closedDuration; late final DateTime _circulationClosingDate; @@ -20,11 +21,12 @@ abstract class AbstractChabanBridgeForecast extends Equatable { required this.closingType}) { _circulationClosingDate = circulationClosingDate; - var tmpCirculationReOpeningDate = circulationReOpeningDate; + var tmpCirculationReOpeningDate = circulationReOpeningDate.toLocal(); var tmpDuration = - tmpCirculationReOpeningDate.difference(_circulationClosingDate); + tmpCirculationReOpeningDate.difference(_circulationClosingDate.toLocal()); if (tmpDuration.isNegative) { + isDuringTwoDays = true; tmpCirculationReOpeningDate = tmpCirculationReOpeningDate.add(const Duration(days: 1)); tmpDuration = diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index d5bf5c8c..afa4665f 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -13,6 +13,7 @@ class ForecastListItemWidget extends StatelessWidget { final AbstractChabanBridgeForecast chabanBridgeForecast; final Function()? onTap; final bool hasPassed; + final bool isInterfering; final bool isCurrent; final int index; @@ -22,36 +23,37 @@ class ForecastListItemWidget extends StatelessWidget { required this.index, required this.hasPassed, required this.isCurrent, - this.onTap}) + this.onTap, required this.isInterfering}) : super(key: key); @override Widget build(BuildContext context) { - return Stack( - children: [ - Card( - shape: isCurrent - ? RoundedRectangleBorder( - borderRadius: const BorderRadius.all( - Radius.circular( - 12, - ), - ), - side: BorderSide( - // border color - color: chabanBridgeForecast.getColor(context, false), - // border thickness - width: 2, - ), - ) - : null, - child: InkWell( - borderRadius: const BorderRadius.all( - Radius.circular( - 12, + return Padding( + padding: const EdgeInsets.all(5), + child: Stack( + children: [ + ElevatedButton( + style: ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(CustomProperties.borderRadius), + side: isCurrent + ? BorderSide( + width: 2, + color: chabanBridgeForecast.getColor(context, false), + ) + : BorderSide.none, + ), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.only( + right: 0, + ), ), ), - onTap: onTap ?? + onPressed: onTap ?? () async => { await showGeneralDialog( context: context, @@ -80,16 +82,34 @@ class ForecastListItemWidget extends StatelessWidget { ), ) }, - child: ListTile( - horizontalTitleGap: 0, - leading: chabanBridgeForecast.getIconWidget(context, false), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Container( + width: 65, + height: 60, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15), + ), + color: chabanBridgeForecast.getColor(context, false), + ), + child: Center(child: chabanBridgeForecast.getIconWidget(context, true)), + ), + Flexible( child: Column( mainAxisSize: MainAxisSize.min, children: [ + Text( + MaterialLocalizations.of(context).formatMediumDate( + chabanBridgeForecast.circulationClosingDate, + ), + style: Theme.of(context).textTheme.bodySmall, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -99,15 +119,10 @@ class ForecastListItemWidget extends StatelessWidget { chabanBridgeForecast.circulationClosingDateString( context, ), + style: Theme.of(context).textTheme.bodyLarge, ), ], ), - Text( - MaterialLocalizations.of(context).formatMediumDate( - chabanBridgeForecast.circulationClosingDate, - ), - style: Theme.of(context).textTheme.bodyMedium, - ) ], ), ), @@ -136,6 +151,12 @@ class ForecastListItemWidget extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ + Text( + MaterialLocalizations.of(context).formatMediumDate( + chabanBridgeForecast.circulationReOpeningDate, + ), + style: Theme.of(context).textTheme.bodySmall, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -146,40 +167,58 @@ class ForecastListItemWidget extends StatelessWidget { .circulationReOpeningDateString( context, ), + style: Theme.of(context).textTheme.bodyLarge, ), + if (chabanBridgeForecast.isDuringTwoDays) + Text('(j +1)', + style: Theme.of(context).textTheme.bodySmall, + ), ], ), - Text( - MaterialLocalizations.of(context).formatMediumDate( - chabanBridgeForecast.circulationReOpeningDate, - ), - style: Theme.of(context).textTheme.bodyMedium, - ) ], ), ), + isInterfering ? + Container( + width: 30, + height: 60, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + color: Theme.of(context).colorScheme.timeColor, + ), + child: Icon( + Icons.warning_rounded, + size: 20, + color: Theme.of(context).cardColor, + ), + ) : Container(width: 15,), ], ), ), ), - ), - if (hasPassed) - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: Center( - child: Text( - AppLocalizations.of(context)!.passedClosure, - style: const TextStyle(fontWeight: FontWeight.bold), + if (hasPassed) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: Center( + child: Text( + AppLocalizations.of(context)!.passedClosure, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), ), ), ), ), - ), - ], + ], + ), ); } } diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index 2577fa42..ebcc5f77 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -44,7 +44,8 @@ class _ForecastListWidgetState extends State { .chabanBridgeForecasts[index].circulationReOpeningDate .isBefore(DateTime.now()), chabanBridgeForecast: state.chabanBridgeForecasts[index], - index: index); + index: index, + isInterfering: index % 2 == 0,); }, itemCount: state.chabanBridgeForecasts.length, controller: diff --git a/lib/widgets/forecast/status_widget.dart b/lib/widgets/forecast/status_widget.dart index ad741d06..4f4d12b7 100644 --- a/lib/widgets/forecast/status_widget.dart +++ b/lib/widgets/forecast/status_widget.dart @@ -186,6 +186,7 @@ class StatusWidgetState extends CustomWidgetState { chabanBridgeForecast: state.currentTarget!, index: -1, + isInterfering: false, ), ) : const SizedBox.shrink(), From 68e1c504d0e1cbf9633f5fc5f9c57850e6e728e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 30 Apr 2023 15:35:27 +0000 Subject: [PATCH 02/34] chore(deps): update kotlin_version to v1.8.21 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 6b10a38f..13b3050e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.20' + ext.kotlin_version = '1.8.21' repositories { google() mavenCentral() From a0487058791b858d34d667d1f056e7aaadc43c0f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:34:39 +0000 Subject: [PATCH 03/34] chore(deps): update ruby/setup-ruby to v1.148.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 3d0b6cf6..93accf78 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.146.0 + uses: ruby/setup-ruby@v1.148.0 with: ruby-version: '3.0' bundler-cache: true From 4f66ce873e5b26132224e644207b6585fa6b82e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 30 Apr 2023 22:37:47 +0000 Subject: [PATCH 04/34] chore(deps): update flutter_local_notifications to v14 --- pubspec.lock | 12 ++++++------ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d07d37c3..05cd7240 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,26 +202,26 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "293995f94e120c8afce768981bd1fa9c5d6de67c547568e3b42ae2defdcbb4a0" + sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.0.0+1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: ccb08b93703aeedb58856e5637450bf3ffec899adb66dc325630b68994734b89 + sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7" url: "https://pub.dev" source: hosted - version: "3.0.0+1" + version: "4.0.0" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "5ec1feac5f7f7d9266759488bc5f76416152baba9aa1b26fe572246caa00d1ab" + sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "7.0.0" flutter_localizations: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index f05347ba..0870638e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: flutter_bloc: ^8.0.0 flutter_localizations: sdk: flutter - flutter_local_notifications: ^13.0.0 + flutter_local_notifications: ^14.0.0 flutter_markdown: ^0.6.14 font_awesome_flutter: ^10.1.0 google_fonts: ^4.0.0 From 99f5ee51715a230ad57ebed1e67d0571afcf192f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 30 Apr 2023 22:40:02 +0000 Subject: [PATCH 05/34] chore(deps): update google_mobile_ads to v3 --- pubspec.lock | 36 ++++++++++++++++++++++++++++++++++-- pubspec.yaml | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d07d37c3..017ba367 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -265,10 +265,10 @@ packages: dependency: "direct main" description: name: google_mobile_ads - sha256: "7a39fe63007764115395550fc3eaf0d469de685298208040138c373d5444ab48" + sha256: "24ee4e9546866cc15ebe565dabf6a6c485ce5fbec0645fde22799800532000f0" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "3.0.0" http: dependency: "direct main" description: @@ -698,6 +698,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: d6cf18cd6c809c5a9294cd99707a21986aac4e08c87e1916ce2590315fb55d3a + url: "https://pub.dev" + source: hosted + version: "3.6.2" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "78715dc442b7849dbde74e92bb67de1cecf5addf95531c5fb474e72f5fe9a507" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: c94d242d8cbe1012c06ba7ac790c46d6e6b68723b7d34f8c74ed19f68d166f49 + url: "https://pub.dev" + source: hosted + version: "3.4.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f05347ba..fd39bd36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter_markdown: ^0.6.14 font_awesome_flutter: ^10.1.0 google_fonts: ^4.0.0 - google_mobile_ads: ^2.3.0 + google_mobile_ads: ^3.0.0 http: ^0.13.0 intl: ^0.17.0 package_info_plus: ^3.0.3 From 317060477f2f737fb2e10b2e504b9dde960dce8f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 24 Apr 2023 21:32:13 +0200 Subject: [PATCH 06/34] chore(refacto): remove unnecessary parameter --- lib/dialogs/days_of_the_week_dialog.dart | 3 +-- lib/screens/notification_screen.dart | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 8298ccad..9d28a104 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -5,9 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class DaysOfTheWeekDialog extends StatelessWidget { - final Day selectedDay; - const DaysOfTheWeekDialog({Key? key, required this.selectedDay}) + const DaysOfTheWeekDialog({Key? key}) : super(key: key); @override diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 2d31bdf0..655c9fae 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -197,8 +197,7 @@ class _NotificationScreenState extends CustomWidgetState { filter: ImageFilter.blur( sigmaX: CustomProperties.blurSigmaX, sigmaY: CustomProperties.blurSigmaY), - child: DaysOfTheWeekDialog( - selectedDay: state.dayNotificationValue), + child: const DaysOfTheWeekDialog(), ); }, ); From deeb8e11dc2f60b3236f3a099bf593b3965d95d5 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:12:24 +0200 Subject: [PATCH 07/34] feat(time-slot): add storage capabilities for timeslots --- lib/service/storage_service.dart | 34 +++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 223d8610..28c8bcde 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -1,7 +1,9 @@ +import 'dart:convert'; import 'dart:developer' as developer; import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/enums/theme_state_status.dart'; +import 'package:chabo/models/time_slot.dart'; import 'package:enum_to_string/enum_to_string.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -48,6 +50,13 @@ class StorageService { return await sharedPreferences.setString(key, value.name); } + Future saveTimeSlots(String key, List timeSlots) async { + developer.log('{$key: $timeSlots}', + name: 'storage-service.on.saveTimeSlots'); + + return await sharedPreferences.setString(key, jsonEncode(timeSlots)); + } + String? readString(String key) { final value = sharedPreferences.getString(key); developer.log('{$key: $value}', name: 'storage-service.on.readString'); @@ -60,17 +69,6 @@ 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) { @@ -117,4 +115,18 @@ class StorageService { return value; } } + + List? readTimeSlots(String key) { + final stringValue = sharedPreferences.getString(key); + if (stringValue == null) { + return null; + } else { + final list = json.decode(stringValue); + final List timeSlotList = + list.map((item) => TimeSlot.fromJSON(item)).toList(); + developer.log('{$key: $timeSlotList', + name: 'storage-service.on.readTimeSlots'); + return timeSlotList; + } + } } From fd856c7701d60c346d4d53cc787a9cc34b453dbb Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:12:46 +0200 Subject: [PATCH 08/34] feat(time-slot): add bloc --- lib/bloc/time_slot/time_slot_bloc.dart | 68 +++++++++++++++++++++++++ lib/bloc/time_slot/time_slot_event.dart | 21 ++++++++ lib/bloc/time_slot/time_slot_state.dart | 23 +++++++++ 3 files changed, 112 insertions(+) create mode 100644 lib/bloc/time_slot/time_slot_bloc.dart create mode 100644 lib/bloc/time_slot/time_slot_event.dart create mode 100644 lib/bloc/time_slot/time_slot_state.dart diff --git a/lib/bloc/time_slot/time_slot_bloc.dart b/lib/bloc/time_slot/time_slot_bloc.dart new file mode 100644 index 00000000..7ac78fbf --- /dev/null +++ b/lib/bloc/time_slot/time_slot_bloc.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/const.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:chabo/service/storage_service.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'time_slot_event.dart'; + +part 'time_slot_state.dart'; + +class TimeSlotBloc extends Bloc { + final StorageService storageService; + + TimeSlotBloc({required this.storageService}) + : super(TimeSlotState( + timeSlots: Const.notificationFavoriteSlotsDefaultValue, + enabledForNotifications: + Const.notificationFavoriteSlotsEnabledDefaultValue)) { + on( + _onAppEvent, + ); + on( + _onEnabledTimeSlotEvent, + ); + on( + _onTimeSlotsEventValue, + ); + } + + Future _onAppEvent( + TimeSlotAppEvent event, Emitter emit) async { + final timeSlots = + storageService.readTimeSlots(Const.notificationFavoriteSlotsValueKey) ?? + Const.notificationFavoriteSlotsDefaultValue; + + final enabledForNotifications = + storageService.readBool(Const.notificationFavoriteSlotsEnabledKey) ?? + Const.notificationFavoriteSlotsEnabledDefaultValue; + emit(state.copyWith( + timeSlots: timeSlots, + enabledForNotifications: enabledForNotifications)); + } + + Future _onEnabledTimeSlotEvent( + EnabledTimeSlotEvent event, Emitter emit) async { + await storageService.saveBool( + Const.notificationFavoriteSlotsEnabledKey, event.enabled); + HapticFeedback.lightImpact(); + + emit(state.copyWith(enabledForNotifications: event.enabled)); + } + + Future _onTimeSlotsEventValue( + ValueTimeSlotEvent event, Emitter emit) async { + final timeSlots = List.from(state.timeSlots); + timeSlots[event.index] = event.timeSlot; + await storageService.saveTimeSlots( + Const.notificationFavoriteSlotsValueKey, timeSlots); + HapticFeedback.lightImpact(); + + emit(state.copyWith(timeSlots: timeSlots)); + } +} diff --git a/lib/bloc/time_slot/time_slot_event.dart b/lib/bloc/time_slot/time_slot_event.dart new file mode 100644 index 00000000..d658c68a --- /dev/null +++ b/lib/bloc/time_slot/time_slot_event.dart @@ -0,0 +1,21 @@ +part of 'time_slot_bloc.dart'; + +@immutable +abstract class TimeSlotEvent extends ChaboEvent {} + +class EnabledTimeSlotEvent extends TimeSlotEvent { + final bool enabled; + + EnabledTimeSlotEvent({required this.enabled}) : super(); +} + +class ValueTimeSlotEvent extends TimeSlotEvent { + final TimeSlot timeSlot; + final int index; + + ValueTimeSlotEvent({required this.timeSlot, required this.index}) : super(); +} + +class TimeSlotAppEvent extends TimeSlotEvent { + TimeSlotAppEvent() : super(); +} diff --git a/lib/bloc/time_slot/time_slot_state.dart b/lib/bloc/time_slot/time_slot_state.dart new file mode 100644 index 00000000..215c6886 --- /dev/null +++ b/lib/bloc/time_slot/time_slot_state.dart @@ -0,0 +1,23 @@ +part of 'time_slot_bloc.dart'; + +class TimeSlotState extends Equatable { + final List timeSlots; + final bool enabledForNotifications; + + const TimeSlotState( + {required this.timeSlots, required this.enabledForNotifications}); + + TimeSlotState copyWith({ + List? timeSlots, + bool? enabledForNotifications, + }) { + return TimeSlotState( + timeSlots: timeSlots ?? this.timeSlots, + enabledForNotifications: + enabledForNotifications ?? this.enabledForNotifications, + ); + } + + @override + List get props => [timeSlots, enabledForNotifications]; +} From 59dfd8c8d1df377f1f64aacd3e759127c1ef3cc3 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:13:27 +0200 Subject: [PATCH 09/34] feat(time-slot): add translations --- lib/l10n/app_en.arb | 13 ++++++++++++- lib/l10n/app_es.arb | 13 ++++++++++++- lib/l10n/app_fr.arb | 14 +++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1b8fc6f0..a179ac3d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -166,5 +166,16 @@ "rightHanded": "Right handed", "statusLoadMessage": "Loading of the bridge's current status", "loading": "Loading...", - "dayNotificationAt": "at" + "dayNotificationAt": "at", + "favoriteSlotsFrom": "From", + "favoriteSlotsTo": "to", + "favoriteSlots": "My favorite time slots", + "favoriteSlotsDescription": "You can fill in two time slots during which the events of the Chaban bridge are likely to impact you", + "favoriteTimeSlotDefaultName": "Time slot n°{index}", + "@favoriteTimeSlotDefaultName": { + "placeholders": { + "index": {} + } + }, + "favoriteSlotsInterferenceWarning": "This schedule interferes with one or more time slots" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 32eae21e..28c40926 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -166,5 +166,16 @@ "rightHanded": "Diestro.a", "statusLoadMessage": "Carga del estado actual del puente", "loading": "Cargando...", - "dayNotificationAt": "en las" + "dayNotificationAt": "en las", + "favoriteSlotsFrom": "De", + "favoriteSlotsTo": "a", + "favoriteSlots": "Mis franjas horarias favoritas", + "favoriteSlotsDescription": "Puede completar dos intervalos de tiempo durante los cuales es probable que los eventos del puente Chaban lo afecten", + "favoriteTimeSlotDefaultName": "Franja horaria n°{index}", + "@favoriteTimeSlotDefaultName": { + "placeholders": { + "index": {} + } + }, + "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o más intervalos de tiempo" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e72edff6..61544ea8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -166,5 +166,17 @@ "rightHanded": "Droitier.ère", "statusLoadMessage": "Chargement de l'état actuel du pont", "loading": "Chargement...", - "dayNotificationAt": "à" + "dayNotificationAt": "à", + "favoriteSlotsFrom": "De", + "favoriteSlotsTo": "à", + "favoriteSlots": "Mes créneaux horaires favoris", + "favoriteSlotsDescription": "Vous pouvez renseigner deux créneaux durant lesquels les évènements du pont Chaban risques de vous impacter", + "favoriteTimeSlotDefaultName": "Créneau n°{index}", + "@favoriteTimeSlotDefaultName": { + "placeholders": { + "index": {} + } + }, + "favoriteSlotsInterferenceWarning": "Cette prévision interfère avec un ou plusieurs créneaux" + } \ No newline at end of file From e10b5c3d5aa87f9ca08e9f118348aaaa3c2f09d1 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:14:44 +0200 Subject: [PATCH 10/34] chore(format): run `dart format lib` --- lib/dialogs/days_of_the_week_dialog.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 9d28a104..75cf13a5 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -5,9 +5,7 @@ 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); + const DaysOfTheWeekDialog({Key? key}) : super(key: key); @override Widget build(BuildContext context) { From 946ca64539b3ad9838b4a1b63eef7e09a8978fb9 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:15:12 +0200 Subject: [PATCH 11/34] ui(border): change the global border radius to `17` --- lib/custom_properties.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_properties.dart b/lib/custom_properties.dart index 8d7e1182..f4a70526 100644 --- a/lib/custom_properties.dart +++ b/lib/custom_properties.dart @@ -1,5 +1,5 @@ class CustomProperties { - static const double borderRadius = 12; + static const double borderRadius = 17; static const int animationDurationMs = 200; static const double blurSigmaX = 4; From cd13d06a0acfdcd429ada3c673ddcffab890febe Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:15:48 +0200 Subject: [PATCH 12/34] feat(time-slot): add const related to time slots --- lib/const.dart | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/const.dart b/lib/const.dart index bb5a34c8..1dec4bdf 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -1,5 +1,6 @@ import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/link_icon.dart'; +import 'package:chabo/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -57,6 +58,10 @@ class Const { static const String notificationClosingEnabledKey = 'NOTIFICATION_CLOSING_SETTINGS_ENABLED'; static const String isRightHandedKey = 'RIGHT_HANDED'; + static const String notificationFavoriteSlotsEnabledKey = + 'NOTIFICATION_FAVORITE_SLOTS_SETTINGS_ENABLED'; + static const String notificationFavoriteSlotsValueKey = + 'NOTIFICATION_FAVORITE_SLOTS_SETTINGS_VALUE'; /// Notifications static const String androidAppLogoPath = @@ -73,6 +78,25 @@ class Const { static const bool notificationDayEnabledDefaultValue = false; static const bool notificationOpeningEnabledDefaultValue = false; static const bool notificationClosingEnabledDefaultValue = false; + static const bool notificationFavoriteSlotsEnabledDefaultValue = false; + static List notificationFavoriteSlotsDefaultValue = [ + const TimeSlot( + name: '', + from: TimeOfDay(hour: 7, minute: 0), + to: TimeOfDay( + hour: 9, + minute: 30, + ), + ), + const TimeSlot( + name: '', + from: TimeOfDay(hour: 17, minute: 0), + to: TimeOfDay( + hour: 19, + minute: 30, + ), + ) + ]; /// UI static const bool isRightHandedDefaultValue = true; From b12a6e6941686fd48ef0eed22fab26c5798bfe31 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:16:38 +0200 Subject: [PATCH 13/34] ui(icon): change the icon size (to large for english time) --- lib/models/chaban_bridge_boat_forecast.dart | 10 +++++----- lib/models/chaban_bridge_maintenance_forecast.dart | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index 02b95c5e..4584b1d9 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -199,31 +199,31 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { child: Icon( Icons.directions_boat_rounded, color: getColor(context, reversed), - size: 30, + size: 25, ), ), Positioned( right: 0, - top: 0, + top: -3, child: RotatedBox( quarterTurns: boats[0].isLeaving ? 0 : 2, child: Icon( Icons.double_arrow_rounded, color: getColor(context, reversed), - size: 18, + size: 15, ), ), ), boats.length == 2 ? Positioned( right: 0, - top: 14, + top: 10, child: RotatedBox( quarterTurns: boats[1].isLeaving ? 0 : 2, child: Icon( Icons.double_arrow_rounded, color: getColor(context, reversed), - size: 18, + size: 15, ), ), ) diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index d4f5ae5e..62c54e3a 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -160,7 +160,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { return Icon( Icons.construction_rounded, color: getColor(context, reversed), - size: 30, + size: 25, ); } From a9f43d446e7748aefc28a7f6b367bcbcd154705f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:17:03 +0200 Subject: [PATCH 14/34] feat(time-slot): add the bloc provider --- lib/chabo.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/chabo.dart b/lib/chabo.dart index 4f568409..dd0be7aa 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,5 +1,6 @@ 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/time_slot/time_slot_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/cubits/notification_service_cubit.dart'; @@ -81,6 +82,15 @@ class Chabo extends StatelessWidget { AppEvent(), ), ), + + /// Bloc intended to manage favorite time slots + BlocProvider( + create: (_) => TimeSlotBloc( + storageService: storageService, + )..add( + TimeSlotAppEvent(), + ), + ), ], child: BlocBuilder( builder: (context, state) { From 025db2cbc7c5ea0458a844f7284484d95cb09bbd Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:17:43 +0200 Subject: [PATCH 15/34] feat(time-slot): add the info of interfering into the info dialog --- ...an_bridge_forecast_information_dialog.dart | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart index 21f75ea5..11f46aac 100644 --- a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart +++ b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart @@ -1,3 +1,4 @@ +import 'package:chabo/extensions/color_scheme_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'; @@ -15,7 +16,7 @@ class ChabanBridgeForecastInformationDialog extends StatelessWidget { horizontal: 20, ), titlePadding: const EdgeInsets.all(0), - contentPadding: const EdgeInsets.all(20), + contentPadding: const EdgeInsets.all(0), actionsPadding: const EdgeInsets.fromLTRB( 0, 0, @@ -59,7 +60,45 @@ class ChabanBridgeForecastInformationDialog extends StatelessWidget { 15, ), ), - content: chabanBridgeForecast.getInformationWidget(context), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: chabanBridgeForecast.getInformationWidget(context), + ), + if (chabanBridgeForecast.interferingTimeSlots.isNotEmpty) + Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.warningColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular( + 15.0, + ), + bottomRight: Radius.circular( + 15.0, + ), + ), + ), + child: Row( + children: [ + const SizedBox(width: 10), + Flexible( + child: Text( + AppLocalizations.of(context)! + .favoriteSlotsInterferenceWarning, + overflow: TextOverflow.clip, + style: TextStyle( + color: Theme.of(context).cardColor, + ), + ), + ), + ], + ), + ), + ], + ), ); } } From 436bb997e90dc4073e96d156d41723abe57160c5 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:17:56 +0200 Subject: [PATCH 16/34] feat(time-slot): add widgets --- lib/dialogs/time_slot_dialog.dart | 103 +++++++++++++++++++++++++++ lib/models/time_slot.dart | 39 ++++++++++ lib/models/time_slots.dart | 7 ++ lib/screens/notification_screen.dart | 57 ++++++++++++++- lib/widgets/time_slot_widget.dart | 63 ++++++++++++++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 lib/dialogs/time_slot_dialog.dart create mode 100644 lib/models/time_slot.dart create mode 100644 lib/models/time_slots.dart create mode 100644 lib/widgets/time_slot_widget.dart diff --git a/lib/dialogs/time_slot_dialog.dart b/lib/dialogs/time_slot_dialog.dart new file mode 100644 index 00000000..4e4cbc1e --- /dev/null +++ b/lib/dialogs/time_slot_dialog.dart @@ -0,0 +1,103 @@ +import 'package:chabo/bloc/time_slot/time_slot_bloc.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class TimeSlotDialog extends StatelessWidget { + final int index; + + const TimeSlotDialog({Key? key, required this.index}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.all(15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 15, + ), + ), + content: BlocBuilder( + builder: (context, state) { + return Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 5, + runSpacing: 10, + children: [ + Text( + ' ${AppLocalizations.of(context)!.favoriteSlotsFrom} ', + style: Theme.of(context).textTheme.titleMedium, + ), + ElevatedButton( + onPressed: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state.timeSlots[index].from, + 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( + ValueTimeSlotEvent( + timeSlot: TimeSlot( + name: state.timeSlots[index].name, + from: time, + to: state.timeSlots[index].to), + index: index), + ); + } + }, + child: Text( + state.timeSlots[index].from.format(context), + style: Theme.of(context).textTheme.titleMedium, + ), + ), + Text( + ' ${AppLocalizations.of(context)!.favoriteSlotsTo} ', + style: Theme.of(context).textTheme.titleMedium, + ), + ElevatedButton( + onPressed: () async { + var time = await showTimePicker( + initialEntryMode: TimePickerEntryMode.dialOnly, + context: context, + initialTime: state.timeSlots[index].to, + 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( + ValueTimeSlotEvent( + timeSlot: TimeSlot( + name: state.timeSlots[index].name, + from: state.timeSlots[index].from, + to: time), + index: index), + ); + } + }, + child: Text( + state.timeSlots[index].to.format(context), + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/models/time_slot.dart b/lib/models/time_slot.dart new file mode 100644 index 00000000..c5fe19b8 --- /dev/null +++ b/lib/models/time_slot.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class TimeSlot extends Equatable { + final String name; + final TimeOfDay from; + final TimeOfDay to; + + const TimeSlot({required this.name, required this.from, required this.to}); + + Map toJson() { + return { + 'name': name, + 'from': '${from.hour}:${from.minute}', + 'to': '${to.hour}:${to.minute}', + }; + } + + factory TimeSlot.fromJSON(Map json) { + final format = DateFormat.Hm(); + return TimeSlot( + name: json['name'] ?? '', + from: TimeOfDay.fromDateTime( + format.parse( + json['from'], + ), + ), + to: TimeOfDay.fromDateTime( + format.parse( + json['to'], + ), + ), + ); + } + + @override + List get props => [name, from, to]; +} diff --git a/lib/models/time_slots.dart b/lib/models/time_slots.dart new file mode 100644 index 00000000..e7abbaa3 --- /dev/null +++ b/lib/models/time_slots.dart @@ -0,0 +1,7 @@ +import 'package:chabo/models/time_slot.dart'; + +class TimeSlots { + final List timeSlots; + + TimeSlots({required this.timeSlots}); +} diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 655c9fae..3c6ac6ce 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -1,13 +1,16 @@ import 'dart:ui'; -import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/bloc/time_slot/time_slot_bloc.dart'; +import 'package:chabo/cubits/floating_actions_cubit.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/color_scheme_extension.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:chabo/widgets/time_slot_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -62,6 +65,56 @@ class _NotificationScreenState extends CustomWidgetState { builder: (context, state) { return Column( children: [ + BlocBuilder( + builder: (context, state) { + return Column( + children: [ + _CustomListTile( + onChanged: (bool value) => + BlocProvider.of(context).add( + EnabledTimeSlotEvent( + enabled: value, + ), + ), + enabled: state.enabledForNotifications, + title: + AppLocalizations.of(context)!.favoriteSlots, + subtitle: AppLocalizations.of(context)! + .favoriteSlotsDescription, + leadingIcon: Icons.warning_rounded, + iconColor: + Theme.of(context).colorScheme.warningColor, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + for (var i = 0; + i < state.timeSlots.length; + i++) ...[ + TimeSlotWidget( + timeSlot: state.timeSlots[i], + index: i) + ], + ]), + ), + ], + ); + }, + ), + const SizedBox( + height: 10, + ), + const Divider( + height: 5, + indent: 25, + endIndent: 25, + ), + const SizedBox( + height: 10, + ), _CustomListTile( onChanged: (bool value) => BlocProvider.of(context).add( @@ -222,7 +275,7 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), - ) + ), ], ); }, diff --git a/lib/widgets/time_slot_widget.dart b/lib/widgets/time_slot_widget.dart new file mode 100644 index 00000000..288bda1d --- /dev/null +++ b/lib/widgets/time_slot_widget.dart @@ -0,0 +1,63 @@ +import 'dart:ui'; + +import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/dialogs/time_slot_dialog.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class TimeSlotWidget extends StatelessWidget { + final TimeSlot timeSlot; + final int index; + + const TimeSlotWidget({Key? key, required this.timeSlot, required this.index}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () async { + final day = await showDialog( + context: context, + builder: ( + BuildContext context, + ) { + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY), + child: TimeSlotDialog( + index: index, + ), + ); + }, + ); + if (day != null) { + BlocProvider.of(context).add( + DayNotificationValueEvent(day: day), + ); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Text( + timeSlot.name != '' + ? timeSlot.name + : AppLocalizations.of(context)! + .favoriteTimeSlotDefaultName(index + 1), + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + '${timeSlot.from.format(context)} - ${timeSlot.to.format(context)}', + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ); + } +} From 82bf67e2bb4323f0c1285cd976509c99f554f86c Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:18:30 +0200 Subject: [PATCH 17/34] feat(time-slot): forecast list now listen the TimeBloc --- .../forecast/forecast_list_widget.dart | 91 +++++++++++-------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index ebcc5f77..64021714 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -1,5 +1,6 @@ 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/bloc/time_slot/time_slot_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'; @@ -30,45 +31,57 @@ class _ForecastListWidgetState extends State { return true; }, child: BlocBuilder( - builder: (context, state) { - return ListView.separated( - cacheExtent: 5000, - padding: const EdgeInsets.all(0), - itemBuilder: (BuildContext context, int index) { - return ForecastListItemWidget( - 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, - isInterfering: index % 2 == 0,); - }, - 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) && - !kIsWeb) { - return const AdBannerWidget(); - } - return const SizedBox.shrink(); + builder: (context, forecastState) { + return BlocBuilder( + builder: (context, timeSlotState) { + return ListView.separated( + cacheExtent: 5000, + padding: const EdgeInsets.all(0), + itemBuilder: (BuildContext context, int index) { + forecastState.chabanBridgeForecasts[index] + .checkSlotInterference(timeSlotState.timeSlots); + return ForecastListItemWidget( + key: GlobalObjectKey( + forecastState.chabanBridgeForecasts[index].hashCode), + isCurrent: forecastState.chabanBridgeForecasts[index] == + forecastState.currentChabanBridgeForecast, + hasPassed: forecastState + .chabanBridgeForecasts[index].circulationReOpeningDate + .isBefore(DateTime.now()), + chabanBridgeForecast: + forecastState.chabanBridgeForecasts[index], + index: index, + timeSlots: forecastState + .chabanBridgeForecasts[index].interferingTimeSlots, + ); + }, + itemCount: forecastState.chabanBridgeForecasts.length, + controller: + BlocProvider.of(context).scrollController, + separatorBuilder: (BuildContext context, int index) { + if ((index + 1 <= + forecastState.chabanBridgeForecasts.length && + forecastState.chabanBridgeForecasts[index] + .circulationClosingDate.month != + forecastState.chabanBridgeForecasts[index + 1] + .circulationClosingDate.month)) { + return _MonthWidget( + chabanBridgeForecast: + forecastState.chabanBridgeForecasts[index + 1], + ); + } + if (((index % 10 == 0 || + index == + forecastState.chabanBridgeForecasts.indexOf( + forecastState + .currentChabanBridgeForecast!)) && + index != 0) && + !kIsWeb) { + return const AdBannerWidget(); + } + return const SizedBox.shrink(); + }, + ); }, ); }, From 77033d03a7956457b5308912952e807589ac3570 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:18:52 +0200 Subject: [PATCH 18/34] feat(time-slot): add a DateTime extension --- lib/extensions/date_time_extension.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/extensions/date_time_extension.dart b/lib/extensions/date_time_extension.dart index 0c12f24e..c16b8d87 100644 --- a/lib/extensions/date_time_extension.dart +++ b/lib/extensions/date_time_extension.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + extension DateTimeExtension on DateTime { DateTime previous(int day) { if (day == weekday) { @@ -11,4 +13,8 @@ extension DateTimeExtension on DateTime { ); } } + + DateTime applied(TimeOfDay time) { + return DateTime(year, month, day, time.hour, time.minute); + } } From d73019e3882395b0913a3c897cf3aaacb617076d Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 14:21:05 +0200 Subject: [PATCH 19/34] feat(time-slot): integration of the time slot into the existing UI --- .../chaban_bridge_forecast_bloc.dart | 4 +- .../abstract_chaban_bridge_forecast.dart | 31 +++-- .../forecast/forecast_list_item_widget.dart | 119 ++++++++++-------- lib/widgets/forecast/status_widget.dart | 2 +- 4 files changed, 94 insertions(+), 62 deletions(-) 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 4570e5ad..789d78e0 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -1,5 +1,3 @@ -// ignore_for_file: invalid_use_of_visible_for_testing_member - import 'dart:async'; import 'dart:convert'; @@ -35,6 +33,7 @@ class ChabanBridgeForecastBloc _getPreviousStatus(state.chabanBridgeForecasts, currentStatus); if (currentStatus != state.currentChabanBridgeForecast && currentStatus != previousStatus) { + // ignore: invalid_use_of_visible_for_testing_member emit( state.copyWith( currentChabanBridgeForecast: currentStatus, @@ -44,6 +43,7 @@ class ChabanBridgeForecastBloc } } } catch (_) { + // ignore: invalid_use_of_visible_for_testing_member emit(state.copyWith( status: ChabanBridgeForecastStatus.failure, message: _.toString())); } diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index ac0e02ac..a586266b 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -1,5 +1,7 @@ +import 'package:chabo/extensions/date_time_extension.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:chabo/models/time_slot.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -12,6 +14,7 @@ abstract class AbstractChabanBridgeForecast extends Equatable { late final DateTime _circulationClosingDate; late final DateTime _circulationReOpeningDate; final ChabanBridgeForecastClosingType closingType; + final List interferingTimeSlots = []; AbstractChabanBridgeForecast( {required this.totalClosing, @@ -38,16 +41,12 @@ abstract class AbstractChabanBridgeForecast extends Equatable { DateTime get circulationReOpeningDate => _circulationReOpeningDate.toLocal(); - DateTime get circulationReOpeningDateUTC => circulationClosingDate; - set circulationReOpeningDate(DateTime value) { _circulationReOpeningDate = value; } DateTime get circulationClosingDate => _circulationClosingDate.toLocal(); - DateTime get circulationClosingDateUTC => _circulationClosingDate; - set circulationClosingDate(DateTime value) { _circulationClosingDate = value; } @@ -75,10 +74,28 @@ abstract class AbstractChabanBridgeForecast extends Equatable { .format(circulationReOpeningDate); } + void checkSlotInterference(List timeSlots) { + interferingTimeSlots.clear(); + for (var timeSlot in timeSlots) { + if (isOverlappingWithTimeOfDay(timeSlot.to)) { + interferingTimeSlots.add(timeSlot); + } + } + } + bool isCurrentlyClosed() { - var now = DateTime.now(); - return now.isAfter(circulationClosingDate) && - now.isBefore(circulationReOpeningDate); + return isOverlappingWith(DateTime.now()); + } + + bool isOverlappingWith(DateTime dateTime) { + return dateTime.isAfter(circulationClosingDate) && + dateTime.isBefore(circulationReOpeningDate); + } + + bool isOverlappingWithTimeOfDay(TimeOfDay timeOfDay) { + final dateTimeConversion = circulationClosingDate.applied(timeOfDay); + return dateTimeConversion.isAfter(circulationClosingDate) && + dateTimeConversion.isBefore(circulationReOpeningDate); } static bool getBooleanTotalClosingValue(String stringValue) { diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index afa4665f..2834b549 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -5,6 +5,7 @@ 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:chabo/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -13,7 +14,7 @@ class ForecastListItemWidget extends StatelessWidget { final AbstractChabanBridgeForecast chabanBridgeForecast; final Function()? onTap; final bool hasPassed; - final bool isInterfering; + final List timeSlots; final bool isCurrent; final int index; @@ -23,7 +24,8 @@ class ForecastListItemWidget extends StatelessWidget { required this.index, required this.hasPassed, required this.isCurrent, - this.onTap, required this.isInterfering}) + this.onTap, + required this.timeSlots}) : super(key: key); @override @@ -83,46 +85,58 @@ class ForecastListItemWidget extends StatelessWidget { ) }, child: SizedBox( - height: 60, + height: 65, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - width: 65, - height: 60, + width: 55, + height: 65, decoration: BoxDecoration( borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - bottomLeft: Radius.circular(15), + topLeft: Radius.circular( + CustomProperties.borderRadius, + ), + bottomLeft: Radius.circular( + CustomProperties.borderRadius, + ), + ), + color: chabanBridgeForecast.getColor( + context, + false, + ), + ), + child: Center( + child: chabanBridgeForecast.getIconWidget( + context, + true, ), - color: chabanBridgeForecast.getColor(context, false), ), - child: Center(child: chabanBridgeForecast.getIconWidget(context, true)), ), - Flexible( child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - MaterialLocalizations.of(context).formatMediumDate( - chabanBridgeForecast.circulationClosingDate, - ), - style: Theme.of(context).textTheme.bodySmall, - ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ const Icon(Icons.block_rounded, - size: 20, color: Colors.red), + size: 18, color: Colors.red), Text( - chabanBridgeForecast.circulationClosingDateString( - context, + MaterialLocalizations.of(context) + .formatMediumDate( + chabanBridgeForecast.circulationClosingDate, ), - style: Theme.of(context).textTheme.bodyLarge, + style: Theme.of(context).textTheme.bodySmall, ), ], ), + Text( + chabanBridgeForecast.circulationClosingDateString( + context, + ), + style: Theme.of(context).textTheme.headlineSmall, + ), ], ), ), @@ -151,50 +165,51 @@ class ForecastListItemWidget extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - MaterialLocalizations.of(context).formatMediumDate( - chabanBridgeForecast.circulationReOpeningDate, - ), - style: Theme.of(context).textTheme.bodySmall, - ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ const Icon(Icons.check_circle, - size: 20, color: Colors.green), + size: 18, color: Colors.green), Text( - chabanBridgeForecast - .circulationReOpeningDateString( - context, + MaterialLocalizations.of(context) + .formatMediumDate( + chabanBridgeForecast.circulationReOpeningDate, ), - style: Theme.of(context).textTheme.bodyLarge, + style: Theme.of(context).textTheme.bodySmall, ), - if (chabanBridgeForecast.isDuringTwoDays) - Text('(j +1)', - style: Theme.of(context).textTheme.bodySmall, - ), ], ), + Text( + chabanBridgeForecast.circulationReOpeningDateString( + context, + ), + style: Theme.of(context).textTheme.headlineSmall, + ), ], ), ), - isInterfering ? - Container( - width: 30, - height: 60, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(15), - bottomRight: Radius.circular(15), - ), - color: Theme.of(context).colorScheme.timeColor, - ), - child: Icon( - Icons.warning_rounded, - size: 20, - color: Theme.of(context).cardColor, - ), - ) : Container(width: 15,), + timeSlots.isNotEmpty + ? Container( + width: 30, + height: 65, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular( + CustomProperties.borderRadius), + bottomRight: Radius.circular( + CustomProperties.borderRadius), + ), + color: Theme.of(context).colorScheme.warningColor, + ), + child: Icon( + Icons.warning_rounded, + size: 20, + color: Theme.of(context).cardColor, + ), + ) + : Container( + width: 15, + ), ], ), ), diff --git a/lib/widgets/forecast/status_widget.dart b/lib/widgets/forecast/status_widget.dart index 4f4d12b7..b59e3ccc 100644 --- a/lib/widgets/forecast/status_widget.dart +++ b/lib/widgets/forecast/status_widget.dart @@ -186,7 +186,7 @@ class StatusWidgetState extends CustomWidgetState { chabanBridgeForecast: state.currentTarget!, index: -1, - isInterfering: false, + timeSlots: const [], ), ) : const SizedBox.shrink(), From adfe7ad194919d76ee033e19b6709afabb9b9b22 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 17:00:40 +0200 Subject: [PATCH 20/34] chore(format): use of `dart_code_metrics` --- .../flutter.analyze-test.action.yaml | 18 +- analysis_options.yaml | 33 ++- lib/app_theme.dart | 2 +- .../chaban_bridge_forecast_bloc.dart | 64 +++-- .../chaban_bridge_forecast_event.dart | 3 - .../chaban_bridge_forecast_state.dart | 59 ++-- .../chaban_bridge_status_bloc.dart | 87 +++--- .../chaban_bridge_status_event.dart | 8 +- .../chaban_bridge_status_state.dart | 117 ++++---- lib/bloc/notification/notification_bloc.dart | 136 +++++---- lib/bloc/notification/notification_state.dart | 81 +++--- .../scroll_status/scroll_status_bloc.dart | 42 ++- .../scroll_status/scroll_status_state.dart | 29 +- lib/bloc/simple_bloc_observer.dart | 17 -- lib/bloc/theme/theme_bloc.dart | 23 +- lib/bloc/theme/theme_state.dart | 12 +- lib/bloc/time_slot/time_slot_bloc.dart | 45 ++- lib/bloc/time_slot/time_slot_state.dart | 6 +- lib/chabo.dart | 7 +- lib/const.dart | 39 ++- lib/cubits/floating_actions_cubit.dart | 13 +- ...ts_state.dart => custom_widget_state.dart} | 0 ...an_bridge_forecast_information_dialog.dart | 6 +- lib/dialogs/chabo_about_dialog.dart | 33 ++- lib/dialogs/days_of_the_week_dialog.dart | 27 +- lib/dialogs/theme_picker_dialog.dart | 121 -------- lib/dialogs/time_slot_dialog.dart | 72 ++--- ...s_extensions.dart => boats_extension.dart} | 6 +- lib/extensions/date_time_extension.dart | 19 +- lib/extensions/duration_extension.dart | 3 +- lib/extensions/string_extension.dart | 8 +- lib/helpers/ad_helper.dart | 6 +- lib/l10n/app_fr.arb | 1 - lib/main.dart | 5 +- lib/misc/no_scaling_animation.dart | 7 +- .../abstract_chaban_bridge_forecast.dart | 44 +-- lib/models/boat.dart | 35 ++- lib/models/chaban_bridge_boat_forecast.dart | 72 +++-- .../chaban_bridge_maintenance_forecast.dart | 57 ++-- lib/models/time_slot.dart | 1 + lib/models/time_slots.dart | 7 - .../{link_icon.dart => web_link_icon.dart} | 0 .../chaban_bridge_forecast_screen.dart | 96 ++++--- lib/screens/changelog_screen.dart | 9 +- lib/screens/error_screen.dart | 21 +- lib/screens/notification_screen.dart | 138 +++++---- lib/screens/settings_screen.dart | 121 -------- lib/service/notification_service.dart | 261 +++++++++++------- lib/service/storage_service.dart | 33 ++- lib/widgets/ad_banner_widget.dart | 6 +- ...n_item.dart => floating_actions_item.dart} | 16 +- .../floating_actions_widget.dart | 27 +- .../forecast/forecast_list_item_widget.dart | 60 ++-- .../forecast/forecast_list_widget.dart | 10 +- lib/widgets/forecast/status_widget.dart | 17 +- lib/widgets/notification_tile_widget.dart | 51 ---- .../custom_progress_bar_indicator.dart | 10 +- lib/widgets/theme_switcher_widget.dart | 29 +- lib/widgets/time_slot_widget.dart | 23 +- .../ephemeral/Flutter-Generated.xcconfig | 8 +- .../ephemeral/flutter_export_environment.sh | 8 +- pubspec.lock | 120 ++++++++ pubspec.yaml | 1 + 63 files changed, 1307 insertions(+), 1129 deletions(-) delete mode 100644 lib/bloc/simple_bloc_observer.dart rename lib/{custom_widgets_state.dart => custom_widget_state.dart} (100%) delete mode 100644 lib/dialogs/theme_picker_dialog.dart rename lib/extensions/{boats_extensions.dart => boats_extension.dart} (93%) delete mode 100644 lib/models/time_slots.dart rename lib/models/{link_icon.dart => web_link_icon.dart} (100%) delete mode 100644 lib/screens/settings_screen.dart rename lib/widgets/floating_actions/{floating_action_item.dart => floating_actions_item.dart} (68%) delete mode 100644 lib/widgets/notification_tile_widget.dart diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index a12f57f3..3796c752 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -25,7 +25,7 @@ jobs: with: flutter-version: ${{ inputs.flutter_version }} - name: 'Flutter analyze' - run: flutter analyze + run: flutter analyze lib format: name: 'Format' runs-on: ubuntu-latest @@ -38,6 +38,22 @@ jobs: flutter-version: ${{ inputs.flutter_version }} - name: 'Flutter format' run: dart format lib --set-exit-if-changed + code-metrics: + name: 'Code metrics' + 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: '[Global] Code metrics' + run: flutter pub run dart_code_metrics:metrics analyze --fatal-style --fatal-warnings --fatal-performance --reporter=github lib + - name: '[Unused files] Code metrics' + run: flutter pub run dart_code_metrics:metrics check-unused-files lib + - name: '[Unused code] Code metrics' + run: flutter pub run dart_code_metrics:metrics check-unused-code lib test: name: 'Test' runs-on: ubuntu-latest diff --git a/analysis_options.yaml b/analysis_options.yaml index bac0015c..6847264e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,7 +3,34 @@ include: package:flutter_lints/flutter.yaml linter: rules: prefer_single_quotes: true - use_build_context_synchronously: false -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +analyzer: + plugins: + - dart_code_metrics + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-parameters: 5 + maximum-nesting-level: 5 + metrics-exclude: + - test/** + rules: + - avoid-dynamic + - avoid-passing-async-when-sync-expected + - avoid-redundant-async + - avoid-unnecessary-type-assertions + - avoid-unnecessary-type-casts + - avoid-unrelated-type-assertions + - avoid-nested-conditional-expressions: + acceptable-level: 2 + - newline-before-return + - no-boolean-literal-compare + - no-empty-block + - prefer-trailing-comma + - prefer-conditional-expressions: + ignore-nested: true + - no-equal-then-else + - prefer-moving-to-variable: + allowed-duplicated-chains: 3 + - prefer-match-file-name diff --git a/lib/app_theme.dart b/lib/app_theme.dart index e9d67c61..48c61cc9 100644 --- a/lib/app_theme.dart +++ b/lib/app_theme.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -class AppThemes { +class AppTheme { static final lightTheme = ThemeData.light( useMaterial3: true, ).copyWith( 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 789d78e0..b78e0272 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart @@ -45,12 +45,15 @@ class ChabanBridgeForecastBloc } catch (_) { // ignore: invalid_use_of_visible_for_testing_member emit(state.copyWith( - status: ChabanBridgeForecastStatus.failure, message: _.toString())); + status: ChabanBridgeForecastStatus.failure, + message: _.toString(), + )); } } Future> _fetchChabanBridgeForecasts( - int offset) async { + int offset, + ) async { var uri = Uri.https( 'opendata.bordeaux-metropole.fr', '/api/records/1.0/search', @@ -59,30 +62,35 @@ class ChabanBridgeForecastBloc 'rows': '${Const.chabanBridgeForecastLimit}', 'sort': '-date_passage', 'start': '$offset', - 'timezone': 'Europe/Paris' + 'timezone': 'Europe/Paris', }, ); final response = await httpClient.get(uri); if (response.statusCode == 200) { final body = json.decode(response.body); - return (body['records'] as List).map((dynamic json) { + + return (body['records'] as List).map((json) { if (json['fields']['bateau'].toString().toLowerCase() == 'maintenance') { final maintenanceForecast = ChabanBridgeMaintenanceForecast.fromJSON(json); + return maintenanceForecast; } final boatForecast = ChabanBridgeBoatForecast.fromJSON(json); + return boatForecast; }).toList() ..sort((a, b) => a.circulationClosingDate.compareTo(b.circulationClosingDate)); } + return []; } AbstractChabanBridgeForecast _getCurrentStatus( - List chabanBridgeForecast) { + List chabanBridgeForecast, + ) { int middle = chabanBridgeForecast.length ~/ 2; if ((chabanBridgeForecast[middle] .circulationClosingDate @@ -111,18 +119,19 @@ class ChabanBridgeForecastBloc } AbstractChabanBridgeForecast? _getPreviousStatus( - List chabanBridgeForecasts, - AbstractChabanBridgeForecast currentStatus) { - if (chabanBridgeForecasts.indexOf(currentStatus) == 0) { - return null; - } else { - return chabanBridgeForecasts - .elementAt(chabanBridgeForecasts.indexOf(currentStatus) - 1); - } + List chabanBridgeForecasts, + AbstractChabanBridgeForecast currentStatus, + ) { + return chabanBridgeForecasts.indexOf(currentStatus) == 0 + ? null + : chabanBridgeForecasts + .elementAt(chabanBridgeForecasts.indexOf(currentStatus) - 1); } - Future _onChabanBridgeForecastFetched(ChabanBridgeForecastFetched event, - Emitter emit) async { + Future _onChabanBridgeForecastFetched( + ChabanBridgeForecastFetched event, + Emitter emit, + ) async { if (state.hasReachedMax) return; try { if (state.status == ChabanBridgeForecastStatus.initial) { @@ -130,13 +139,14 @@ class ChabanBridgeForecastBloc await _fetchChabanBridgeForecasts(state.offset); final currentStatus = _getCurrentStatus(chabanBridgeForecasts); emit(state.copyWith( - status: ChabanBridgeForecastStatus.success, - chabanBridgeForecasts: chabanBridgeForecasts, - currentChabanBridgeForecast: currentStatus, - previousChabanBridgeForecast: - _getPreviousStatus(chabanBridgeForecasts, currentStatus), - hasReachedMax: false, - offset: state.offset + Const.chabanBridgeForecastLimit)); + status: ChabanBridgeForecastStatus.success, + chabanBridgeForecasts: chabanBridgeForecasts, + currentChabanBridgeForecast: currentStatus, + previousChabanBridgeForecast: + _getPreviousStatus(chabanBridgeForecasts, currentStatus), + hasReachedMax: false, + offset: state.offset + Const.chabanBridgeForecastLimit, + )); } final chabanBridgeForecasts = await _fetchChabanBridgeForecasts(state.chabanBridgeForecasts.length); @@ -149,8 +159,10 @@ class ChabanBridgeForecastBloc _getCurrentStatus(chabanBridgeForecasts), previousChabanBridgeForecast: state.previousChabanBridgeForecast ?? - _getPreviousStatus(chabanBridgeForecasts, - _getCurrentStatus(chabanBridgeForecasts)), + _getPreviousStatus( + chabanBridgeForecasts, + _getCurrentStatus(chabanBridgeForecasts), + ), status: ChabanBridgeForecastStatus.success, chabanBridgeForecasts: List.of(state.chabanBridgeForecasts) ..addAll(chabanBridgeForecasts), @@ -160,7 +172,9 @@ class ChabanBridgeForecastBloc ); } catch (_) { emit(state.copyWith( - status: ChabanBridgeForecastStatus.failure, message: _.toString())); + status: ChabanBridgeForecastStatus.failure, + message: _.toString(), + )); } } } 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 020cc3d3..34ae8233 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart @@ -3,6 +3,3 @@ 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 67feb542..70e0703e 100644 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart +++ b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart @@ -1,7 +1,5 @@ part of 'chaban_bridge_forecast_bloc.dart'; -enum ChabanBridgeForecastStatus { initial, success, failure } - class ChabanBridgeForecastState extends Equatable { final ChabanBridgeForecastStatus status; final List chabanBridgeForecasts; @@ -11,34 +9,37 @@ class ChabanBridgeForecastState extends Equatable { final int offset; final String message; - const ChabanBridgeForecastState( - {this.status = ChabanBridgeForecastStatus.initial, - this.chabanBridgeForecasts = const [], - this.currentChabanBridgeForecast, - this.previousChabanBridgeForecast, - this.hasReachedMax = false, - this.offset = 0, - this.message = 'OK'}); + const ChabanBridgeForecastState({ + this.status = ChabanBridgeForecastStatus.initial, + this.chabanBridgeForecasts = const [], + this.currentChabanBridgeForecast, + this.previousChabanBridgeForecast, + this.hasReachedMax = false, + this.offset = 0, + this.message = 'OK', + }); - ChabanBridgeForecastState copyWith( - {ChabanBridgeForecastStatus? status, - List? chabanBridgeForecasts, - AbstractChabanBridgeForecast? currentChabanBridgeForecast, - AbstractChabanBridgeForecast? previousChabanBridgeForecast, - bool? hasReachedMax, - int? offset, - String? message}) { + ChabanBridgeForecastState copyWith({ + ChabanBridgeForecastStatus? status, + List? chabanBridgeForecasts, + AbstractChabanBridgeForecast? currentChabanBridgeForecast, + AbstractChabanBridgeForecast? previousChabanBridgeForecast, + bool? hasReachedMax, + int? offset, + String? message, + }) { return ChabanBridgeForecastState( - status: status ?? this.status, - chabanBridgeForecasts: - chabanBridgeForecasts ?? this.chabanBridgeForecasts, - currentChabanBridgeForecast: - currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, - previousChabanBridgeForecast: - previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, - hasReachedMax: hasReachedMax ?? this.hasReachedMax, - offset: offset ?? this.offset, - message: message ?? this.message); + status: status ?? this.status, + chabanBridgeForecasts: + chabanBridgeForecasts ?? this.chabanBridgeForecasts, + currentChabanBridgeForecast: + currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, + previousChabanBridgeForecast: + previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, + hasReachedMax: hasReachedMax ?? this.hasReachedMax, + offset: offset ?? this.offset, + message: message ?? this.message, + ); } @override @@ -57,3 +58,5 @@ class ChabanBridgeForecastState extends Equatable { previousChabanBridgeForecast, ]; } + +enum ChabanBridgeForecastStatus { initial, success, failure } 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 5c069866..31627c1e 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart @@ -10,7 +10,6 @@ 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 @@ -27,8 +26,10 @@ class ChabanBridgeStatusBloc ); } - void _onDurationChanged(ChabanBridgeStatusDurationChanged event, - Emitter emit) { + void _onDurationChanged( + ChabanBridgeStatusDurationChanged event, + Emitter emit, + ) { emit( state.copyWith( durationForCloseClosing: event.duration, @@ -37,21 +38,28 @@ class ChabanBridgeStatusBloc } void _onChabanBridgeStatusChanged( - ChabanBridgeStatusChanged event, Emitter emit) { + ChabanBridgeStatusChanged event, + Emitter emit, + ) { emit( state.copyWith( - currentChabanBridgeForecast: event.currentChabanBridgeForecast, - previousChabanBridgeForecast: event.previousChabanBridgeForecast), + currentChabanBridgeForecast: event.currentChabanBridgeForecast, + previousChabanBridgeForecast: event.previousChabanBridgeForecast, + ), ); } void _onRefresh( - ChabanBridgeStatusRefresh event, Emitter emit) { + ChabanBridgeStatusRefresh event, + Emitter emit, + ) { final Duration? durationUntilNextEvent = _getDurationUntilNextEvent(); final Duration? durationBetweenPreviousAndNextEvent = _getDurationBetweenPreviousAndNextEvent(); final double completionPercentage = _getDiffPercentage( - durationBetweenPreviousAndNextEvent, durationUntilNextEvent); + durationBetweenPreviousAndNextEvent, + durationUntilNextEvent, + ); final String mainMessageStatus = _getMainStatus(event.context); final String timeMessagePrefix = _getTimeMessagePrefix(event.context); final Color foregroundColor = _getForegroundColor(event.context); @@ -97,13 +105,13 @@ class ChabanBridgeStatusBloc final currentChabanBridgeForecast = state.currentChabanBridgeForecast; if (currentChabanBridgeForecast != null) { final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); - if (isOpen || - state.durationUntilNextEvent.inMinutes < - state.durationForCloseClosing.inMinutes) { - return Theme.of(context).colorScheme.background; - } else { - return Theme.of(context).colorScheme.onError; - } + final colorScheme = Theme.of(context).colorScheme; + + return isOpen || + state.durationUntilNextEvent.inMinutes < + state.durationForCloseClosing.inMinutes + ? colorScheme.background + : colorScheme.onError; } else { return state.foregroundColor; } @@ -112,11 +120,9 @@ class ChabanBridgeStatusBloc 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()} '; - } + return currentChabanBridgeForecast.isCurrentlyClosed() + ? '${AppLocalizations.of(context)!.scheduledToOpen.capitalize()} ' + : '${AppLocalizations.of(context)!.nextClosingScheduled.capitalize()} '; } else { return 'NO_TIME'; } @@ -143,13 +149,9 @@ class ChabanBridgeStatusBloc 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); - } + return currentChabanBridgeForecast.isCurrentlyClosed() + ? currentChabanBridgeForecast.circulationReOpeningDate.difference(now) + : currentChabanBridgeForecast.circulationClosingDate.difference(now); } else { return null; } @@ -160,27 +162,26 @@ class ChabanBridgeStatusBloc final previousChabanBridgeForecast = state.previousChabanBridgeForecast; if (currentChabanBridgeForecast != null && previousChabanBridgeForecast != null) { - if (currentChabanBridgeForecast.isCurrentlyClosed()) { - return currentChabanBridgeForecast.closedDuration; - } else { - return currentChabanBridgeForecast.circulationClosingDate - .difference(previousChabanBridgeForecast.circulationReOpeningDate); - } + return currentChabanBridgeForecast.isCurrentlyClosed() + ? currentChabanBridgeForecast.closedDuration + : 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; - } + double _getDiffPercentage( + Duration? durationBetweenPreviousAndNextEvent, + Duration? durationUntilNextEvent, + ) { + return durationBetweenPreviousAndNextEvent != null && + durationUntilNextEvent != null + ? 1 - + (durationUntilNextEvent.inSeconds / + durationBetweenPreviousAndNextEvent.inSeconds) + : -1; } String _getGreetings(BuildContext context) { 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 3f0ab772..bb9151d6 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart @@ -6,10 +6,10 @@ class ChabanBridgeStatusChanged extends ChabanBridgeStatusEvent { final AbstractChabanBridgeForecast? currentChabanBridgeForecast; final AbstractChabanBridgeForecast? previousChabanBridgeForecast; - ChabanBridgeStatusChanged( - {required this.currentChabanBridgeForecast, - required this.previousChabanBridgeForecast}) - : super(); + ChabanBridgeStatusChanged({ + required this.currentChabanBridgeForecast, + required this.previousChabanBridgeForecast, + }) : super(); } class ChabanBridgeStatusRefresh extends ChabanBridgeStatusEvent { 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 74c48b3f..9c68a042 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart +++ b/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart @@ -1,7 +1,5 @@ part of 'chaban_bridge_status_bloc.dart'; -enum ChabanBridgeStatusLifecycle { empty, populated } - class ChabanBridgeStatusState extends Equatable { final ChabanBridgeStatusLifecycle chabanBridgeStatusLifecycle; final AbstractChabanBridgeForecast? currentChabanBridgeForecast; @@ -15,50 +13,53 @@ class ChabanBridgeStatusState extends Equatable { final Color foregroundColor; final Color backgroundColor; - const ChabanBridgeStatusState( - {required this.chabanBridgeStatusLifecycle, - required this.currentChabanBridgeForecast, - required this.previousChabanBridgeForecast, - required this.durationUntilNextEvent, - required this.durationForCloseClosing, - required this.durationBetweenPreviousAndNextEvent, - required this.completionPercentage, - required this.mainMessageStatus, - required this.timeMessagePrefix, - required this.foregroundColor, - required this.backgroundColor}); + const ChabanBridgeStatusState({ + required this.chabanBridgeStatusLifecycle, + required this.currentChabanBridgeForecast, + required this.previousChabanBridgeForecast, + required this.durationUntilNextEvent, + required this.durationForCloseClosing, + required this.durationBetweenPreviousAndNextEvent, + required this.completionPercentage, + required this.mainMessageStatus, + required this.timeMessagePrefix, + required this.foregroundColor, + required this.backgroundColor, + }); - ChabanBridgeStatusState copyWith( - {ChabanBridgeStatusLifecycle? chabanBridgeStatusLifecycle, - AbstractChabanBridgeForecast? currentChabanBridgeForecast, - AbstractChabanBridgeForecast? previousChabanBridgeForecast, - Duration? durationUntilNextEvent, - Duration? durationForCloseClosing, - Duration? durationBetweenPreviousAndNextEvent, - double? completionPercentage, - String? mainMessageStatus, - String? timeMessagePrefix, - Color? foregroundColor, - Color? backgroundColor}) { + ChabanBridgeStatusState copyWith({ + ChabanBridgeStatusLifecycle? chabanBridgeStatusLifecycle, + AbstractChabanBridgeForecast? currentChabanBridgeForecast, + AbstractChabanBridgeForecast? previousChabanBridgeForecast, + Duration? durationUntilNextEvent, + Duration? durationForCloseClosing, + Duration? durationBetweenPreviousAndNextEvent, + double? completionPercentage, + String? mainMessageStatus, + String? timeMessagePrefix, + Color? foregroundColor, + Color? backgroundColor, + }) { return ChabanBridgeStatusState( - chabanBridgeStatusLifecycle: - chabanBridgeStatusLifecycle ?? this.chabanBridgeStatusLifecycle, - currentChabanBridgeForecast: - currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, - previousChabanBridgeForecast: - previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, - durationUntilNextEvent: - durationUntilNextEvent ?? this.durationUntilNextEvent, - durationForCloseClosing: - durationForCloseClosing ?? this.durationForCloseClosing, - durationBetweenPreviousAndNextEvent: - durationBetweenPreviousAndNextEvent ?? - this.durationBetweenPreviousAndNextEvent, - completionPercentage: completionPercentage ?? this.completionPercentage, - mainMessageStatus: mainMessageStatus ?? this.mainMessageStatus, - timeMessagePrefix: timeMessagePrefix ?? this.timeMessagePrefix, - foregroundColor: foregroundColor ?? this.foregroundColor, - backgroundColor: backgroundColor ?? this.backgroundColor); + chabanBridgeStatusLifecycle: + chabanBridgeStatusLifecycle ?? this.chabanBridgeStatusLifecycle, + currentChabanBridgeForecast: + currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, + previousChabanBridgeForecast: + previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, + durationUntilNextEvent: + durationUntilNextEvent ?? this.durationUntilNextEvent, + durationForCloseClosing: + durationForCloseClosing ?? this.durationForCloseClosing, + 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 @@ -73,23 +74,25 @@ class ChabanBridgeStatusState extends Equatable { mainMessageStatus, timeMessagePrefix, foregroundColor, - backgroundColor + backgroundColor, ]; } class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { const ChabanBridgeStatusStateInitial() : super( - previousChabanBridgeForecast: null, - currentChabanBridgeForecast: null, - durationUntilNextEvent: Duration.zero, - durationBetweenPreviousAndNextEvent: null, - durationForCloseClosing: - Const.notificationDurationValueDefaultValue, - chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.empty, - completionPercentage: 0, - mainMessageStatus: '', - timeMessagePrefix: '', - foregroundColor: Colors.white, - backgroundColor: Colors.white); + previousChabanBridgeForecast: null, + currentChabanBridgeForecast: null, + durationUntilNextEvent: Duration.zero, + durationBetweenPreviousAndNextEvent: null, + durationForCloseClosing: Const.notificationDurationValueDefaultValue, + chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.empty, + completionPercentage: 0, + mainMessageStatus: '', + timeMessagePrefix: '', + foregroundColor: Colors.white, + backgroundColor: Colors.white, + ); } + +enum ChabanBridgeStatusLifecycle { empty, populated } diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 8c2639a7..45138fca 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -9,27 +9,27 @@ import 'package:flutter_bloc/flutter_bloc.dart'; part 'notification_event.dart'; part 'notification_state.dart'; -class NotificationBloc extends Bloc { +class NotificationBloc extends Bloc { final StorageService storageService; NotificationBloc({required this.storageService}) : super( - NotificationSate( - 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), + NotificationState( + 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, @@ -64,10 +64,13 @@ class NotificationBloc extends Bloc { } Future _onOpeningNotificationStateEvent( - OpeningNotificationStateEvent event, - Emitter emit) async { + OpeningNotificationStateEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationOpeningEnabledKey, event.enabled); + Const.notificationOpeningEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); emit( state.copyWith(openingNotificationEnabled: event.enabled), @@ -75,10 +78,13 @@ class NotificationBloc extends Bloc { } Future _onClosingNotificationStateEvent( - ClosingNotificationStateEvent event, - Emitter emit) async { + ClosingNotificationStateEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationClosingEnabledKey, event.enabled); + Const.notificationClosingEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); emit( state.copyWith(closingNotificationEnabled: event.enabled), @@ -86,9 +92,13 @@ class NotificationBloc extends Bloc { } Future _onDayNotificationStateEvent( - DayNotificationStateEvent event, Emitter emit) async { + DayNotificationStateEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationDayEnabledKey, event.enabled); + Const.notificationDayEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); emit( state.copyWith(dayNotificationEnabled: event.enabled), @@ -96,7 +106,9 @@ class NotificationBloc extends Bloc { } Future _onDayNotificationValueEvent( - DayNotificationValueEvent event, Emitter emit) async { + DayNotificationValueEvent event, + Emitter emit, + ) async { await storageService.saveDay(Const.notificationDayValueKey, event.day); HapticFeedback.lightImpact(); emit( @@ -105,10 +117,13 @@ class NotificationBloc extends Bloc { } Future _onDayNotificationTimeValueEvent( - DayNotificationTimeValueEvent event, - Emitter emit) async { + DayNotificationTimeValueEvent event, + Emitter emit, + ) async { await storageService.saveTimeOfDay( - Const.notificationDayTimeValueKey, event.time); + Const.notificationDayTimeValueKey, + event.time, + ); HapticFeedback.lightImpact(); emit( state.copyWith(dayNotificationTimeValue: event.time), @@ -116,9 +131,13 @@ class NotificationBloc extends Bloc { } Future _onTimeNotificationStateEvent( - TimeNotificationStateEvent event, Emitter emit) async { + TimeNotificationStateEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationTimeEnabledKey, event.enabled); + Const.notificationTimeEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); emit( state.copyWith(timeNotificationEnabled: event.enabled), @@ -126,37 +145,51 @@ class NotificationBloc extends Bloc { } Future _onTimeNotificationValueEvent( - TimeNotificationValueEvent event, Emitter emit) async { + TimeNotificationValueEvent event, + Emitter emit, + ) async { await storageService.saveTimeOfDay( - Const.notificationTimeValueKey, event.time); + Const.notificationTimeValueKey, + event.time, + ); emit( state.copyWith(timeNotificationValue: event.time), ); } Future _onDurationNotificationStateEvent( - DurationNotificationStateEvent event, - Emitter emit) async { + DurationNotificationStateEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationDurationEnabledKey, event.enabled); + Const.notificationDurationEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); emit( - state.copyWith(durationNotificationEnabled: event.enabled), + state.copyWith( + durationNotificationEnabled: event.enabled, + ), ); } Future _onDurationNotificationValueEvent( - DurationNotificationValueEvent event, - Emitter emit) async { + DurationNotificationValueEvent event, + Emitter emit, + ) async { await storageService.saveDuration( - Const.notificationDurationValueKey, event.duration); + Const.notificationDurationValueKey, + event.duration, + ); emit( state.copyWith(durationNotificationValue: event.duration), ); } - Future _onAppEvent( - AppEvent event, Emitter emit) async { + void _onAppEvent( + AppEvent event, + Emitter emit, + ) { final durationNotificationEnabled = storageService.readBool(Const.notificationDurationEnabledKey) ?? Const.notificationDurationEnabledDefaultValue; @@ -195,15 +228,16 @@ class NotificationBloc extends Bloc { emit( state.copyWith( - durationNotificationEnabled: durationNotificationEnabled, - durationNotificationValue: durationNotificationValue, - timeNotificationEnabled: timeNotificationEnabled, - timeNotificationValue: timeNotificationValue, - dayNotificationEnabled: dayNotificationEnabled, - dayNotificationValue: dayNotificationValue, - dayNotificationTimeValue: dayNotificationTimeValue, - openingNotificationEnabled: openingNotificationEnabled, - closingNotificationEnabled: closingNotificationEnabled), + durationNotificationEnabled: durationNotificationEnabled, + durationNotificationValue: durationNotificationValue, + timeNotificationEnabled: timeNotificationEnabled, + timeNotificationValue: timeNotificationValue, + dayNotificationEnabled: dayNotificationEnabled, + dayNotificationValue: dayNotificationValue, + dayNotificationTimeValue: dayNotificationTimeValue, + openingNotificationEnabled: openingNotificationEnabled, + closingNotificationEnabled: closingNotificationEnabled, + ), ); } } diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index 3ff54583..56eb1676 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -1,6 +1,6 @@ part of 'notification_bloc.dart'; -class NotificationSate { +class NotificationState { final bool durationNotificationEnabled; final Duration durationNotificationValue; final bool timeNotificationEnabled; @@ -11,44 +11,47 @@ class NotificationSate { 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.dayNotificationTimeValue, - required this.openingNotificationEnabled, - required this.closingNotificationEnabled}); + NotificationState({ + required this.durationNotificationEnabled, + required this.durationNotificationValue, + required this.timeNotificationEnabled, + required this.timeNotificationValue, + required this.dayNotificationEnabled, + required this.dayNotificationValue, + required this.dayNotificationTimeValue, + required this.openingNotificationEnabled, + required this.closingNotificationEnabled, + }); - NotificationSate copyWith( - {bool? durationNotificationEnabled, - Duration? durationNotificationValue, - bool? timeNotificationEnabled, - TimeOfDay? timeNotificationValue, - bool? dayNotificationEnabled, - Day? dayNotificationValue, - TimeOfDay? dayNotificationTimeValue, - 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, - dayNotificationTimeValue: - dayNotificationTimeValue ?? this.dayNotificationTimeValue, - openingNotificationEnabled: - openingNotificationEnabled ?? this.openingNotificationEnabled, - closingNotificationEnabled: - closingNotificationEnabled ?? this.closingNotificationEnabled); + NotificationState copyWith({ + bool? durationNotificationEnabled, + Duration? durationNotificationValue, + bool? timeNotificationEnabled, + TimeOfDay? timeNotificationValue, + bool? dayNotificationEnabled, + Day? dayNotificationValue, + TimeOfDay? dayNotificationTimeValue, + bool? openingNotificationEnabled, + bool? closingNotificationEnabled, + }) { + return NotificationState( + durationNotificationEnabled: + durationNotificationEnabled ?? this.durationNotificationEnabled, + durationNotificationValue: + durationNotificationValue ?? this.durationNotificationValue, + timeNotificationEnabled: + timeNotificationEnabled ?? this.timeNotificationEnabled, + timeNotificationValue: + timeNotificationValue ?? this.timeNotificationValue, + dayNotificationEnabled: + dayNotificationEnabled ?? this.dayNotificationEnabled, + dayNotificationValue: dayNotificationValue ?? this.dayNotificationValue, + dayNotificationTimeValue: + dayNotificationTimeValue ?? this.dayNotificationTimeValue, + openingNotificationEnabled: + openingNotificationEnabled ?? this.openingNotificationEnabled, + closingNotificationEnabled: + closingNotificationEnabled ?? this.closingNotificationEnabled, + ); } } diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index 035a871b..09d8148e 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -12,9 +12,10 @@ class ScrollStatusBloc extends Bloc { ScrollStatusBloc({required this.scrollController}) : super(ScrollStatusState( - showCurrentStatus: true, - status: ScrollStatus.ok, - currentTarget: null)) { + showCurrentStatus: true, + status: ScrollStatus.ok, + currentTarget: null, + )) { on( _onScrollChanged, transformer: droppable(), @@ -25,13 +26,16 @@ class ScrollStatusBloc extends Bloc { ); } - Future _onScrollChanged( - ScrollStatusChanged event, Emitter emit) async { + void _onScrollChanged( + ScrollStatusChanged event, + Emitter emit, + ) { emit( state.copyWith( - showCurrentStatus: true, - status: ScrollStatus.ok, - currentTarget: state.currentTarget), + showCurrentStatus: true, + status: ScrollStatus.ok, + currentTarget: state.currentTarget, + ), ); } @@ -44,8 +48,11 @@ class ScrollStatusBloc extends Bloc { ? scrollController.position.pixels - offset : scrollController.position.pixels + offset; - await scrollController.animateTo(pixel, - duration: const Duration(milliseconds: 300), curve: Curves.linear); + await scrollController.animateTo( + pixel, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); targetContext = GlobalObjectKey(event.goTo.hashCode).currentContext; offset += 100; emit( @@ -55,13 +62,18 @@ class ScrollStatusBloc extends Bloc { ); } - Scrollable.ensureVisible(targetContext, - duration: const Duration(seconds: 1), curve: Curves.fastOutSlowIn); + // ignore: use_build_context_synchronously + Scrollable.ensureVisible( + targetContext, + duration: const Duration(seconds: 1), + curve: Curves.fastOutSlowIn, + ); emit( state.copyWith( - showCurrentStatus: false, - status: ScrollStatus.ok, - currentTarget: event.goTo), + 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 9cdc1c2e..dfeb5261 100644 --- a/lib/bloc/scroll_status/scroll_status_state.dart +++ b/lib/bloc/scroll_status/scroll_status_state.dart @@ -1,24 +1,27 @@ 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, - required this.currentTarget}); + ScrollStatusState({ + required this.status, + required this.showCurrentStatus, + required this.currentTarget, + }); - ScrollStatusState copyWith( - {bool? showCurrentStatus, - ScrollStatus? status, - AbstractChabanBridgeForecast? currentTarget}) { + ScrollStatusState copyWith({ + bool? showCurrentStatus, + ScrollStatus? status, + AbstractChabanBridgeForecast? currentTarget, + }) { return ScrollStatusState( - status: status ?? this.status, - showCurrentStatus: showCurrentStatus ?? this.showCurrentStatus, - currentTarget: currentTarget ?? this.currentTarget); + status: status ?? this.status, + showCurrentStatus: showCurrentStatus ?? this.showCurrentStatus, + currentTarget: currentTarget ?? this.currentTarget, + ); } } + +enum ScrollStatus { ok, error } diff --git a/lib/bloc/simple_bloc_observer.dart b/lib/bloc/simple_bloc_observer.dart deleted file mode 100644 index fe8198a1..00000000 --- a/lib/bloc/simple_bloc_observer.dart +++ /dev/null @@ -1,17 +0,0 @@ -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'); - } -} diff --git a/lib/bloc/theme/theme_bloc.dart b/lib/bloc/theme/theme_bloc.dart index 525d2667..6845096d 100644 --- a/lib/bloc/theme/theme_bloc.dart +++ b/lib/bloc/theme/theme_bloc.dart @@ -14,7 +14,7 @@ class ThemeBloc extends Bloc { final StorageService storageService; ThemeBloc({required this.storageService}) - : super(ThemeState(themeData: AppThemes.lightTheme)) { + : super(ThemeState(themeData: AppTheme.lightTheme)) { on( _onThemeChanged, ); @@ -27,11 +27,14 @@ class ThemeBloc extends Bloc { var brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness; bool isDarkMode = brightness == Brightness.dark; - return isDarkMode ? AppThemes.darkTheme : AppThemes.lightTheme; + + return isDarkMode ? AppTheme.darkTheme : AppTheme.lightTheme; } - Future _onAppStateChanged( - AppStateChanged event, Emitter emit) async { + void _onAppStateChanged( + AppStateChanged event, + Emitter emit, + ) { var savedStatus = storageService.readTheme(Const.storageThemeKey); if (savedStatus == null) { emit( @@ -45,14 +48,14 @@ class ThemeBloc extends Bloc { emit( state.copyWith( status: ThemeStateStatus.light, - themeData: AppThemes.lightTheme, + themeData: AppTheme.lightTheme, ), ); } else if (savedStatus == ThemeStateStatus.dark) { emit( state.copyWith( status: ThemeStateStatus.dark, - themeData: AppThemes.darkTheme, + themeData: AppTheme.darkTheme, ), ); } else if (savedStatus == ThemeStateStatus.system) { @@ -67,7 +70,9 @@ class ThemeBloc extends Bloc { } Future _onThemeChanged( - ThemeChanged event, Emitter emit) async { + ThemeChanged event, + Emitter emit, + ) async { await storageService.saveTheme( Const.storageThemeKey, event.status, @@ -76,14 +81,14 @@ class ThemeBloc extends Bloc { emit( state.copyWith( status: ThemeStateStatus.light, - themeData: AppThemes.lightTheme, + themeData: AppTheme.lightTheme, ), ); } else if (event.status == ThemeStateStatus.dark) { emit( state.copyWith( status: ThemeStateStatus.dark, - themeData: AppThemes.darkTheme, + themeData: AppTheme.darkTheme, ), ); } else if (event.status == ThemeStateStatus.system) { diff --git a/lib/bloc/theme/theme_state.dart b/lib/bloc/theme/theme_state.dart index 00bce4e4..c5442111 100644 --- a/lib/bloc/theme/theme_state.dart +++ b/lib/bloc/theme/theme_state.dart @@ -8,14 +8,14 @@ class ThemeState { ThemeState copyWith({ThemeStateStatus? status, ThemeData? themeData}) { return ThemeState( - status: status ?? this.status, themeData: themeData ?? this.themeData); + status: status ?? this.status, + themeData: themeData ?? this.themeData, + ); } IconData getIconData() { - if (themeData == AppThemes.lightTheme) { - return Icons.brightness_low; - } else { - return Icons.dark_mode_outlined; - } + return themeData == AppTheme.lightTheme + ? Icons.brightness_low + : Icons.dark_mode_outlined; } } diff --git a/lib/bloc/time_slot/time_slot_bloc.dart b/lib/bloc/time_slot/time_slot_bloc.dart index 7ac78fbf..94a4ce06 100644 --- a/lib/bloc/time_slot/time_slot_bloc.dart +++ b/lib/bloc/time_slot/time_slot_bloc.dart @@ -10,7 +10,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'time_slot_event.dart'; - part 'time_slot_state.dart'; class TimeSlotBloc extends Bloc { @@ -18,9 +17,10 @@ class TimeSlotBloc extends Bloc { TimeSlotBloc({required this.storageService}) : super(TimeSlotState( - timeSlots: Const.notificationFavoriteSlotsDefaultValue, - enabledForNotifications: - Const.notificationFavoriteSlotsEnabledDefaultValue)) { + timeSlots: Const.notificationFavoriteSlotsDefaultValue, + enabledForNotifications: + Const.notificationFavoriteSlotsEnabledDefaultValue, + )) { on( _onAppEvent, ); @@ -32,8 +32,10 @@ class TimeSlotBloc extends Bloc { ); } - Future _onAppEvent( - TimeSlotAppEvent event, Emitter emit) async { + void _onAppEvent( + TimeSlotAppEvent event, + Emitter emit, + ) { final timeSlots = storageService.readTimeSlots(Const.notificationFavoriteSlotsValueKey) ?? Const.notificationFavoriteSlotsDefaultValue; @@ -42,27 +44,42 @@ class TimeSlotBloc extends Bloc { storageService.readBool(Const.notificationFavoriteSlotsEnabledKey) ?? Const.notificationFavoriteSlotsEnabledDefaultValue; emit(state.copyWith( - timeSlots: timeSlots, - enabledForNotifications: enabledForNotifications)); + timeSlots: timeSlots, + enabledForNotifications: enabledForNotifications, + )); } Future _onEnabledTimeSlotEvent( - EnabledTimeSlotEvent event, Emitter emit) async { + EnabledTimeSlotEvent event, + Emitter emit, + ) async { await storageService.saveBool( - Const.notificationFavoriteSlotsEnabledKey, event.enabled); + Const.notificationFavoriteSlotsEnabledKey, + event.enabled, + ); HapticFeedback.lightImpact(); - emit(state.copyWith(enabledForNotifications: event.enabled)); + emit(state.copyWith( + enabledForNotifications: event.enabled, + )); } Future _onTimeSlotsEventValue( - ValueTimeSlotEvent event, Emitter emit) async { + ValueTimeSlotEvent event, + Emitter emit, + ) async { final timeSlots = List.from(state.timeSlots); timeSlots[event.index] = event.timeSlot; await storageService.saveTimeSlots( - Const.notificationFavoriteSlotsValueKey, timeSlots); + Const.notificationFavoriteSlotsValueKey, + timeSlots, + ); HapticFeedback.lightImpact(); - emit(state.copyWith(timeSlots: timeSlots)); + emit( + state.copyWith( + timeSlots: timeSlots, + ), + ); } } diff --git a/lib/bloc/time_slot/time_slot_state.dart b/lib/bloc/time_slot/time_slot_state.dart index 215c6886..07e11de4 100644 --- a/lib/bloc/time_slot/time_slot_state.dart +++ b/lib/bloc/time_slot/time_slot_state.dart @@ -4,8 +4,10 @@ class TimeSlotState extends Equatable { final List timeSlots; final bool enabledForNotifications; - const TimeSlotState( - {required this.timeSlots, required this.enabledForNotifications}); + const TimeSlotState({ + required this.timeSlots, + required this.enabledForNotifications, + }); TimeSlotState copyWith({ List? timeSlots, diff --git a/lib/chabo.dart b/lib/chabo.dart index dd0be7aa..95482ef9 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,11 +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/time_slot/time_slot_bloc.dart'; -import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.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/bloc/time_slot/time_slot_bloc.dart'; +import 'package:chabo/cubits/floating_actions_cubit.dart'; +import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; @@ -115,6 +115,7 @@ class Chabo extends StatelessWidget { return deviceLocale; } } + return const Locale('en', ''); }, ); diff --git a/lib/const.dart b/lib/const.dart index 1dec4bdf..a94068c2 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:chabo/models/time_slot.dart'; +import 'package:chabo/models/web_link_icon.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -25,16 +25,31 @@ class Const { static const String privacyInfoLink = 'https://chabo.vareversat.fr/privacy'; 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'), + 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 @@ -95,7 +110,7 @@ class Const { hour: 19, minute: 30, ), - ) + ), ]; /// UI diff --git a/lib/cubits/floating_actions_cubit.dart b/lib/cubits/floating_actions_cubit.dart index f3c24c73..711b78a5 100644 --- a/lib/cubits/floating_actions_cubit.dart +++ b/lib/cubits/floating_actions_cubit.dart @@ -25,7 +25,7 @@ class FloatingActionsCubit extends Cubit { ); } - void init() async { + void init() { final isRightHanded = storageService.readBool(Const.isRightHandedKey) ?? Const.isRightHandedDefaultValue; emit( @@ -40,13 +40,16 @@ class FloatingActionsState extends Equatable { final bool isMenuOpen; final bool isRightHanded; - const FloatingActionsState( - {required this.isMenuOpen, required this.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); + isMenuOpen: isMenuOpen ?? this.isMenuOpen, + isRightHanded: isRightHanded ?? this.isRightHanded, + ); } @override diff --git a/lib/custom_widgets_state.dart b/lib/custom_widget_state.dart similarity index 100% rename from lib/custom_widgets_state.dart rename to lib/custom_widget_state.dart diff --git a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart index 11f46aac..19731c49 100644 --- a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart +++ b/lib/dialogs/chaban_bridge_forecast_information_dialog.dart @@ -6,8 +6,10 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ChabanBridgeForecastInformationDialog extends StatelessWidget { final AbstractChabanBridgeForecast chabanBridgeForecast; - const ChabanBridgeForecastInformationDialog( - {super.key, required this.chabanBridgeForecast}); + const ChabanBridgeForecastInformationDialog({ + super.key, + required this.chabanBridgeForecast, + }); @override Widget build(BuildContext context) { diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart index ac2fac02..a3b4c502 100644 --- a/lib/dialogs/chabo_about_dialog.dart +++ b/lib/dialogs/chabo_about_dialog.dart @@ -20,6 +20,9 @@ class ChaboAboutDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + return FutureBuilder( builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -29,6 +32,7 @@ class ChaboAboutDialog extends StatelessWidget { snapshot.data == null) { return Text(AppLocalizations.of(context)!.unableAppInfo); } + return AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20), titlePadding: const EdgeInsets.all(20), @@ -70,14 +74,14 @@ class ChaboAboutDialog extends StatelessWidget { ), ), Text( - ' | v${snapshot.data!.version} (${snapshot.data!.buildNumber})', - style: Theme.of(context).textTheme.bodyMedium), + ' | v${snapshot.data!.version} (${snapshot.data!.buildNumber})', + style: textTheme.bodyMedium, + ), ], ), Text( Const.legalLease, - style: - Theme.of(context).textTheme.bodySmall!.copyWith(), + style: textTheme.bodySmall!.copyWith(), ), ], ), @@ -94,7 +98,7 @@ class ChaboAboutDialog extends StatelessWidget { children: [ Text( AppLocalizations.of(context)!.appDescription, - style: Theme.of(context).textTheme.bodyLarge, + style: textTheme.bodyLarge, ), const SizedBox( height: 15, @@ -116,7 +120,7 @@ class ChaboAboutDialog extends StatelessWidget { children: Const.usefulLinks .map( (link) => ElevatedButton( - onPressed: () async => link.launchURL(), + onPressed: () => link.launchURL(), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -156,9 +160,11 @@ class ChaboAboutDialog extends StatelessWidget { ElevatedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.secondaryContainer), + colorScheme.secondaryContainer, + ), foregroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.onSecondaryContainer), + colorScheme.onSecondaryContainer, + ), ), onPressed: () => Navigator.push( context, @@ -190,9 +196,11 @@ class ChaboAboutDialog extends StatelessWidget { ElevatedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.secondaryContainer), + colorScheme.secondaryContainer, + ), foregroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.onSecondaryContainer), + colorScheme.onSecondaryContainer, + ), ), onPressed: () { showLicensePage( @@ -249,7 +257,8 @@ class ChaboAboutDialog extends StatelessWidget { style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.white), foregroundColor: MaterialStateProperty.all( - Theme.of(context).primaryColor), + Theme.of(context).primaryColor, + ), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular( @@ -263,7 +272,7 @@ class ChaboAboutDialog extends StatelessWidget { label: Text( MaterialLocalizations.of(context).closeButtonLabel, ), - ) + ), ], scrollable: true, ); diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index 75cf13a5..cf550f2d 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -16,7 +16,7 @@ class DaysOfTheWeekDialog extends StatelessWidget { 15, ), ), - content: BlocBuilder( + content: BlocBuilder( builder: (context, state) { return Wrap( alignment: WrapAlignment.center, @@ -25,7 +25,7 @@ class DaysOfTheWeekDialog extends StatelessWidget { runSpacing: 10, children: [ ElevatedButton( - onPressed: () {}, + onPressed: () {}, // ignore: no-empty-block child: DropdownButtonHideUnderline( child: DropdownButton( borderRadius: BorderRadius.circular(12.0), @@ -63,8 +63,8 @@ class DaysOfTheWeekDialog extends StatelessWidget { style: Theme.of(context).textTheme.titleMedium, ), ElevatedButton( - onPressed: () async { - var time = await showTimePicker( + onPressed: () { + showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, initialTime: state.dayNotificationTimeValue, @@ -74,15 +74,18 @@ class DaysOfTheWeekDialog extends StatelessWidget { child: child!, ); }, + ).then( + (value) => { + if (value != null) + { + BlocProvider.of(context).add( + DayNotificationTimeValueEvent( + time: value, + ), + ), + }, + }, ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - DayNotificationTimeValueEvent( - time: time, - ), - ); - } }, child: Text( state.dayNotificationTimeValue.format(context), diff --git a/lib/dialogs/theme_picker_dialog.dart b/lib/dialogs/theme_picker_dialog.dart deleted file mode 100644 index 3e4c5805..00000000 --- a/lib/dialogs/theme_picker_dialog.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:chabo/bloc/theme/theme_bloc.dart'; -import 'package:chabo/custom_properties.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 ThemePickerDialog extends StatelessWidget { - const ThemePickerDialog({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return AlertDialog( - content: BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - RadioListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.lightTheme, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox( - width: 10, - ), - AnimatedRotation( - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs), - curve: Curves.easeOut, - turns: state.status == ThemeStateStatus.light ? 1 : 0, - child: Icon( - Icons.brightness_low, - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ), - value: ThemeStateStatus.light, - groupValue: state.status, - onChanged: (ThemeStateStatus? value) { - if (value != null) { - BlocProvider.of(context).add( - ThemeChanged(status: value), - ); - } - }, - ), - RadioListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.darkTheme, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - width: 10, - ), - AnimatedRotation( - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs), - curve: Curves.easeOut, - turns: state.status == ThemeStateStatus.light ? 0 : 1, - child: Icon( - Icons.dark_mode_outlined, - color: Theme.of(context).colorScheme.secondary, - ), - ), - ], - ), - value: ThemeStateStatus.dark, - groupValue: state.status, - onChanged: (ThemeStateStatus? value) { - if (value != null) { - BlocProvider.of(context).add( - ThemeChanged(status: value), - ); - } - }, - ), - RadioListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.systemTheme, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox( - width: 10, - ), - const Icon( - Icons.settings, - ), - ], - ), - value: ThemeStateStatus.system, - groupValue: state.status, - onChanged: (ThemeStateStatus? value) { - if (value != null) { - BlocProvider.of(context).add( - ThemeChanged( - status: value, - ), - ); - } - }, - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/dialogs/time_slot_dialog.dart b/lib/dialogs/time_slot_dialog.dart index 4e4cbc1e..a4547808 100644 --- a/lib/dialogs/time_slot_dialog.dart +++ b/lib/dialogs/time_slot_dialog.dart @@ -11,6 +11,8 @@ class TimeSlotDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return AlertDialog( contentPadding: const EdgeInsets.all(15), shape: RoundedRectangleBorder( @@ -28,11 +30,11 @@ class TimeSlotDialog extends StatelessWidget { children: [ Text( ' ${AppLocalizations.of(context)!.favoriteSlotsFrom} ', - style: Theme.of(context).textTheme.titleMedium, + style: textTheme.titleMedium, ), ElevatedButton( - onPressed: () async { - var time = await showTimePicker( + onPressed: () { + showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, initialTime: state.timeSlots[index].from, @@ -42,31 +44,34 @@ class TimeSlotDialog extends StatelessWidget { child: child!, ); }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - ValueTimeSlotEvent( - timeSlot: TimeSlot( - name: state.timeSlots[index].name, - from: time, - to: state.timeSlots[index].to), - index: index), - ); - } + ).then((value) => { + if (value != null) + { + BlocProvider.of(context).add( + ValueTimeSlotEvent( + timeSlot: TimeSlot( + name: state.timeSlots[index].name, + from: value, + to: state.timeSlots[index].to, + ), + index: index, + ), + ), + }, + }); }, child: Text( state.timeSlots[index].from.format(context), - style: Theme.of(context).textTheme.titleMedium, + style: textTheme.titleMedium, ), ), Text( ' ${AppLocalizations.of(context)!.favoriteSlotsTo} ', - style: Theme.of(context).textTheme.titleMedium, + style: textTheme.titleMedium, ), ElevatedButton( - onPressed: () async { - var time = await showTimePicker( + onPressed: () { + showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, initialTime: state.timeSlots[index].to, @@ -76,22 +81,25 @@ class TimeSlotDialog extends StatelessWidget { child: child!, ); }, - ); - if (time != null) { - // ignore: use_build_context_synchronously - BlocProvider.of(context).add( - ValueTimeSlotEvent( - timeSlot: TimeSlot( - name: state.timeSlots[index].name, - from: state.timeSlots[index].from, - to: time), - index: index), - ); - } + ).then((value) => { + if (value != null) + { + BlocProvider.of(context).add( + ValueTimeSlotEvent( + timeSlot: TimeSlot( + name: state.timeSlots[index].name, + from: state.timeSlots[index].from, + to: value, + ), + index: index, + ), + ), + }, + }); }, child: Text( state.timeSlots[index].to.format(context), - style: Theme.of(context).textTheme.titleMedium, + style: textTheme.titleMedium, ), ), ], diff --git a/lib/extensions/boats_extensions.dart b/lib/extensions/boats_extension.dart similarity index 93% rename from lib/extensions/boats_extensions.dart rename to lib/extensions/boats_extension.dart index d4026946..86e64361 100644 --- a/lib/extensions/boats_extensions.dart +++ b/lib/extensions/boats_extension.dart @@ -15,7 +15,10 @@ extension BoatsExtension on List { .add(TextSpan(text: ' ${AppLocalizations.of(context)!.and} ')); } } - return TextSpan(children: finalTextSpan); + + return TextSpan( + children: finalTextSpan, + ); } } @@ -30,6 +33,7 @@ extension BoatsExtension on List { finalString += ' ${AppLocalizations.of(context)!.and} '; } } + return finalString; } } diff --git a/lib/extensions/date_time_extension.dart b/lib/extensions/date_time_extension.dart index c16b8d87..70f19b77 100644 --- a/lib/extensions/date_time_extension.dart +++ b/lib/extensions/date_time_extension.dart @@ -2,16 +2,15 @@ import 'package:flutter/material.dart'; 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), - ); - } + return day == weekday + ? subtract(const Duration(days: 7)) + : subtract( + Duration( + days: (weekday - day) % DateTime.daysPerWeek, + hours: hour, + minutes: minute, + ), + ); } DateTime applied(TimeOfDay time) { diff --git a/lib/extensions/duration_extension.dart b/lib/extensions/duration_extension.dart index 35bb91b2..4dc7defe 100644 --- a/lib/extensions/duration_extension.dart +++ b/lib/extensions/duration_extension.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -extension DurationExtention on Duration { +extension DurationExtension on Duration { String durationToString(BuildContext context) { final dayToken = inDays == 0 ? '' @@ -15,6 +15,7 @@ extension DurationExtention on Duration { final secsToken = inSeconds.remainder(60) == 0 ? '' : '${inSeconds.remainder(60).toString()}s '; + return '$dayToken$hourToken$minToken$secsToken'; } diff --git a/lib/extensions/string_extension.dart b/lib/extensions/string_extension.dart index 860aff14..d8444c08 100644 --- a/lib/extensions/string_extension.dart +++ b/lib/extensions/string_extension.dart @@ -1,9 +1,7 @@ extension StringExtension on String { String capitalize() { - if (isEmpty) { - return this; - } else { - return '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; - } + return isEmpty + ? this + : '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; } } diff --git a/lib/helpers/ad_helper.dart b/lib/helpers/ad_helper.dart index 9c143e24..3c6ff4af 100644 --- a/lib/helpers/ad_helper.dart +++ b/lib/helpers/ad_helper.dart @@ -8,7 +8,8 @@ class AdHelper { return Const.androidInlineBanner; } else { throw UnsupportedError( - 'Unsupported platform to determine the banner unit ID'); + 'Unsupported platform to determine the banner unit ID', + ); } } @@ -17,7 +18,8 @@ class AdHelper { return Const.androidNativeBanner; } else { throw UnsupportedError( - 'Unsupported platform to determine the banner unit ID'); + 'Unsupported platform to determine the banner unit ID', + ); } } } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 61544ea8..a004f570 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -178,5 +178,4 @@ } }, "favoriteSlotsInterferenceWarning": "Cette prévision interfère avec un ou plusieurs créneaux" - } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 2596d76e..c555330f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,8 @@ void main() async { runApp( Chabo( - storageService: storageService, - notificationService: notificationService), + storageService: storageService, + notificationService: notificationService, + ), ); } diff --git a/lib/misc/no_scaling_animation.dart b/lib/misc/no_scaling_animation.dart index 4b9cf364..3e107ae5 100644 --- a/lib/misc/no_scaling_animation.dart +++ b/lib/misc/no_scaling_animation.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; class NoScalingAnimation extends FloatingActionButtonAnimator { @override - Offset getOffset( - {required Offset begin, required Offset end, required double progress}) { + Offset getOffset({ + required Offset begin, + required Offset end, + required double progress, + }) { return end; } diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_chaban_bridge_forecast.dart index a586266b..5f5f431b 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -16,12 +16,13 @@ abstract class AbstractChabanBridgeForecast extends Equatable { final ChabanBridgeForecastClosingType closingType; final List interferingTimeSlots = []; - AbstractChabanBridgeForecast( - {required this.totalClosing, - required this.closingReason, - required DateTime circulationClosingDate, - required DateTime circulationReOpeningDate, - required this.closingType}) { + AbstractChabanBridgeForecast({ + required this.totalClosing, + required this.closingReason, + required DateTime circulationClosingDate, + required DateTime circulationReOpeningDate, + required this.closingType, + }) { _circulationClosingDate = circulationClosingDate; var tmpCirculationReOpeningDate = circulationReOpeningDate.toLocal(); @@ -56,7 +57,9 @@ abstract class AbstractChabanBridgeForecast extends Equatable { Widget getIconWidget(BuildContext context, bool reversed); String getNotificationDurationMessage( - BuildContext context, String pickedDuration); + BuildContext context, + String pickedDuration, + ); String getNotificationTimeMessage(BuildContext context); @@ -94,27 +97,34 @@ abstract class AbstractChabanBridgeForecast extends Equatable { bool isOverlappingWithTimeOfDay(TimeOfDay timeOfDay) { final dateTimeConversion = circulationClosingDate.applied(timeOfDay); - return dateTimeConversion.isAfter(circulationClosingDate) && - dateTimeConversion.isBefore(circulationReOpeningDate); + + return dateTimeConversion.isAfter( + circulationClosingDate, + ) && + dateTimeConversion.isBefore( + circulationReOpeningDate, + ); } static bool getBooleanTotalClosingValue(String stringValue) { - if (stringValue == 'oui') { - return true; - } else { - return false; - } + return stringValue == 'oui'; } static String getApiTimeZone(String recordTimestamp) { return recordTimestamp.substring( - recordTimestamp.indexOf('+'), recordTimestamp.length); + recordTimestamp.indexOf('+'), + recordTimestamp.length, + ); } static DateTime parseFieldDate( - Map json, String fieldName, String timezone) { + Map json, + String fieldName, + String timezone, + ) { return DateTime.parse( - "${json['fields']['date_passage']}T${json['fields'][fieldName]}:00$timezone"); + "${json['fields']['date_passage']}T${json['fields'][fieldName]}:00$timezone", + ); } @override diff --git a/lib/models/boat.dart b/lib/models/boat.dart index 6e4dd6d0..141733fd 100644 --- a/lib/models/boat.dart +++ b/lib/models/boat.dart @@ -31,20 +31,25 @@ class Boat { decoration: TextDecoration.underline, ), ); - if (isLeaving) { - return TextSpan(children: [ - TextSpan( - text: - '${AppLocalizations.of(context)!.dialogInformationContentBridgeDeparture} '), - textSpanLink, - ]); - } else { - return TextSpan(children: [ - TextSpan( - text: - '${AppLocalizations.of(context)!.dialogInformationContentBridgeArrival} '), - textSpanLink, - ]); - } + + return isLeaving + ? TextSpan( + children: [ + TextSpan( + text: + '${AppLocalizations.of(context)!.dialogInformationContentBridgeDeparture} ', + ), + textSpanLink, + ], + ) + : TextSpan( + children: [ + TextSpan( + text: + '${AppLocalizations.of(context)!.dialogInformationContentBridgeArrival} ', + ), + textSpanLink, + ], + ); } } diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/chaban_bridge_boat_forecast.dart index 4584b1d9..5ddaba4a 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/chaban_bridge_boat_forecast.dart @@ -1,4 +1,4 @@ -import 'package:chabo/extensions/boats_extensions.dart'; +import 'package:chabo/extensions/boats_extension.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/extensions/string_extension.dart'; @@ -15,34 +15,42 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { static final List allBoatNames = []; - ChabanBridgeBoatForecast( - {required bool totalClosing, - required DateTime circulationClosingDate, - required DateTime circulationReOpeningDate, - required this.boats, - required ChabanBridgeForecastClosingType closingType}) - : assert(boats.isNotEmpty), + ChabanBridgeBoatForecast({ + required bool totalClosing, + required DateTime circulationClosingDate, + required DateTime circulationReOpeningDate, + required this.boats, + required ChabanBridgeForecastClosingType closingType, + }) : assert(boats.isNotEmpty), super( - circulationClosingDate: circulationClosingDate, - circulationReOpeningDate: circulationReOpeningDate, - closingReason: ChabanBridgeForecastClosingReason.boat, - closingType: closingType, - totalClosing: totalClosing); + circulationClosingDate: circulationClosingDate, + circulationReOpeningDate: circulationReOpeningDate, + closingReason: ChabanBridgeForecastClosingReason.boat, + closingType: closingType, + totalClosing: totalClosing, + ); factory ChabanBridgeBoatForecast.fromJSON(Map json) { var apiTimezone = AbstractChabanBridgeForecast.getApiTimeZone(json['record_timestamp']); var closingDate = AbstractChabanBridgeForecast.parseFieldDate( - json, 'fermeture_a_la_circulation', apiTimezone); + json, + 'fermeture_a_la_circulation', + apiTimezone, + ); var reopeningDate = AbstractChabanBridgeForecast.parseFieldDate( - json, 're_ouverture_a_la_circulation', apiTimezone); + json, + 're_ouverture_a_la_circulation', + apiTimezone, + ); var closingType = (json['fields']['type_de_fermeture'] as String).toLowerCase() == 'totale' ? ChabanBridgeForecastClosingType.complete : ChabanBridgeForecastClosingType.partial; var totalClosing = AbstractChabanBridgeForecast.getBooleanTotalClosingValue( - json['fields']['fermeture_totale']); + json['fields']['fermeture_totale'], + ); List boats = []; bool isLeaving = false; @@ -59,11 +67,12 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { } return ChabanBridgeBoatForecast( - boats: boats, - totalClosing: totalClosing, - circulationReOpeningDate: reopeningDate, - circulationClosingDate: closingDate, - closingType: closingType); + boats: boats, + totalClosing: totalClosing, + circulationReOpeningDate: reopeningDate, + circulationClosingDate: closingDate, + closingType: closingType, + ); } @override @@ -74,7 +83,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { boats, circulationClosingDate, circulationReOpeningDate, - closingType + closingType, ]; @override @@ -135,12 +144,14 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { ), ), TextSpan( - text: - ', ${AppLocalizations.of(context)!.dialogInformationContentBridge_closed} '), + text: + ', ${AppLocalizations.of(context)!.dialogInformationContentBridge_closed} ', + ), boats.toLocalizedTextSpan(context), TextSpan( - text: - '\n\n${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : '), + text: + '\n\n${AppLocalizations.of(context)!.dialogInformationContentClosing_time.capitalize()} : ', + ), TextSpan( text: '${closedDuration.durationToString(context)}\n', style: TextStyle( @@ -149,8 +160,9 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { ), ), TextSpan( - text: - '${AppLocalizations.of(context)!.dialogInformationContentTime_of_crossing.capitalize()} : '), + text: + '${AppLocalizations.of(context)!.dialogInformationContentTime_of_crossing.capitalize()} : ', + ), TextSpan( text: scheduleString, style: TextStyle( @@ -165,7 +177,9 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { @override String getNotificationDurationMessage( - BuildContext context, String pickedDuration) { + BuildContext context, + String pickedDuration, + ) { return AppLocalizations.of(context)!.notificationDurationBoatMessage( boats.toLocalizedString(context), pickedDuration, diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/chaban_bridge_maintenance_forecast.dart index 62c54e3a..5b2d303d 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/chaban_bridge_maintenance_forecast.dart @@ -9,38 +9,47 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { - ChabanBridgeMaintenanceForecast( - {required bool totalClosing, - required DateTime circulationClosingDate, - required DateTime circulationReOpeningDate, - required ChabanBridgeForecastClosingType closingType}) - : super( - circulationClosingDate: circulationClosingDate, - circulationReOpeningDate: circulationReOpeningDate, - closingReason: ChabanBridgeForecastClosingReason.maintenance, - closingType: closingType, - totalClosing: totalClosing); + ChabanBridgeMaintenanceForecast({ + required bool totalClosing, + required DateTime circulationClosingDate, + required DateTime circulationReOpeningDate, + required ChabanBridgeForecastClosingType closingType, + }) : super( + circulationClosingDate: circulationClosingDate, + circulationReOpeningDate: circulationReOpeningDate, + closingReason: ChabanBridgeForecastClosingReason.maintenance, + closingType: closingType, + totalClosing: totalClosing, + ); factory ChabanBridgeMaintenanceForecast.fromJSON(Map json) { var apiTimezone = AbstractChabanBridgeForecast.getApiTimeZone(json['record_timestamp']); var closingDate = AbstractChabanBridgeForecast.parseFieldDate( - json, 'fermeture_a_la_circulation', apiTimezone); + json, + 'fermeture_a_la_circulation', + apiTimezone, + ); var reopeningDate = AbstractChabanBridgeForecast.parseFieldDate( - json, 're_ouverture_a_la_circulation', apiTimezone); + json, + 're_ouverture_a_la_circulation', + apiTimezone, + ); var closingType = (json['fields']['type_de_fermeture'] as String).toLowerCase() == 'totale' ? ChabanBridgeForecastClosingType.complete : ChabanBridgeForecastClosingType.partial; var totalClosing = AbstractChabanBridgeForecast.getBooleanTotalClosingValue( - json['fields']['fermeture_totale']); + json['fields']['fermeture_totale'], + ); return ChabanBridgeMaintenanceForecast( - totalClosing: totalClosing, - circulationReOpeningDate: reopeningDate, - circulationClosingDate: closingDate, - closingType: closingType); + totalClosing: totalClosing, + circulationReOpeningDate: reopeningDate, + circulationClosingDate: closingDate, + closingType: closingType, + ); } @override @@ -50,12 +59,14 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { closedDuration, circulationClosingDate, circulationReOpeningDate, - closingType + closingType, ]; @override String getNotificationDurationMessage( - BuildContext context, String pickedDuration) { + BuildContext context, + String pickedDuration, + ) { return AppLocalizations.of(context)!.notificationDurationMaintenanceMessage( pickedDuration, closedDuration.durationToString(context), @@ -73,7 +84,10 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { @override String getNotificationClosingMessage(BuildContext context) { return AppLocalizations.of(context)!.notificationClosingMaintenanceMessage( - closedDuration.durationToString(context)); + closedDuration.durationToString( + context, + ), + ); } @override @@ -99,6 +113,7 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { '${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), diff --git a/lib/models/time_slot.dart b/lib/models/time_slot.dart index c5fe19b8..61c86218 100644 --- a/lib/models/time_slot.dart +++ b/lib/models/time_slot.dart @@ -19,6 +19,7 @@ class TimeSlot extends Equatable { factory TimeSlot.fromJSON(Map json) { final format = DateFormat.Hm(); + return TimeSlot( name: json['name'] ?? '', from: TimeOfDay.fromDateTime( diff --git a/lib/models/time_slots.dart b/lib/models/time_slots.dart deleted file mode 100644 index e7abbaa3..00000000 --- a/lib/models/time_slots.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:chabo/models/time_slot.dart'; - -class TimeSlots { - final List timeSlots; - - TimeSlots({required this.timeSlots}); -} diff --git a/lib/models/link_icon.dart b/lib/models/web_link_icon.dart similarity index 100% rename from lib/models/link_icon.dart rename to lib/models/web_link_icon.dart diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 7963bf93..818e554e 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/cubits/floating_actions_cubit.dart'; import 'package:chabo/bloc/notification/notification_bloc.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/cubits/floating_actions_cubit.dart'; +import 'package:chabo/cubits/notification_service_cubit.dart'; +import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; +import 'package:chabo/widgets/floating_actions/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'; @@ -55,7 +55,7 @@ class _ChabanBridgeForecastScreenState listeners: [ BlocListener( - listener: (context, state) async { + listener: (context, state) { BlocProvider.of(context) .add( ChabanBridgeStatusChanged( @@ -70,8 +70,8 @@ class _ChabanBridgeForecastScreenState ); }, ), - BlocListener( - listener: (context, state) async { + BlocListener( + listener: (context, state) { BlocProvider.of(context) .add( ChabanBridgeStatusDurationChanged( @@ -106,51 +106,57 @@ class _ChabanBridgeForecastScreenState ), ), ); - await context + 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, + BlocProvider.of( + context, + ).state.chabanBridgeForecasts, + state, + context, + ) + .then( + (value) => { + 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 [ diff --git a/lib/screens/changelog_screen.dart b/lib/screens/changelog_screen.dart index 1c1e60b8..c16647b1 100644 --- a/lib/screens/changelog_screen.dart +++ b/lib/screens/changelog_screen.dart @@ -1,5 +1,5 @@ import 'package:chabo/const.dart'; -import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/screens/error_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -17,8 +17,10 @@ class ChangeLogScreen extends StatefulWidget { class _ChangeLogScreenState extends CustomWidgetState { String _getChangelogPath(BuildContext context) { - return Const.changelogPath.replaceAll(Const.changelogPlaceholder, - Localizations.localeOf(context).languageCode); + return Const.changelogPath.replaceAll( + Const.changelogPlaceholder, + Localizations.localeOf(context).languageCode, + ); } @override @@ -41,6 +43,7 @@ class _ChangeLogScreenState extends CustomWidgetState { errorMessage: snapshot.error.toString(), ); } + return const Center( child: CircularProgressIndicator(), ); diff --git a/lib/screens/error_screen.dart b/lib/screens/error_screen.dart index d5494cc7..2d581e83 100644 --- a/lib/screens/error_screen.dart +++ b/lib/screens/error_screen.dart @@ -1,4 +1,4 @@ -import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/custom_widget_state.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -44,15 +44,16 @@ class _ErrorScreenState extends CustomWidgetState { ), ), Flexible( - flex: 1, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '${AppLocalizations.of(context)!.errorScreenContentTechnical_Info} : ${widget.errorMessage}', - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey), - ), - )) + flex: 1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${AppLocalizations.of(context)!.errorScreenContentTechnical_Info} : ${widget.errorMessage}', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey), + ), + ), + ), ], ), ), diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 3c6ac6ce..663deb25 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -4,7 +4,7 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/time_slot/time_slot_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_properties.dart'; -import 'package:chabo/custom_widgets_state.dart'; +import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; import 'package:chabo/extensions/color_scheme_extension.dart'; import 'package:chabo/extensions/duration_extension.dart'; @@ -61,7 +61,7 @@ class _NotificationScreenState extends CustomWidgetState { padding: const EdgeInsets.only( top: 20, ), - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { return Column( children: [ @@ -88,17 +88,19 @@ class _NotificationScreenState extends CustomWidgetState { Padding( padding: const EdgeInsets.all(8.0), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - for (var i = 0; - i < state.timeSlots.length; - i++) ...[ - TimeSlotWidget( - timeSlot: state.timeSlots[i], - index: i) - ], - ]), + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + for (var i = 0; + i < state.timeSlots.length; + i++) ...[ + TimeSlotWidget( + timeSlot: state.timeSlots[i], + index: i, + ), + ], + ], + ), ), ], ); @@ -149,8 +151,8 @@ class _NotificationScreenState extends CustomWidgetState { height: 20, ), _CustomListTile( - onTap: () async { - var time = await showTimePicker( + onTap: () { + showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, initialTime: state.durationNotificationValue @@ -163,18 +165,21 @@ class _NotificationScreenState extends CustomWidgetState { child: child!, ); }, + ).then( + (value) => { + if (value != null) + { + BlocProvider.of(context).add( + DurationNotificationValueEvent( + duration: Duration( + hours: value.hour, + minutes: value.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( @@ -196,8 +201,8 @@ class _NotificationScreenState extends CustomWidgetState { leadingIcon: Icons.timer_outlined, ), _CustomListTile( - onTap: () async { - var time = await showTimePicker( + onTap: () { + showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, initialTime: state.timeNotificationValue, @@ -209,18 +214,21 @@ class _NotificationScreenState extends CustomWidgetState { child: child!, ); }, + ).then( + (value) => { + if (value != null) + { + BlocProvider.of(context).add( + TimeNotificationValueEvent( + time: TimeOfDay( + hour: value.hour, + minute: value.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( @@ -240,25 +248,32 @@ class _NotificationScreenState extends CustomWidgetState { leadingIcon: Icons.plus_one_outlined, ), _CustomListTile( - onTap: () async { - final day = await showDialog( + onTap: () { + showDialog( context: context, builder: ( BuildContext context, ) { return BackdropFilter( filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), child: const DaysOfTheWeekDialog(), ); }, + ).then( + (value) => { + if (value != null) + { + BlocProvider.of(context).add( + DayNotificationValueEvent( + day: value, + ), + ), + }, + }, ); - if (day != null) { - BlocProvider.of(context).add( - DayNotificationValueEvent(day: day), - ); - } }, enabled: state.dayNotificationEnabled, title: AppLocalizations.of(context)!.dayNotificationTitle( @@ -266,8 +281,11 @@ class _NotificationScreenState extends CustomWidgetState { ), subtitle: AppLocalizations.of(context)! .dayNotificationExplanation( - state.dayNotificationValue.localizedName(context), - state.dayNotificationTimeValue.format(context)), + state.dayNotificationValue.localizedName(context), + state.dayNotificationTimeValue.format( + context, + ), + ), leadingIcon: Icons.calendar_month_outlined, onChanged: (bool value) => BlocProvider.of(context).add( @@ -296,16 +314,16 @@ class _CustomListTile extends StatelessWidget { 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); + 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) { diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart deleted file mode 100644 index 95edf0a3..00000000 --- a/lib/screens/settings_screen.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'dart:ui'; - -import 'package:chabo/bloc/theme/theme_bloc.dart'; -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/custom_widgets_state.dart'; -import 'package:chabo/dialogs/chabo_about_dialog.dart'; -import 'package:chabo/dialogs/theme_picker_dialog.dart'; -import 'package:chabo/models/enums/theme_state_status.dart'; -import 'package:chabo/widgets/notification_tile_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class SettingsScreen extends StatefulWidget { - const SettingsScreen({Key? key}) : super(key: key); - - @override - State createState() { - return _SettingsScreenState(); - } -} - -class _SettingsScreenState extends CustomWidgetState { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - leading: const 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( - builder: (context, state) { - return ListTile( - key: const ValueKey('themeDialog'), - title: Text( - AppLocalizations.of(context)!.themeSetting, - style: const TextStyle(fontSize: 25), - ), - subtitle: Text( - AppLocalizations.of(context)!.themeSettingSubtitle, - style: const TextStyle( - fontSize: 15, - ), - ), - selected: true, - leading: AnimatedRotation( - duration: const Duration( - milliseconds: CustomProperties.animationDurationMs), - turns: state.status == ThemeStateStatus.light ? 0 : 1, - child: Icon( - state.getIconData(), - size: 30, - ), - ), - onTap: () async => await showDialog( - context: context, - builder: ( - BuildContext context, - ) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), - child: const ThemePickerDialog(), - ); - }, - ), - ); - }, - ), - const NotificationTileWidget(), - ListTile( - key: const ValueKey('aboutButton'), - title: Text( - AppLocalizations.of(context)!.about, - style: const TextStyle(fontSize: 25), - ), - subtitle: Text( - AppLocalizations.of(context)!.informationAboutTheApp, - style: const TextStyle( - fontSize: 15, - ), - ), - selected: true, - leading: const Icon( - Icons.info_outline, - size: 30, - ), - onTap: () async => await showDialog( - context: context, - builder: (BuildContext context) { - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY, - ), - child: ChaboAboutDialog(), - ); - }, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 4ee75364..6396dea6 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use_build_context_synchronously + import 'dart:developer' as developer; import 'dart:io'; @@ -23,8 +25,9 @@ class NotificationService { NotificationService._({required this.storageService}); - static Future create( - {required StorageService storageService}) async { + static Future create({ + required StorageService storageService, + }) async { var notificationService = NotificationService._(storageService: storageService); @@ -37,31 +40,38 @@ class NotificationService { ); /// Initialize the notification plugin - await localNotifications.initialize(initializationSettings, - onDidReceiveNotificationResponse: _onDidReceiveLocalNotification, - onDidReceiveBackgroundNotificationResponse: - _onDidReceiveBackgroundNotificationResponse); - developer.log('Notification plugin initialized', - name: 'notification-service.on.ctor'); + await localNotifications.initialize( + initializationSettings, + onDidReceiveNotificationResponse: _onDidReceiveLocalNotification, + onDidReceiveBackgroundNotificationResponse: + _onDidReceiveBackgroundNotificationResponse, + ); + developer.log( + 'Notification plugin initialized', + name: 'notification-service.on.ctor', + ); /// Wip out all existing notifications if (!kIsWeb) { await localNotifications.cancelAll(); - developer.log('Previous existing notifications cleaned', - name: 'notification-service.on.ctor'); + developer.log( + 'Previous existing notifications cleaned', + name: 'notification-service.on.ctor', + ); } + return notificationService; } static _onDidReceiveBackgroundNotificationResponse( - NotificationResponse notificationResponse) { - // WIP - } + NotificationResponse notificationResponse, + // ignore: avoid-unused-parameters + ) {} // ignore: no-empty-block static _onDidReceiveLocalNotification( - NotificationResponse notificationResponse) { - // WIP - } + NotificationResponse notificationResponse, + // ignore: avoid-unused-parameters + ) {} // ignore: no-empty-block Future _requestPermissions() async { if (Platform.isAndroid) { @@ -71,32 +81,44 @@ class NotificationService { return await androidImplementation?.requestPermission() ?? false; } + return false; } Future computeNotifications( - List chabanBridgeForecasts, - NotificationSate notificationSate, - BuildContext context) async { + List chabanBridgeForecasts, + NotificationState notificationSate, + BuildContext context, + ) async { tz.initializeTimeZones(); int index = 0; - await localNotifications.cancelAll(); + localNotifications.cancelAll().then((value) => null); List weekSeparatedChabanBridgeForecast = []; for (final chabanBridgeForecast in chabanBridgeForecasts) { if (notificationSate.openingNotificationEnabled) { index += 1; await _createOpeningScheduledNotifications( - index, chabanBridgeForecast, context); + index, + chabanBridgeForecast, + context, + ); } if (notificationSate.closingNotificationEnabled) { index += 1; await _createClosingScheduledNotifications( - index, chabanBridgeForecast, context); + index, + chabanBridgeForecast, + context, + ); } if (notificationSate.timeNotificationEnabled) { index += 1; - await _createTimeScheduledNotifications(index, chabanBridgeForecast, - context, notificationSate.timeNotificationValue); + await _createTimeScheduledNotifications( + index, + chabanBridgeForecast, + context, + notificationSate.timeNotificationValue, + ); } if (notificationSate.dayNotificationEnabled) { var last = chabanBridgeForecast.circulationClosingDate @@ -131,44 +153,51 @@ class NotificationService { } Future _createOpeningScheduledNotifications( - int index, - AbstractChabanBridgeForecast chabanBridgeForecast, - BuildContext context) async { + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, + BuildContext context, + ) async { final notificationScheduleTime = chabanBridgeForecast.circulationReOpeningDate; NotificationDetails notificationDetails = _notificationDetails( - Const.notificationOpeningChannelId, - AppLocalizations.of(context)!.notificationOpeningChannelName); + Const.notificationOpeningChannelId, + AppLocalizations.of(context)!.notificationOpeningChannelName, + ); await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationOpeningTitle, - AppLocalizations.of(context)!.notificationOpeningMessage, - notificationScheduleTime, - notificationDetails); + index, + AppLocalizations.of(context)!.notificationOpeningTitle, + AppLocalizations.of(context)!.notificationOpeningMessage, + notificationScheduleTime, + notificationDetails, + ); } Future _createClosingScheduledNotifications( - int index, - AbstractChabanBridgeForecast chabanBridgeForecast, - BuildContext context) async { + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, + BuildContext context, + ) async { final notificationScheduleTime = chabanBridgeForecast.circulationClosingDate; NotificationDetails notificationDetails = _notificationDetails( - Const.notificationClosingChannelId, - AppLocalizations.of(context)!.notificationClosingChannelName); + Const.notificationClosingChannelId, + AppLocalizations.of(context)!.notificationClosingChannelName, + ); await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationClosingTitle, - chabanBridgeForecast.getNotificationClosingMessage(context), - notificationScheduleTime, - notificationDetails); + index, + AppLocalizations.of(context)!.notificationClosingTitle, + chabanBridgeForecast.getNotificationClosingMessage(context), + notificationScheduleTime, + notificationDetails, + ); } Future _createTimeScheduledNotifications( - int index, - AbstractChabanBridgeForecast chabanBridgeForecast, - BuildContext context, - TimeOfDay value) async { + int index, + AbstractChabanBridgeForecast chabanBridgeForecast, + BuildContext context, + TimeOfDay value, + ) async { final notificationScheduleTime = chabanBridgeForecast.circulationClosingDate .subtract( const Duration( @@ -177,89 +206,113 @@ class NotificationService { ) .copyWith(hour: value.hour, minute: value.minute); NotificationDetails notificationDetails = _notificationDetails( - Const.notificationTimeChannelId, - AppLocalizations.of(context)!.notificationTimeChannelName); + Const.notificationTimeChannelId, + AppLocalizations.of(context)!.notificationTimeChannelName, + ); await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationTimeTitle, - chabanBridgeForecast.getNotificationTimeMessage(context), - notificationScheduleTime, - notificationDetails); + index, + AppLocalizations.of(context)!.notificationTimeTitle, + chabanBridgeForecast.getNotificationTimeMessage(context), + notificationScheduleTime, + notificationDetails, + ); } Future _createDurationScheduledNotifications( - int index, - AbstractChabanBridgeForecast chabanBridgeForecast, - BuildContext context, - Duration durationValue, - String durationString) async { + 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); + Const.notificationDurationChannelId, + AppLocalizations.of(context)!.notificationDurationChannelName, + ); await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationDurationTitle, - chabanBridgeForecast.getNotificationDurationMessage( - context, durationString), - notificationScheduleTime, - notificationDetails); + index, + AppLocalizations.of(context)!.notificationDurationTitle, + chabanBridgeForecast.getNotificationDurationMessage( + context, + durationString, + ), + notificationScheduleTime, + notificationDetails, + ); } - Future _createDayScheduledNotifications(int index, int closingCount, - DateTime day, TimeOfDay timeOfDay, BuildContext context) async { + 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); + Const.notificationDayChannelId, + AppLocalizations.of(context)!.notificationDayChannelName, + ); await _scheduleNotification( - index, - AppLocalizations.of(context)!.notificationDayTitle, - AppLocalizations.of(context)!.notificationDayMessage(closingCount), - notificationScheduleTime, - notificationDetails); + index, + AppLocalizations.of(context)!.notificationDayTitle, + AppLocalizations.of(context)!.notificationDayMessage(closingCount), + notificationScheduleTime, + notificationDetails, + ); } NotificationDetails _notificationDetails( - String notificationChannelId, String notificationChannelName) { + String notificationChannelId, + String notificationChannelName, + ) { final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - notificationChannelId, notificationChannelName, - importance: Importance.high, - priority: Priority.max, - ongoing: true, - fullScreenIntent: true, - styleInformation: const BigTextStyleInformation(''), - ticker: Const.androidTicket); - return NotificationDetails(android: androidNotificationDetails); + notificationChannelId, + notificationChannelName, + importance: Importance.high, + priority: Priority.max, + ongoing: true, + fullScreenIntent: true, + styleInformation: const BigTextStyleInformation(''), + ticker: Const.androidTicket, + ); + + return NotificationDetails( + android: androidNotificationDetails, + ); } Future _scheduleNotification( - int notificationId, - String notificationTitle, - String notificationMessage, - DateTime notificationScheduleTime, - NotificationDetails notificationDetails) async { + int notificationId, + String notificationTitle, + String notificationMessage, + DateTime notificationScheduleTime, + NotificationDetails notificationDetails, + ) async { /// Prevent from creating notification in the past if (notificationScheduleTime.isAfter(DateTime.now()) && await _requestPermissions()) { developer.log( - 'Creating a notification on channel ${notificationDetails.android!.channelId} with ID $notificationId scheduled at $notificationScheduleTime', - name: 'notification-service.on.scheduleNotification'); + 'Creating a notification on channel ${notificationDetails.android!.channelId} with ID $notificationId scheduled at $notificationScheduleTime', + name: 'notification-service.on.scheduleNotification', + ); await localNotifications.zonedSchedule( - notificationId, - notificationTitle, - notificationMessage, - tz.TZDateTime.from( - notificationScheduleTime, - tz.local, - ), - notificationDetails, - androidAllowWhileIdle: true, - uiLocalNotificationDateInterpretation: - UILocalNotificationDateInterpretation.absoluteTime); + notificationId, + notificationTitle, + notificationMessage, + tz.TZDateTime.from( + notificationScheduleTime, + tz.local, + ), + notificationDetails, + androidAllowWhileIdle: true, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + ); } } } diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 28c8bcde..8e5e0214 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -16,43 +16,54 @@ class StorageService { Future saveString(String key, String message) async { developer.log('{$key: $message}', name: 'storage-service.on.saveString'); + return await sharedPreferences.setString(key, message); } Future saveBool(String key, bool value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveBool'); + return await sharedPreferences.setBool(key, value); } Future saveDuration(String key, Duration value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveDuration'); + 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( - key, '${value.hour.toString()}:${value.minute.toString()}'); + key, + '${value.hour.toString()}:${value.minute.toString()}', + ); } Future saveDay(String key, Day value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveDay'); + return await sharedPreferences.setString(key, value.name); } Future saveTheme(String key, ThemeStateStatus value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveTheme'); + return await sharedPreferences.setString(key, value.name); } Future saveTimeSlots(String key, List timeSlots) async { - developer.log('{$key: $timeSlots}', - name: 'storage-service.on.saveTimeSlots'); + developer.log( + '{$key: $timeSlots}', + name: 'storage-service.on.saveTimeSlots', + ); return await sharedPreferences.setString(key, jsonEncode(timeSlots)); } @@ -60,12 +71,14 @@ class StorageService { String? readString(String key) { final value = sharedPreferences.getString(key); developer.log('{$key: $value}', name: 'storage-service.on.readString'); + return value; } bool? readBool(String key) { final value = sharedPreferences.getBool(key); developer.log('{$key: $value}', name: 'storage-service.on.readBool'); + return value; } @@ -77,6 +90,7 @@ class StorageService { final value = Duration(minutes: int.parse(sharedPreferences.getString(key)!)); developer.log('{$key: $value}', name: 'storage-service.on.readDuration'); + return value; } } @@ -87,8 +101,10 @@ class StorageService { return null; } else { final value = TimeOfDay.fromDateTime( - DateFormat('hh:mm').parse(sharedPreferences.getString(key)!)); + DateFormat('hh:mm').parse(sharedPreferences.getString(key)!), + ); developer.log('{$key: $value}', name: 'storage-service.on.readTimeOfDay'); + return value; } } @@ -100,6 +116,7 @@ class StorageService { } else { final value = EnumToString.fromString(Day.values, stringValue); developer.log('{$key: $value}', name: 'storage-service.on.readDay'); + return value; } } @@ -112,6 +129,7 @@ class StorageService { final value = EnumToString.fromString(ThemeStateStatus.values, stringValue); developer.log('{$key: $value}', name: 'storage-service.on.readTheme'); + return value; } } @@ -124,8 +142,11 @@ class StorageService { final list = json.decode(stringValue); final List timeSlotList = list.map((item) => TimeSlot.fromJSON(item)).toList(); - developer.log('{$key: $timeSlotList', - name: 'storage-service.on.readTimeSlots'); + developer.log( + '{$key: $timeSlotList', + name: 'storage-service.on.readTimeSlots', + ); + return timeSlotList; } } diff --git a/lib/widgets/ad_banner_widget.dart b/lib/widgets/ad_banner_widget.dart index b6253ac9..3822e6da 100644 --- a/lib/widgets/ad_banner_widget.dart +++ b/lib/widgets/ad_banner_widget.dart @@ -31,8 +31,10 @@ class _AdBannerWidgetState extends State { ); }, onAdFailedToLoad: (ad, error) { - developer.log('Enable to load the ad : ${error.message}', - name: 'banner-widget'); + developer.log( + 'Enable to load the ad : ${error.message}', + name: 'banner-widget', + ); _ad?.dispose(); }, ), diff --git a/lib/widgets/floating_actions/floating_action_item.dart b/lib/widgets/floating_actions/floating_actions_item.dart similarity index 68% rename from lib/widgets/floating_actions/floating_action_item.dart rename to lib/widgets/floating_actions/floating_actions_item.dart index d1b2cddf..b8dba8b5 100644 --- a/lib/widgets/floating_actions/floating_action_item.dart +++ b/lib/widgets/floating_actions/floating_actions_item.dart @@ -4,15 +4,15 @@ class FloatingActionsItem extends StatelessWidget { final bool isSpaced; final List content; final bool isRightHanded; - final Function()? onPressed; + final Function() onPressed; - const FloatingActionsItem( - {Key? key, - required this.isRightHanded, - this.onPressed, - required this.content, - required this.isSpaced}) - : super(key: key); + const FloatingActionsItem({ + Key? key, + required this.isRightHanded, + required this.onPressed, + required this.content, + required this.isSpaced, + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/widgets/floating_actions/floating_actions_widget.dart b/lib/widgets/floating_actions/floating_actions_widget.dart index cbb84889..b24d2271 100644 --- a/lib/widgets/floating_actions/floating_actions_widget.dart +++ b/lib/widgets/floating_actions/floating_actions_widget.dart @@ -4,7 +4,7 @@ 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'; -import 'package:chabo/widgets/floating_actions/floating_action_item.dart'; +import 'package:chabo/widgets/floating_actions/floating_actions_item.dart'; import 'package:chabo/widgets/theme_switcher_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -95,7 +95,7 @@ class _FloatingActionsWidgetState extends State ), ), builder: (context) { - return const TheSwitcherWidget(); + return const ThemeSwitcherWidget(); }, ); context @@ -104,7 +104,9 @@ class _FloatingActionsWidgetState extends State }, content: [ Text(AppLocalizations.of(context)!.themeSetting), - const Icon(Icons.format_paint_rounded) + const Icon( + Icons.format_paint_rounded, + ), ], isRightHanded: state.isRightHanded, isSpaced: true, @@ -116,8 +118,12 @@ class _FloatingActionsWidgetState extends State pageBuilder: (context, animation1, animation2) => const NotificationScreen(), - transitionsBuilder: (context, animation, - secondaryAnimation, child) { + transitionsBuilder: ( + context, + animation, + secondaryAnimation, + child, + ) { const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; @@ -217,11 +223,12 @@ class _FloatingActionsWidgetState extends State end: const Offset(0.0, 0.0), ).animate(animation), child: FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeIn, - ), - child: child), + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: child, + ), ); }, child: state.isMenuOpen diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index 2834b549..10a771e5 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -18,18 +18,20 @@ class ForecastListItemWidget extends StatelessWidget { final bool isCurrent; final int index; - const ForecastListItemWidget( - {Key? key, - required this.chabanBridgeForecast, - required this.index, - required this.hasPassed, - required this.isCurrent, - this.onTap, - required this.timeSlots}) - : super(key: key); + const ForecastListItemWidget({ + Key? key, + required this.chabanBridgeForecast, + required this.index, + required this.hasPassed, + required this.isCurrent, + this.onTap, + required this.timeSlots, + }) : super(key: key); @override Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return Padding( padding: const EdgeInsets.all(5), child: Stack( @@ -69,8 +71,9 @@ class ForecastListItemWidget extends StatelessWidget { Tween(begin: 0.0, end: 1.0).animate(a1), child: BackdropFilter( filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), child: ChabanBridgeForecastInformationDialog( chabanBridgeForecast: chabanBridgeForecast, ), @@ -82,7 +85,7 @@ class ForecastListItemWidget extends StatelessWidget { transitionDuration: const Duration( milliseconds: 300, ), - ) + ), }, child: SizedBox( height: 65, @@ -120,14 +123,17 @@ class ForecastListItemWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - const Icon(Icons.block_rounded, - size: 18, color: Colors.red), + const Icon( + Icons.block_rounded, + size: 18, + color: Colors.red, + ), Text( MaterialLocalizations.of(context) .formatMediumDate( chabanBridgeForecast.circulationClosingDate, ), - style: Theme.of(context).textTheme.bodySmall, + style: textTheme.bodySmall, ), ], ), @@ -135,7 +141,7 @@ class ForecastListItemWidget extends StatelessWidget { chabanBridgeForecast.circulationClosingDateString( context, ), - style: Theme.of(context).textTheme.headlineSmall, + style: textTheme.headlineSmall, ), ], ), @@ -168,14 +174,17 @@ class ForecastListItemWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - const Icon(Icons.check_circle, - size: 18, color: Colors.green), + const Icon( + Icons.check_circle, + size: 18, + color: Colors.green, + ), Text( MaterialLocalizations.of(context) .formatMediumDate( chabanBridgeForecast.circulationReOpeningDate, ), - style: Theme.of(context).textTheme.bodySmall, + style: textTheme.bodySmall, ), ], ), @@ -183,7 +192,7 @@ class ForecastListItemWidget extends StatelessWidget { chabanBridgeForecast.circulationReOpeningDateString( context, ), - style: Theme.of(context).textTheme.headlineSmall, + style: textTheme.headlineSmall, ), ], ), @@ -195,9 +204,11 @@ class ForecastListItemWidget extends StatelessWidget { decoration: BoxDecoration( borderRadius: const BorderRadius.only( topRight: Radius.circular( - CustomProperties.borderRadius), + CustomProperties.borderRadius, + ), bottomRight: Radius.circular( - CustomProperties.borderRadius), + CustomProperties.borderRadius, + ), ), color: Theme.of(context).colorScheme.warningColor, ), @@ -219,8 +230,9 @@ class ForecastListItemWidget extends StatelessWidget { child: ClipRect( child: BackdropFilter( filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), child: Center( child: Text( AppLocalizations.of(context)!.passedClosure, diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index 64021714..f8effb30 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -28,6 +28,7 @@ class _ForecastListWidgetState extends State { ScrollStatusChanged(), ); } + return true; }, child: BlocBuilder( @@ -40,9 +41,11 @@ class _ForecastListWidgetState extends State { itemBuilder: (BuildContext context, int index) { forecastState.chabanBridgeForecasts[index] .checkSlotInterference(timeSlotState.timeSlots); + return ForecastListItemWidget( key: GlobalObjectKey( - forecastState.chabanBridgeForecasts[index].hashCode), + forecastState.chabanBridgeForecasts[index].hashCode, + ), isCurrent: forecastState.chabanBridgeForecasts[index] == forecastState.currentChabanBridgeForecast, hasPassed: forecastState @@ -73,12 +76,13 @@ class _ForecastListWidgetState extends State { if (((index % 10 == 0 || index == forecastState.chabanBridgeForecasts.indexOf( - forecastState - .currentChabanBridgeForecast!)) && + forecastState.currentChabanBridgeForecast!, + )) && index != 0) && !kIsWeb) { return const AdBannerWidget(); } + return const SizedBox.shrink(); }, ); diff --git a/lib/widgets/forecast/status_widget.dart b/lib/widgets/forecast/status_widget.dart index b59e3ccc..ed56c234 100644 --- a/lib/widgets/forecast/status_widget.dart +++ b/lib/widgets/forecast/status_widget.dart @@ -3,7 +3,7 @@ import 'dart:async'; 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/custom_widget_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; @@ -60,7 +60,8 @@ class StatusWidgetState extends CustomWidgetState { ChabanBridgeStatusLifecycle.empty ? Padding( padding: EdgeInsets.symmetric( - vertical: MediaQuery.of(context).size.height / 5), + vertical: MediaQuery.of(context).size.height / 5, + ), child: CustomCircularProgressIndicator( message: AppLocalizations.of(context)!.statusLoadMessage, ), @@ -90,7 +91,9 @@ class StatusWidgetState extends CustomWidgetState { transitionBuilder: (Widget child, Animation animation) { return FadeTransition( - opacity: animation, child: child); + opacity: animation, + child: child, + ); }, child: Text( state.mainMessageStatus, @@ -127,7 +130,9 @@ class StatusWidgetState extends CustomWidgetState { state.completionPercentage != -1 ? Padding( padding: const EdgeInsets.symmetric( - horizontal: 20.0, vertical: 5), + horizontal: 20.0, + vertical: 5, + ), child: SizedBox( height: 10, child: ClipRRect( @@ -175,8 +180,8 @@ class StatusWidgetState extends CustomWidgetState { child: ForecastListItemWidget( onTap: () => BlocProvider.of( - context) - .add( + context, + ).add( GoTo( goTo: state.currentTarget, ), diff --git a/lib/widgets/notification_tile_widget.dart b/lib/widgets/notification_tile_widget.dart deleted file mode 100644 index c1904f0c..00000000 --- a/lib/widgets/notification_tile_widget.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:chabo/screens/notification_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class NotificationTileWidget extends StatelessWidget { - const NotificationTileWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListTile( - key: const ValueKey('notificationButton'), - title: Text( - AppLocalizations.of(context)!.notifications, - style: const TextStyle(fontSize: 25), - ), - subtitle: Text( - AppLocalizations.of(context)!.notificationsSubtitle, - style: const TextStyle( - fontSize: 15, - ), - ), - selected: true, - leading: const Icon( - Icons.notifications_active_outlined, - size: 30, - ), - onTap: () async => Navigator.of(context).push( - 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, - ); - }, - ), - ), - ); - } -} diff --git a/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart b/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart index 18060336..4bd6ede0 100644 --- a/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart +++ b/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart @@ -5,9 +5,12 @@ class CustomProgressBarIndicator extends StatelessWidget { final double current; final Color color; - const CustomProgressBarIndicator( - {Key? key, required this.max, required this.current, required this.color}) - : super(key: key); + const CustomProgressBarIndicator({ + Key? key, + required this.max, + required this.current, + required this.color, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -15,6 +18,7 @@ class CustomProgressBarIndicator extends StatelessWidget { builder: (_, boxConstraints) { var x = boxConstraints.maxWidth; var percent = (current / max) * x; + return Stack( alignment: Alignment.centerLeft, children: [ diff --git a/lib/widgets/theme_switcher_widget.dart b/lib/widgets/theme_switcher_widget.dart index f904b1bf..c6aee3b1 100644 --- a/lib/widgets/theme_switcher_widget.dart +++ b/lib/widgets/theme_switcher_widget.dart @@ -5,11 +5,13 @@ 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); +class ThemeSwitcherWidget extends StatelessWidget { + const ThemeSwitcherWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return BlocBuilder( builder: (context, state) { return Padding( @@ -31,18 +33,18 @@ class TheSwitcherWidget extends StatelessWidget { values: const [ ThemeStateStatus.light, ThemeStateStatus.dark, - ThemeStateStatus.system + ThemeStateStatus.system, ], - indicatorColor: Theme.of(context).colorScheme.tertiary, - innerColor: Theme.of(context).colorScheme.primaryContainer, - borderColor: Theme.of(context).colorScheme.primary, + indicatorColor: colorScheme.tertiary, + innerColor: colorScheme.primaryContainer, + borderColor: 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, + ? colorScheme.onTertiary + : colorScheme.onPrimaryContainer, ); }, onChanged: (value) => BlocProvider.of(context).add( @@ -65,11 +67,12 @@ class TheSwitcherWidget extends StatelessWidget { end: const Offset(0.0, 0.0), ).animate(animation), child: FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeIn, - ), - child: child), + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), + child: child, + ), ); }, child: Text( diff --git a/lib/widgets/time_slot_widget.dart b/lib/widgets/time_slot_widget.dart index 288bda1d..dbaf5a53 100644 --- a/lib/widgets/time_slot_widget.dart +++ b/lib/widgets/time_slot_widget.dart @@ -18,27 +18,32 @@ class TimeSlotWidget extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton( - onPressed: () async { - final day = await showDialog( + onPressed: () { + showDialog( context: context, builder: ( BuildContext context, ) { return BackdropFilter( filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY), + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), child: TimeSlotDialog( index: index, ), ); }, + ).then( + (value) => { + if (value != null) + { + BlocProvider.of(context).add( + DayNotificationValueEvent(day: value), + ), + }, + }, ); - if (day != null) { - BlocProvider.of(context).add( - DayNotificationValueEvent(day: day), - ); - } }, child: Padding( padding: const EdgeInsets.all(8.0), diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index 987dc6a7..06420d84 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=C:\src\flutter -FLUTTER_APPLICATION_PATH=C:\Users\Valentin\Documents\Projets\chabo +FLUTTER_ROOT=D:\src\Flutter +FLUTTER_APPLICATION_PATH=D:\Code\chabo COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=1.1.0 -FLUTTER_BUILD_NUMBER=1.1.0 +FLUTTER_BUILD_NAME=1.3.2 +FLUTTER_BUILD_NUMBER=1.3.2 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 fb71f946..d29ae3eb 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=C:\src\flutter" -export "FLUTTER_APPLICATION_PATH=C:\Users\Valentin\Documents\Projets\chabo" +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=1.1.0" -export "FLUTTER_BUILD_NUMBER=1.1.0" +export "FLUTTER_BUILD_NAME=1.3.2" +export "FLUTTER_BUILD_NUMBER=1.3.2" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/pubspec.lock b/pubspec.lock index d07d37c3..266df46d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" + url: "https://pub.dev" + source: hosted + version: "59.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 + url: "https://pub.dev" + source: hosted + version: "5.11.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + url: "https://pub.dev" + source: hosted + version: "0.11.2" animated_toggle_switch: dependency: "direct main" description: @@ -9,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.2" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a" + url: "https://pub.dev" + source: hosted + version: "2.0.1" archive: dependency: transitive description: @@ -113,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + csslib: + dependency: transitive + description: + name: csslib + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" + source: hosted + version: "0.17.2" cupertino_icons: dependency: "direct main" description: @@ -121,6 +161,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + dart_code_metrics: + dependency: "direct dev" + description: + name: dart_code_metrics + sha256: "162c81dbd0a2ba182f38ca615335f3e8878f212ec7beea83d6bfad4e99eb541a" + url: "https://pub.dev" + source: hosted + version: "5.7.3" + dart_code_metrics_presets: + dependency: transitive + description: + name: dart_code_metrics_presets + sha256: "22e27f98e8c7d8b11cca43d2656a822935280747050ae65e8cd03c52d09c0d1c" + url: "https://pub.dev" + source: hosted + version: "1.7.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + url: "https://pub.dev" + source: hosted + version: "2.3.1" dbus: dependency: transitive description: @@ -253,6 +317,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.4.0" + glob: + dependency: transitive + description: + name: glob + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" google_fonts: dependency: "direct main" description: @@ -269,6 +341,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + html: + dependency: transitive + description: + name: html + sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + url: "https://pub.dev" + source: hosted + version: "0.15.3" http: dependency: "direct main" description: @@ -365,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_info_plus: dependency: "direct main" description: @@ -485,6 +573,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pub_updater: + dependency: transitive + description: + name: pub_updater + sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" + url: "https://pub.dev" + source: hosted + version: "0.3.0" shared_preferences: dependency: "direct main" description: @@ -682,6 +786,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -698,6 +810,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" + watcher: + dependency: transitive + description: + name: watcher + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" + source: hosted + version: "1.0.2" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7e63c0a3..ba015aec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 flutter_launcher_icons: ^0.13.0 + dart_code_metrics: ^5.7.3 flutter: generate: true From 47928e23e28490119eaca1e5ca548f22648ae5b8 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 17:18:19 +0200 Subject: [PATCH 21/34] fix(format): fix formatting uses --- .../flutter.analyze-test.action.yaml | 2 + .github/workflows/flutter.build.action.yaml | 196 +++++++++--------- .../abstract_chaban_bridge_forecast.dart | 10 +- 3 files changed, 106 insertions(+), 102 deletions(-) diff --git a/.github/workflows/flutter.analyze-test.action.yaml b/.github/workflows/flutter.analyze-test.action.yaml index 3796c752..24f27538 100644 --- a/.github/workflows/flutter.analyze-test.action.yaml +++ b/.github/workflows/flutter.analyze-test.action.yaml @@ -48,6 +48,8 @@ jobs: uses: subosito/flutter-action@v2.10.0 with: flutter-version: ${{ inputs.flutter_version }} + - name: 'Get dependencies' + run: flutter pub get - name: '[Global] Code metrics' run: flutter pub run dart_code_metrics:metrics analyze --fatal-style --fatal-warnings --fatal-performance --reporter=github lib - name: '[Unused files] Code metrics' diff --git a/.github/workflows/flutter.build.action.yaml b/.github/workflows/flutter.build.action.yaml index 7bbda5a6..e1f4be2f 100644 --- a/.github/workflows/flutter.build.action.yaml +++ b/.github/workflows/flutter.build.action.yaml @@ -1,99 +1,99 @@ -name: Flutter - Build - -on: - workflow_call: - inputs: - flutter_version: - description: 'The Flutter used (ex: 2.5.1)' - required: true - type: string - android_output: - description: 'Android build file type output (apk or abb)' - required: true - type: string - secrets: - passphrase: - description: 'The passphrase to decrypt the configuration' - required: true - - -jobs: - build_android: - name: 'Android (${{ inputs.android_output }})' - runs-on: ubuntu-latest - steps: - - name: 'Checkout source code' - uses: actions/checkout@v3.5.2 - with: - fetch-depth: 0 - - 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: 'Set up JAVA' - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11.x' - - name: 'Setup Flutter' - uses: subosito/flutter-action@v2.10.0 - with: - flutter-version: ${{ inputs.flutter_version }} - - name: 'Build Android APK' - if: ${{ inputs.android_output == 'apk' }} - # Build APK version of the app - run: flutter build apk --split-per-abi - - name: 'Save APK' - if: ${{ inputs.android_output == 'apk' }} - uses: actions/upload-artifact@v3 - with: - name: 'apk-build' - path: build/app/outputs/apk/release/app-arm64-v8a-release.apk - - name: 'Generate build number' - if: ${{ inputs.android_output == 'aab' }} - # Build App Bundle version of the app - run: | - BUILD_NUMBER=$(git rev-list --all --count) - echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV - echo "This build is tagged as $BUILD_NUMBER on $GITHUB_REF" - - name: 'Build Android App Bundle' - if: ${{ inputs.android_output == 'aab' }} - run: flutter build appbundle --dart-define=FLAVOR=prod --build-number="$BUILD_NUMBER" - env: - BUILD_NUMBER: ${{ env.BUILD_NUMBER }} - - name: 'Save AAB' - if: ${{ inputs.android_output == 'aab' }} - uses: actions/upload-artifact@v3 - with: - name: 'aab-build' - path: build/app/outputs/bundle/release/app-release.aab - build_ios: - name: 'iOS' - 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: '🥺' - run: echo 'WIP' - build_web: - name: 'WEB' - 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: '🥺' +name: Flutter - Build + +on: + workflow_call: + inputs: + flutter_version: + description: 'The Flutter used (ex: 2.5.1)' + required: true + type: string + android_output: + description: 'Android build file type output (apk or abb)' + required: true + type: string + secrets: + passphrase: + description: 'The passphrase to decrypt the configuration' + required: true + + +jobs: + build_android: + name: 'Android (${{ inputs.android_output }})' + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v3.5.2 + with: + fetch-depth: 0 + - 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: 'Set up JAVA' + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11.x' + - name: 'Setup Flutter' + uses: subosito/flutter-action@v2.10.0 + with: + flutter-version: ${{ inputs.flutter_version }} + - name: 'Build Android APK' + if: ${{ inputs.android_output == 'apk' }} + # Build APK version of the app + run: flutter build apk --split-per-abi + - name: 'Save APK' + if: ${{ inputs.android_output == 'apk' }} + uses: actions/upload-artifact@v3 + with: + name: 'apk-build' + path: build/app/outputs/apk/release/app-arm64-v8a-release.apk + - name: 'Generate build number' + if: ${{ inputs.android_output == 'aab' }} + # Build App Bundle version of the app + run: | + BUILD_NUMBER=$(git rev-list --all --count) + echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV + echo "This build is tagged as $BUILD_NUMBER on $GITHUB_REF" + - name: 'Build Android App Bundle' + if: ${{ inputs.android_output == 'aab' }} + run: flutter build appbundle --build-number="$BUILD_NUMBER" + env: + BUILD_NUMBER: ${{ env.BUILD_NUMBER }} + - name: 'Save AAB' + if: ${{ inputs.android_output == 'aab' }} + uses: actions/upload-artifact@v3 + with: + name: 'aab-build' + path: build/app/outputs/bundle/release/app-release.aab + build_ios: + name: 'iOS' + 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: '🥺' + run: echo 'WIP' + build_web: + name: 'WEB' + 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: '🥺' run: echo 'WIP' \ 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 5f5f431b..83716ebc 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -8,7 +8,7 @@ import 'package:intl/intl.dart'; abstract class AbstractChabanBridgeForecast extends Equatable { final bool totalClosing; - late bool isDuringTwoDays = false; + late final bool isDuringTwoDays; final ChabanBridgeForecastClosingReason closingReason; late final Duration closedDuration; late final DateTime _circulationClosingDate; @@ -26,16 +26,18 @@ abstract class AbstractChabanBridgeForecast extends Equatable { _circulationClosingDate = circulationClosingDate; var tmpCirculationReOpeningDate = circulationReOpeningDate.toLocal(); - var tmpDuration = - tmpCirculationReOpeningDate.difference(_circulationClosingDate.toLocal()); + var tmpDuration = tmpCirculationReOpeningDate + .difference(_circulationClosingDate.toLocal()); + var tmpIsDuringTwoDays = false; if (tmpDuration.isNegative) { - isDuringTwoDays = true; + tmpIsDuringTwoDays = true; tmpCirculationReOpeningDate = tmpCirculationReOpeningDate.add(const Duration(days: 1)); tmpDuration = tmpCirculationReOpeningDate.difference(_circulationClosingDate); } + isDuringTwoDays = tmpIsDuringTwoDays; _circulationReOpeningDate = tmpCirculationReOpeningDate; closedDuration = tmpDuration; } From 577bb9baa78e568592fc5286de43eec22f9b8996 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 20:17:58 +0200 Subject: [PATCH 22/34] feat(time-slot): compute notification according to the selected time slots if enabled --- lib/bloc/notification/notification_bloc.dart | 68 ++++++ lib/bloc/notification/notification_event.dart | 13 ++ lib/bloc/notification/notification_state.dart | 9 + lib/bloc/time_slot/time_slot_bloc.dart | 85 -------- lib/bloc/time_slot/time_slot_event.dart | 21 -- lib/bloc/time_slot/time_slot_state.dart | 25 --- lib/chabo.dart | 10 - lib/dialogs/time_slot_dialog.dart | 24 +-- lib/l10n/app_en.arb | 5 +- lib/l10n/app_es.arb | 5 +- lib/l10n/app_fr.arb | 5 +- .../abstract_chaban_bridge_forecast.dart | 46 ++++- .../chaban_bridge_forecast_screen.dart | 67 ------ lib/screens/notification_screen.dart | 194 +++++++++++++----- lib/service/notification_service.dart | 36 +++- .../forecast/forecast_list_widget.dart | 8 +- 16 files changed, 317 insertions(+), 304 deletions(-) delete mode 100644 lib/bloc/time_slot/time_slot_bloc.dart delete mode 100644 lib/bloc/time_slot/time_slot_event.dart delete mode 100644 lib/bloc/time_slot/time_slot_state.dart diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 45138fca..5c35f70e 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -1,12 +1,15 @@ +import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; import 'package:chabo/const.dart'; import 'package:chabo/models/enums/day.dart'; +import 'package:chabo/models/time_slot.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 { @@ -29,37 +32,58 @@ class NotificationBloc extends Bloc { Const.notificationOpeningEnabledDefaultValue, closingNotificationEnabled: Const.notificationClosingEnabledDefaultValue, + timeSlotsEnabledForNotifications: + Const.notificationFavoriteSlotsEnabledDefaultValue, + timeSlotsValue: Const.notificationFavoriteSlotsDefaultValue, ), ) { on( _onOpeningNotificationStateEvent, + transformer: sequential(), ); on( _onClosingNotificationStateEvent, + transformer: sequential(), ); on( _onDayNotificationStateEvent, + transformer: sequential(), ); on( _onDayNotificationValueEvent, + transformer: sequential(), ); on( _onDayNotificationTimeValueEvent, + transformer: sequential(), ); on( _onTimeNotificationStateEvent, + transformer: sequential(), ); on( _onTimeNotificationValueEvent, + transformer: sequential(), ); on( _onDurationNotificationStateEvent, + transformer: sequential(), ); on( _onDurationNotificationValueEvent, + transformer: sequential(), + ); + on( + _onEnabledTimeSlotEvent, + transformer: sequential(), + ); + on( + _onTimeSlotsEventValue, + transformer: sequential(), ); on( _onAppEvent, + transformer: sequential(), ); } @@ -186,6 +210,40 @@ class NotificationBloc extends Bloc { ); } + Future _onEnabledTimeSlotEvent( + EnabledTimeSlotEvent event, + Emitter emit, + ) async { + await storageService.saveBool( + Const.notificationFavoriteSlotsEnabledKey, + event.enabled, + ); + HapticFeedback.lightImpact(); + + emit(state.copyWith( + timeSlotsEnabledForNotifications: event.enabled, + )); + } + + Future _onTimeSlotsEventValue( + ValueTimeSlotEvent event, + Emitter emit, + ) async { + final timeSlots = List.from(state.timeSlotsValue); + timeSlots[event.index] = event.timeSlot; + await storageService.saveTimeSlots( + Const.notificationFavoriteSlotsValueKey, + timeSlots, + ); + HapticFeedback.lightImpact(); + + emit( + state.copyWith( + timeSlotsValue: timeSlots, + ), + ); + } + void _onAppEvent( AppEvent event, Emitter emit, @@ -226,6 +284,14 @@ class NotificationBloc extends Bloc { storageService.readBool(Const.notificationClosingEnabledKey) ?? Const.notificationClosingEnabledDefaultValue; + final timeSlots = + storageService.readTimeSlots(Const.notificationFavoriteSlotsValueKey) ?? + Const.notificationFavoriteSlotsDefaultValue; + + final enabledForNotifications = + storageService.readBool(Const.notificationFavoriteSlotsEnabledKey) ?? + Const.notificationFavoriteSlotsEnabledDefaultValue; + emit( state.copyWith( durationNotificationEnabled: durationNotificationEnabled, @@ -237,6 +303,8 @@ class NotificationBloc extends Bloc { dayNotificationTimeValue: dayNotificationTimeValue, openingNotificationEnabled: openingNotificationEnabled, closingNotificationEnabled: closingNotificationEnabled, + timeSlotsValue: timeSlots, + timeSlotsEnabledForNotifications: enabledForNotifications, ), ); } diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart index 6952ab3a..a1f1a056 100644 --- a/lib/bloc/notification/notification_event.dart +++ b/lib/bloc/notification/notification_event.dart @@ -56,6 +56,19 @@ class TimeNotificationValueEvent extends NotificationEvent { TimeNotificationValueEvent({required this.time}) : super(); } +class EnabledTimeSlotEvent extends NotificationEvent { + final bool enabled; + + EnabledTimeSlotEvent({required this.enabled}) : super(); +} + +class ValueTimeSlotEvent extends NotificationEvent { + final TimeSlot timeSlot; + final int index; + + ValueTimeSlotEvent({required this.timeSlot, required this.index}) : super(); +} + class AppEvent extends NotificationEvent { AppEvent() : super(); } diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index 56eb1676..2bbdbddb 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -10,6 +10,8 @@ class NotificationState { final TimeOfDay dayNotificationTimeValue; final bool openingNotificationEnabled; final bool closingNotificationEnabled; + final bool timeSlotsEnabledForNotifications; + final List timeSlotsValue; NotificationState({ required this.durationNotificationEnabled, @@ -21,6 +23,8 @@ class NotificationState { required this.dayNotificationTimeValue, required this.openingNotificationEnabled, required this.closingNotificationEnabled, + required this.timeSlotsEnabledForNotifications, + required this.timeSlotsValue, }); NotificationState copyWith({ @@ -33,6 +37,8 @@ class NotificationState { TimeOfDay? dayNotificationTimeValue, bool? openingNotificationEnabled, bool? closingNotificationEnabled, + bool? timeSlotsEnabledForNotifications, + List? timeSlotsValue, }) { return NotificationState( durationNotificationEnabled: @@ -52,6 +58,9 @@ class NotificationState { openingNotificationEnabled ?? this.openingNotificationEnabled, closingNotificationEnabled: closingNotificationEnabled ?? this.closingNotificationEnabled, + timeSlotsEnabledForNotifications: timeSlotsEnabledForNotifications ?? + this.timeSlotsEnabledForNotifications, + timeSlotsValue: timeSlotsValue ?? this.timeSlotsValue, ); } } diff --git a/lib/bloc/time_slot/time_slot_bloc.dart b/lib/bloc/time_slot/time_slot_bloc.dart deleted file mode 100644 index 94a4ce06..00000000 --- a/lib/bloc/time_slot/time_slot_bloc.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:async'; - -import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/const.dart'; -import 'package:chabo/models/time_slot.dart'; -import 'package:chabo/service/storage_service.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'time_slot_event.dart'; -part 'time_slot_state.dart'; - -class TimeSlotBloc extends Bloc { - final StorageService storageService; - - TimeSlotBloc({required this.storageService}) - : super(TimeSlotState( - timeSlots: Const.notificationFavoriteSlotsDefaultValue, - enabledForNotifications: - Const.notificationFavoriteSlotsEnabledDefaultValue, - )) { - on( - _onAppEvent, - ); - on( - _onEnabledTimeSlotEvent, - ); - on( - _onTimeSlotsEventValue, - ); - } - - void _onAppEvent( - TimeSlotAppEvent event, - Emitter emit, - ) { - final timeSlots = - storageService.readTimeSlots(Const.notificationFavoriteSlotsValueKey) ?? - Const.notificationFavoriteSlotsDefaultValue; - - final enabledForNotifications = - storageService.readBool(Const.notificationFavoriteSlotsEnabledKey) ?? - Const.notificationFavoriteSlotsEnabledDefaultValue; - emit(state.copyWith( - timeSlots: timeSlots, - enabledForNotifications: enabledForNotifications, - )); - } - - Future _onEnabledTimeSlotEvent( - EnabledTimeSlotEvent event, - Emitter emit, - ) async { - await storageService.saveBool( - Const.notificationFavoriteSlotsEnabledKey, - event.enabled, - ); - HapticFeedback.lightImpact(); - - emit(state.copyWith( - enabledForNotifications: event.enabled, - )); - } - - Future _onTimeSlotsEventValue( - ValueTimeSlotEvent event, - Emitter emit, - ) async { - final timeSlots = List.from(state.timeSlots); - timeSlots[event.index] = event.timeSlot; - await storageService.saveTimeSlots( - Const.notificationFavoriteSlotsValueKey, - timeSlots, - ); - HapticFeedback.lightImpact(); - - emit( - state.copyWith( - timeSlots: timeSlots, - ), - ); - } -} diff --git a/lib/bloc/time_slot/time_slot_event.dart b/lib/bloc/time_slot/time_slot_event.dart deleted file mode 100644 index d658c68a..00000000 --- a/lib/bloc/time_slot/time_slot_event.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of 'time_slot_bloc.dart'; - -@immutable -abstract class TimeSlotEvent extends ChaboEvent {} - -class EnabledTimeSlotEvent extends TimeSlotEvent { - final bool enabled; - - EnabledTimeSlotEvent({required this.enabled}) : super(); -} - -class ValueTimeSlotEvent extends TimeSlotEvent { - final TimeSlot timeSlot; - final int index; - - ValueTimeSlotEvent({required this.timeSlot, required this.index}) : super(); -} - -class TimeSlotAppEvent extends TimeSlotEvent { - TimeSlotAppEvent() : super(); -} diff --git a/lib/bloc/time_slot/time_slot_state.dart b/lib/bloc/time_slot/time_slot_state.dart deleted file mode 100644 index 07e11de4..00000000 --- a/lib/bloc/time_slot/time_slot_state.dart +++ /dev/null @@ -1,25 +0,0 @@ -part of 'time_slot_bloc.dart'; - -class TimeSlotState extends Equatable { - final List timeSlots; - final bool enabledForNotifications; - - const TimeSlotState({ - required this.timeSlots, - required this.enabledForNotifications, - }); - - TimeSlotState copyWith({ - List? timeSlots, - bool? enabledForNotifications, - }) { - return TimeSlotState( - timeSlots: timeSlots ?? this.timeSlots, - enabledForNotifications: - enabledForNotifications ?? this.enabledForNotifications, - ); - } - - @override - List get props => [timeSlots, enabledForNotifications]; -} diff --git a/lib/chabo.dart b/lib/chabo.dart index 95482ef9..ebaee445 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -3,7 +3,6 @@ import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; import 'package:chabo/bloc/notification/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_slot/time_slot_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; @@ -82,15 +81,6 @@ class Chabo extends StatelessWidget { AppEvent(), ), ), - - /// Bloc intended to manage favorite time slots - BlocProvider( - create: (_) => TimeSlotBloc( - storageService: storageService, - )..add( - TimeSlotAppEvent(), - ), - ), ], child: BlocBuilder( builder: (context, state) { diff --git a/lib/dialogs/time_slot_dialog.dart b/lib/dialogs/time_slot_dialog.dart index a4547808..dbfe8c6d 100644 --- a/lib/dialogs/time_slot_dialog.dart +++ b/lib/dialogs/time_slot_dialog.dart @@ -1,4 +1,4 @@ -import 'package:chabo/bloc/time_slot/time_slot_bloc.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,7 +20,7 @@ class TimeSlotDialog extends StatelessWidget { 15, ), ), - content: BlocBuilder( + content: BlocBuilder( builder: (context, state) { return Wrap( alignment: WrapAlignment.center, @@ -37,7 +37,7 @@ class TimeSlotDialog extends StatelessWidget { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state.timeSlots[index].from, + initialTime: state.timeSlotsValue[index].from, builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context), @@ -47,12 +47,12 @@ class TimeSlotDialog extends StatelessWidget { ).then((value) => { if (value != null) { - BlocProvider.of(context).add( + BlocProvider.of(context).add( ValueTimeSlotEvent( timeSlot: TimeSlot( - name: state.timeSlots[index].name, + name: state.timeSlotsValue[index].name, from: value, - to: state.timeSlots[index].to, + to: state.timeSlotsValue[index].to, ), index: index, ), @@ -61,7 +61,7 @@ class TimeSlotDialog extends StatelessWidget { }); }, child: Text( - state.timeSlots[index].from.format(context), + state.timeSlotsValue[index].from.format(context), style: textTheme.titleMedium, ), ), @@ -74,7 +74,7 @@ class TimeSlotDialog extends StatelessWidget { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state.timeSlots[index].to, + initialTime: state.timeSlotsValue[index].to, builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context), @@ -84,11 +84,11 @@ class TimeSlotDialog extends StatelessWidget { ).then((value) => { if (value != null) { - BlocProvider.of(context).add( + BlocProvider.of(context).add( ValueTimeSlotEvent( timeSlot: TimeSlot( - name: state.timeSlots[index].name, - from: state.timeSlots[index].from, + name: state.timeSlotsValue[index].name, + from: state.timeSlotsValue[index].from, to: value, ), index: index, @@ -98,7 +98,7 @@ class TimeSlotDialog extends StatelessWidget { }); }, child: Text( - state.timeSlots[index].to.format(context), + state.timeSlotsValue[index].to.format(context), style: textTheme.titleMedium, ), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a179ac3d..3033c98c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -152,8 +152,6 @@ } }, "day": "Day", - "refreshingNotifications": "Refreshing your notifications", - "refreshingNotificationsDone": "Done !", "notificationDayTitle": "\uD83D\uDD2E Closing scheduled", "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": { @@ -177,5 +175,6 @@ "index": {} } }, - "favoriteSlotsInterferenceWarning": "This schedule interferes with one or more time slots" + "favoriteSlotsInterferenceWarning": "This schedule interferes with one or more time slots", + "favoriteTimeSlotEnabledWarning": "Attention, by activating this parameter you will only receive notifications when an event occurs during one of your time slots" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 28c40926..ad9a7c3b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -152,8 +152,6 @@ } }, "day": "Día", - "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{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": { @@ -177,5 +175,6 @@ "index": {} } }, - "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o más intervalos de tiempo" + "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o más intervalos de tiempo", + "favoriteTimeSlotEnabledWarning": "Atención, al activar este parámetro solo recibirás notificaciones cuando ocurra un evento durante una de tus franjas horarias" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a004f570..8fd70674 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -152,8 +152,6 @@ } }, "day": "Jour", - "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{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": { @@ -177,5 +175,6 @@ "index": {} } }, - "favoriteSlotsInterferenceWarning": "Cette prévision interfère avec un ou plusieurs créneaux" + "favoriteSlotsInterferenceWarning": "Cette prévision interfère avec un ou plusieurs créneaux", + "favoriteTimeSlotEnabledWarning": "Attention, en activant ce paramètres vous allez uniquement recevoir les notifications lorsqu'un évèmenent se produit durant l'un de vos créneaux" } \ 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 83716ebc..05dafa74 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_chaban_bridge_forecast.dart @@ -79,10 +79,10 @@ abstract class AbstractChabanBridgeForecast extends Equatable { .format(circulationReOpeningDate); } - void checkSlotInterference(List timeSlots) { + void computeSlotInterference(List timeSlots) { interferingTimeSlots.clear(); for (var timeSlot in timeSlots) { - if (isOverlappingWithTimeOfDay(timeSlot.to)) { + if (isOverlappingWithPeriod(timeSlot.from, timeSlot.to)) { interferingTimeSlots.add(timeSlot); } } @@ -97,15 +97,41 @@ abstract class AbstractChabanBridgeForecast extends Equatable { dateTime.isBefore(circulationReOpeningDate); } - bool isOverlappingWithTimeOfDay(TimeOfDay timeOfDay) { - final dateTimeConversion = circulationClosingDate.applied(timeOfDay); + bool isOverlappingWithPeriod(TimeOfDay start, TimeOfDay end) { + final startDateTime = circulationClosingDate.applied(start); + final endDateTime = circulationClosingDate.applied(end); - return dateTimeConversion.isAfter( - circulationClosingDate, - ) && - dateTimeConversion.isBefore( - circulationReOpeningDate, - ); + final startIsBeforeClosing = startDateTime.isBefore( + circulationClosingDate, + ); + + final endIsBeforeClosing = endDateTime.isBefore( + circulationClosingDate, + ); + + final startIsBeforeReopening = startDateTime.isBefore( + circulationReOpeningDate, + ); + final endIsBeforeReopening = endDateTime.isBefore( + circulationReOpeningDate, + ); + + return (startIsBeforeClosing && + startIsBeforeReopening && + !endIsBeforeClosing && + endIsBeforeReopening) || + (!startIsBeforeClosing && + startIsBeforeReopening && + endIsBeforeClosing && + !endIsBeforeClosing) || + (!startIsBeforeClosing && + startIsBeforeReopening && + !endIsBeforeClosing && + !endIsBeforeReopening) || + (startIsBeforeClosing && + startIsBeforeReopening && + !endIsBeforeClosing && + !endIsBeforeReopening); } static bool getBooleanTotalClosingValue(String stringValue) { diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index 818e554e..a75ab9bc 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -78,34 +78,6 @@ class _ChabanBridgeForecastScreenState duration: state.durationNotificationValue, ), ); - 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, - ), - ), - ), - ], - ), - ), - ); context .read() .state @@ -115,45 +87,6 @@ class _ChabanBridgeForecastScreenState ).state.chabanBridgeForecasts, state, context, - ) - .then( - (value) => { - 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 index 663deb25..f17a2dc6 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:chabo/bloc/notification/notification_bloc.dart'; -import 'package:chabo/bloc/time_slot/time_slot_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widget_state.dart'; @@ -62,49 +61,79 @@ class _NotificationScreenState extends CustomWidgetState { top: 20, ), child: BlocBuilder( - builder: (context, state) { + builder: (context, notificationState) { return Column( children: [ - BlocBuilder( - builder: (context, state) { - return Column( - children: [ - _CustomListTile( - onChanged: (bool value) => - BlocProvider.of(context).add( - EnabledTimeSlotEvent( - enabled: value, - ), + Column( + children: [ + _CustomListTile( + onChanged: (bool value) => { + BlocProvider.of(context).add( + EnabledTimeSlotEvent( + enabled: value, ), - enabled: state.enabledForNotifications, - title: - AppLocalizations.of(context)!.favoriteSlots, - subtitle: AppLocalizations.of(context)! - .favoriteSlotsDescription, - leadingIcon: Icons.warning_rounded, - iconColor: - Theme.of(context).colorScheme.warningColor, ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - for (var i = 0; - i < state.timeSlots.length; - i++) ...[ - TimeSlotWidget( - timeSlot: state.timeSlots[i], - index: i, + if (value) + { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 7), + showCloseIcon: true, + backgroundColor: Theme.of(context) + .colorScheme + .warningColor, + content: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)! + .favoriteTimeSlotEnabledWarning, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), - ], - ], - ), - ), - ], - ); - }, + ), + ), + } + else + { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar( + reason: SnackBarClosedReason.action, + ), + }, + }, + enabled: notificationState + .timeSlotsEnabledForNotifications, + title: AppLocalizations.of(context)!.favoriteSlots, + subtitle: AppLocalizations.of(context)! + .favoriteSlotsDescription, + leadingIcon: Icons.warning_rounded, + iconColor: Theme.of(context).colorScheme.warningColor, + constrainedBySlots: false, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + for (var i = 0; + i < notificationState.timeSlotsValue.length; + i++) ...[ + TimeSlotWidget( + timeSlot: notificationState.timeSlotsValue[i], + index: i, + ), + ], + ], + ), + ), + ], ), const SizedBox( height: 10, @@ -124,13 +153,15 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), - enabled: state.openingNotificationEnabled, + enabled: notificationState.openingNotificationEnabled, title: AppLocalizations.of(context)! .openingNotificationTitle, subtitle: AppLocalizations.of(context)! .openingNotificationExplanation, leadingIcon: Icons.check_circle, iconColor: Colors.green, + constrainedBySlots: + notificationState.timeSlotsEnabledForNotifications, ), _CustomListTile( onChanged: (bool value) => @@ -139,13 +170,15 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), - enabled: state.closingNotificationEnabled, + enabled: notificationState.closingNotificationEnabled, title: AppLocalizations.of(context)! .closingNotificationTitle, subtitle: AppLocalizations.of(context)! .closingNotificationExplanation, leadingIcon: Icons.block_rounded, iconColor: Colors.red, + constrainedBySlots: + notificationState.timeSlotsEnabledForNotifications, ), const SizedBox( height: 20, @@ -155,7 +188,8 @@ class _NotificationScreenState extends CustomWidgetState { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state.durationNotificationValue + initialTime: notificationState + .durationNotificationValue .durationToTimeOfDay(), builder: (BuildContext context, Widget? child) { return MediaQuery( @@ -187,25 +221,27 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), - enabled: state.durationNotificationEnabled, + enabled: notificationState.durationNotificationEnabled, title: AppLocalizations.of(context)! .durationNotificationTitle( - state.durationNotificationValue + notificationState.durationNotificationValue .durationToString(context), ), subtitle: AppLocalizations.of(context)! .durationNotificationExplanation( - state.durationNotificationValue + notificationState.durationNotificationValue .durationToString(context), ), leadingIcon: Icons.timer_outlined, + constrainedBySlots: + notificationState.timeSlotsEnabledForNotifications, ), _CustomListTile( onTap: () { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, context: context, - initialTime: state.timeNotificationValue, + initialTime: notificationState.timeNotificationValue, builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context).copyWith( @@ -236,16 +272,18 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), - enabled: state.timeNotificationEnabled, + enabled: notificationState.timeNotificationEnabled, title: AppLocalizations.of(context)!.timeNotificationTitle( - state.timeNotificationValue.format(context), + notificationState.timeNotificationValue.format(context), ), subtitle: AppLocalizations.of(context)! .timeNotificationExplanation( - state.timeNotificationValue.format(context), + notificationState.timeNotificationValue.format(context), ), leadingIcon: Icons.plus_one_outlined, + constrainedBySlots: + notificationState.timeSlotsEnabledForNotifications, ), _CustomListTile( onTap: () { @@ -275,14 +313,16 @@ class _NotificationScreenState extends CustomWidgetState { }, ); }, - enabled: state.dayNotificationEnabled, + enabled: notificationState.dayNotificationEnabled, title: AppLocalizations.of(context)!.dayNotificationTitle( - state.dayNotificationValue.localizedName(context), + notificationState.dayNotificationValue + .localizedName(context), ), subtitle: AppLocalizations.of(context)! .dayNotificationExplanation( - state.dayNotificationValue.localizedName(context), - state.dayNotificationTimeValue.format( + notificationState.dayNotificationValue + .localizedName(context), + notificationState.dayNotificationTimeValue.format( context, ), ), @@ -293,6 +333,8 @@ class _NotificationScreenState extends CustomWidgetState { enabled: value, ), ), + constrainedBySlots: + notificationState.timeSlotsEnabledForNotifications, ), ], ); @@ -307,6 +349,7 @@ class _NotificationScreenState extends CustomWidgetState { class _CustomListTile extends StatelessWidget { final bool enabled; + final bool constrainedBySlots; final Function()? onTap; final Function(bool) onChanged; final String title; @@ -323,14 +366,53 @@ class _CustomListTile extends StatelessWidget { required this.subtitle, required this.leadingIcon, required this.onChanged, + required this.constrainedBySlots, }) : super(key: key); @override Widget build(BuildContext context) { return ListTile( - title: Text( - title, - style: Theme.of(context).textTheme.titleLarge, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Flexible( + flex: 3, + child: Text( + title, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.clip, + ), + ), + const SizedBox( + width: 10, + ), + Flexible( + child: AnimatedSwitcher( + duration: 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(-1.0, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: child, + ), + ); + }, + child: constrainedBySlots && enabled + ? CircleAvatar( + radius: 5, + backgroundColor: + Theme.of(context).colorScheme.warningColor, + child: Container(), + ) + : const SizedBox(), + ), + ), + ], ), horizontalTitleGap: 0, subtitle: Text(subtitle), diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 6396dea6..2285f026 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -95,7 +95,15 @@ class NotificationService { localNotifications.cancelAll().then((value) => null); List weekSeparatedChabanBridgeForecast = []; for (final chabanBridgeForecast in chabanBridgeForecasts) { - if (notificationSate.openingNotificationEnabled) { + /// Compute the slot time linked to a forecast before starting the notification computation + chabanBridgeForecast + .computeSlotInterference(notificationSate.timeSlotsValue); + final hasTimeSlots = chabanBridgeForecast.interferingTimeSlots.isNotEmpty; + if ((notificationSate.openingNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.openingNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { index += 1; await _createOpeningScheduledNotifications( index, @@ -103,7 +111,11 @@ class NotificationService { context, ); } - if (notificationSate.closingNotificationEnabled) { + if ((notificationSate.closingNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.closingNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { index += 1; await _createClosingScheduledNotifications( index, @@ -111,7 +123,11 @@ class NotificationService { context, ); } - if (notificationSate.timeNotificationEnabled) { + if ((notificationSate.timeNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.timeNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { index += 1; await _createTimeScheduledNotifications( index, @@ -120,7 +136,11 @@ class NotificationService { notificationSate.timeNotificationValue, ); } - if (notificationSate.dayNotificationEnabled) { + if ((notificationSate.dayNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.dayNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { var last = chabanBridgeForecast.circulationClosingDate .previous(notificationSate.dayNotificationValue.weekPosition); if (weekSeparatedChabanBridgeForecast.isEmpty || @@ -139,7 +159,11 @@ class NotificationService { weekSeparatedChabanBridgeForecast.add(last); } } - if (notificationSate.durationNotificationEnabled) { + if ((notificationSate.durationNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.durationNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { index += 1; await _createDurationScheduledNotifications( index, @@ -293,7 +317,7 @@ class NotificationService { DateTime notificationScheduleTime, NotificationDetails notificationDetails, ) async { - /// Prevent from creating notification in the past + /// Prevent from creating notification in the past AND make sure that the user enable the notification if (notificationScheduleTime.isAfter(DateTime.now()) && await _requestPermissions()) { developer.log( diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index f8effb30..75c56c8a 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -1,6 +1,6 @@ import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; -import 'package:chabo/bloc/time_slot/time_slot_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'; @@ -33,14 +33,16 @@ class _ForecastListWidgetState extends State { }, child: BlocBuilder( builder: (context, forecastState) { - return BlocBuilder( + return BlocBuilder( + buildWhen: (previous, next) => + previous.timeSlotsValue != next.timeSlotsValue, builder: (context, timeSlotState) { return ListView.separated( cacheExtent: 5000, padding: const EdgeInsets.all(0), itemBuilder: (BuildContext context, int index) { forecastState.chabanBridgeForecasts[index] - .checkSlotInterference(timeSlotState.timeSlots); + .computeSlotInterference(timeSlotState.timeSlotsValue); return ForecastListItemWidget( key: GlobalObjectKey( From f51c2f7449ec094240bd8bef99d9f6a6909d6545 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 21:08:30 +0200 Subject: [PATCH 23/34] feat(notifications): display a message if the notifications are disabled --- lib/const.dart | 2 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/screens/notification_screen.dart | 58 +++++++++ lib/service/notification_service.dart | 167 ++++++++++++++------------ pubspec.lock | 8 ++ pubspec.yaml | 1 + 8 files changed, 168 insertions(+), 80 deletions(-) diff --git a/lib/const.dart b/lib/const.dart index a94068c2..f730c99b 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -83,7 +83,7 @@ class Const { '@mipmap/ic_slice_launcher_adaptive_fore'; static const Duration notificationDurationValueDefaultValue = Duration(minutes: 60); - static const bool notificationDurationEnabledDefaultValue = true; + static const bool notificationDurationEnabledDefaultValue = false; static TimeOfDay notificationTimeValueDefaultValue = const TimeOfDay(hour: 6, minute: 0); static const bool notificationTimeEnabledDefaultValue = false; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3033c98c..cc14bdac 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -176,5 +176,7 @@ } }, "favoriteSlotsInterferenceWarning": "This schedule interferes with one or more time slots", - "favoriteTimeSlotEnabledWarning": "Attention, by activating this parameter you will only receive notifications when an event occurs during one of your time slots" + "favoriteTimeSlotEnabledWarning": "Attention, by activating this parameter you will only receive notifications when an event occurs during one of your time slots", + "notificationNotEnabledMessage": "Please note that notifications are not allowed. Please go to the application settings to authorize the sending of notifications", + "notificationNotEnabledOpenSettings": "Open notification settings" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ad9a7c3b..cbe7c664 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -176,5 +176,7 @@ } }, "favoriteSlotsInterferenceWarning": "Este horario interfiere con uno o más intervalos de tiempo", - "favoriteTimeSlotEnabledWarning": "Atención, al activar este parámetro solo recibirás notificaciones cuando ocurra un evento durante una de tus franjas horarias" + "favoriteTimeSlotEnabledWarning": "Atención, al activar este parámetro solo recibirás notificaciones cuando ocurra un evento durante una de tus franjas horarias", + "notificationNotEnabledMessage": "Tenga en cuenta que las notificaciones no están permitidas. Vaya a la configuración de la aplicación para autorizar el envío de notificaciones.", + "notificationNotEnabledOpenSettings": "Abrir configuración de notificaciones" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8fd70674..7312e65d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -176,5 +176,7 @@ } }, "favoriteSlotsInterferenceWarning": "Cette prévision interfère avec un ou plusieurs créneaux", - "favoriteTimeSlotEnabledWarning": "Attention, en activant ce paramètres vous allez uniquement recevoir les notifications lorsqu'un évèmenent se produit durant l'un de vos créneaux" + "favoriteTimeSlotEnabledWarning": "Attention, en activant ce paramètres vous allez uniquement recevoir les notifications lorsqu'un évèmenent se produit durant l'un de vos créneaux", + "notificationNotEnabledMessage": "Attention, les notifications ne sont pas autorisées. Veuillez vous rendre dans les paramètres de l'application pour autoriser l'envoi de notification", + "notificationNotEnabledOpenSettings": "Ouvrir les paramètres de notification" } \ No newline at end of file diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index f17a2dc6..90d210e2 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -1,7 +1,9 @@ import 'dart:ui'; +import 'package:app_settings/app_settings.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; +import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; @@ -64,6 +66,62 @@ class _NotificationScreenState extends CustomWidgetState { builder: (context, notificationState) { return Column( children: [ + FutureBuilder( + future: context + .read() + .state + .areNotificationsEnabled(), + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.hasData && + snapshot.data != null && + !snapshot.data!) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 10), + showCloseIcon: true, + backgroundColor: + Theme.of(context).colorScheme.error, + content: Column( + children: [ + Text( + AppLocalizations.of(context)! + .notificationNotEnabledMessage, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onError, + ), + overflow: TextOverflow.visible, + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () => AppSettings + .openNotificationSettings(), + child: Text( + AppLocalizations.of(context)! + .notificationNotEnabledOpenSettings, + ), + ), + ], + ), + ), + ); + }); + } + + return const SizedBox.shrink(); + }, + ), Column( children: [ _CustomListTile( diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 2285f026..7a92aeea 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -85,6 +85,18 @@ class NotificationService { return false; } + Future areNotificationsEnabled() async { + if (Platform.isAndroid) { + final AndroidFlutterLocalNotificationsPlugin? androidImplementation = + localNotifications.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + + return await androidImplementation?.areNotificationsEnabled() ?? false; + } + + return false; + } + Future computeNotifications( List chabanBridgeForecasts, NotificationState notificationSate, @@ -92,86 +104,90 @@ class NotificationService { ) async { tz.initializeTimeZones(); int index = 0; - localNotifications.cancelAll().then((value) => null); + localNotifications.cancelAll(); List weekSeparatedChabanBridgeForecast = []; - for (final chabanBridgeForecast in chabanBridgeForecasts) { - /// Compute the slot time linked to a forecast before starting the notification computation - chabanBridgeForecast - .computeSlotInterference(notificationSate.timeSlotsValue); - final hasTimeSlots = chabanBridgeForecast.interferingTimeSlots.isNotEmpty; - if ((notificationSate.openingNotificationEnabled && - !notificationSate.timeSlotsEnabledForNotifications) || - (notificationSate.openingNotificationEnabled && - notificationSate.timeSlotsEnabledForNotifications && - hasTimeSlots)) { - index += 1; - await _createOpeningScheduledNotifications( - index, - chabanBridgeForecast, - context, - ); - } - if ((notificationSate.closingNotificationEnabled && - !notificationSate.timeSlotsEnabledForNotifications) || - (notificationSate.closingNotificationEnabled && - notificationSate.timeSlotsEnabledForNotifications && - hasTimeSlots)) { - index += 1; - await _createClosingScheduledNotifications( - index, - chabanBridgeForecast, - context, - ); - } - if ((notificationSate.timeNotificationEnabled && - !notificationSate.timeSlotsEnabledForNotifications) || - (notificationSate.timeNotificationEnabled && - notificationSate.timeSlotsEnabledForNotifications && - hasTimeSlots)) { - index += 1; - await _createTimeScheduledNotifications( - index, - chabanBridgeForecast, - context, - notificationSate.timeNotificationValue, - ); - } - if ((notificationSate.dayNotificationEnabled && - !notificationSate.timeSlotsEnabledForNotifications) || - (notificationSate.dayNotificationEnabled && - notificationSate.timeSlotsEnabledForNotifications && - hasTimeSlots)) { - var last = chabanBridgeForecast.circulationClosingDate - .previous(notificationSate.dayNotificationValue.weekPosition); - if (weekSeparatedChabanBridgeForecast.isEmpty || - weekSeparatedChabanBridgeForecast.last == last) { - weekSeparatedChabanBridgeForecast.add(last); - } else { + if (await _requestPermissions()) { + for (final chabanBridgeForecast in chabanBridgeForecasts) { + /// Compute the slot time linked to a forecast before starting the notification computation + chabanBridgeForecast + .computeSlotInterference(notificationSate.timeSlotsValue); + final hasTimeSlots = + chabanBridgeForecast.interferingTimeSlots.isNotEmpty; + if ((notificationSate.openingNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.openingNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { index += 1; - await _createDayScheduledNotifications( + await _createOpeningScheduledNotifications( index, - weekSeparatedChabanBridgeForecast.length, - weekSeparatedChabanBridgeForecast.last, - notificationSate.dayNotificationTimeValue, + chabanBridgeForecast, context, ); - weekSeparatedChabanBridgeForecast.clear(); - weekSeparatedChabanBridgeForecast.add(last); } - } - if ((notificationSate.durationNotificationEnabled && - !notificationSate.timeSlotsEnabledForNotifications) || - (notificationSate.durationNotificationEnabled && - notificationSate.timeSlotsEnabledForNotifications && - hasTimeSlots)) { - index += 1; - await _createDurationScheduledNotifications( - index, - chabanBridgeForecast, - context, - notificationSate.durationNotificationValue, - notificationSate.durationNotificationValue.durationToString(context), - ); + if ((notificationSate.closingNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.closingNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { + index += 1; + await _createClosingScheduledNotifications( + index, + chabanBridgeForecast, + context, + ); + } + if ((notificationSate.timeNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.timeNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { + index += 1; + await _createTimeScheduledNotifications( + index, + chabanBridgeForecast, + context, + notificationSate.timeNotificationValue, + ); + } + if ((notificationSate.dayNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.dayNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { + var last = chabanBridgeForecast.circulationClosingDate + .previous(notificationSate.dayNotificationValue.weekPosition); + if (weekSeparatedChabanBridgeForecast.isEmpty || + weekSeparatedChabanBridgeForecast.last == last) { + weekSeparatedChabanBridgeForecast.add(last); + } else { + index += 1; + await _createDayScheduledNotifications( + index, + weekSeparatedChabanBridgeForecast.length, + weekSeparatedChabanBridgeForecast.last, + notificationSate.dayNotificationTimeValue, + context, + ); + weekSeparatedChabanBridgeForecast.clear(); + weekSeparatedChabanBridgeForecast.add(last); + } + } + if ((notificationSate.durationNotificationEnabled && + !notificationSate.timeSlotsEnabledForNotifications) || + (notificationSate.durationNotificationEnabled && + notificationSate.timeSlotsEnabledForNotifications && + hasTimeSlots)) { + index += 1; + await _createDurationScheduledNotifications( + index, + chabanBridgeForecast, + context, + notificationSate.durationNotificationValue, + notificationSate.durationNotificationValue + .durationToString(context), + ); + } } } } @@ -318,8 +334,7 @@ class NotificationService { NotificationDetails notificationDetails, ) async { /// Prevent from creating notification in the past AND make sure that the user enable the notification - if (notificationScheduleTime.isAfter(DateTime.now()) && - await _requestPermissions()) { + if (notificationScheduleTime.isAfter(DateTime.now())) { developer.log( 'Creating a notification on channel ${notificationDetails.android!.channelId} with ID $notificationId scheduled at $notificationScheduleTime', name: 'notification-service.on.scheduleNotification', diff --git a/pubspec.lock b/pubspec.lock index 266df46d..a0e95557 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + app_settings: + dependency: "direct main" + description: + name: app_settings + sha256: "66715a323ac36d6c8201035ba678777c0d2ea869e4d7064300d95af10c3bb8cb" + url: "https://pub.dev" + source: hosted + version: "4.2.0" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ba015aec..c5ed75b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: stream_transform: ^2.0.0 timezone: ^0.9.1 url_launcher: ^6.1.7 + app_settings: ^4.2.0 dev_dependencies: From baf2d9fae02500807fa410a6c1b8ce0166250eaf Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 23:00:40 +0200 Subject: [PATCH 24/34] feat(notifications): automatic check if the notifications are enabled --- android/app/src/main/AndroidManifest.xml | 1 + lib/bloc/notification/notification_bloc.dart | 75 ++++++++++++--- lib/bloc/notification/notification_event.dart | 8 ++ lib/bloc/notification/notification_state.dart | 4 + lib/chabo.dart | 9 +- lib/cubits/notification_service_cubit.dart | 6 -- .../chaban_bridge_forecast_screen.dart | 18 ++-- lib/screens/notification_screen.dart | 92 ++++++++----------- .../forecast/forecast_list_item_widget.dart | 2 + 9 files changed, 127 insertions(+), 88 deletions(-) delete mode 100644 lib/cubits/notification_service_cubit.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c71fca2b..d060281a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 5c35f70e..4fb0136d 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -1,22 +1,28 @@ +import 'dart:async'; + import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; import 'package:chabo/const.dart'; +import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/time_slot.dart'; +import 'package:chabo/service/notification_service.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; + final NotificationService notificationService; - NotificationBloc({required this.storageService}) - : super( + NotificationBloc({ + required this.storageService, + required this.notificationService, + }) : super( NotificationState( durationNotificationEnabled: Const.notificationDurationEnabledDefaultValue, @@ -35,6 +41,7 @@ class NotificationBloc extends Bloc { timeSlotsEnabledForNotifications: Const.notificationFavoriteSlotsEnabledDefaultValue, timeSlotsValue: Const.notificationFavoriteSlotsDefaultValue, + notificationEnabled: false, ), ) { on( @@ -81,6 +88,10 @@ class NotificationBloc extends Bloc { _onTimeSlotsEventValue, transformer: sequential(), ); + on( + _onComputeNotificationEvent, + transformer: sequential(), + ); on( _onAppEvent, transformer: sequential(), @@ -97,7 +108,9 @@ class NotificationBloc extends Bloc { ); HapticFeedback.lightImpact(); emit( - state.copyWith(openingNotificationEnabled: event.enabled), + state.copyWith( + openingNotificationEnabled: event.enabled, + ), ); } @@ -109,9 +122,14 @@ class NotificationBloc extends Bloc { Const.notificationClosingEnabledKey, event.enabled, ); + HapticFeedback.lightImpact(); + final enabled = await notificationService.areNotificationsEnabled(); emit( - state.copyWith(closingNotificationEnabled: event.enabled), + state.copyWith( + closingNotificationEnabled: event.enabled, + notificationEnabled: enabled, + ), ); } @@ -124,8 +142,12 @@ class NotificationBloc extends Bloc { event.enabled, ); HapticFeedback.lightImpact(); + final enabled = await notificationService.areNotificationsEnabled(); emit( - state.copyWith(dayNotificationEnabled: event.enabled), + state.copyWith( + dayNotificationEnabled: event.enabled, + notificationEnabled: enabled, + ), ); } @@ -133,10 +155,15 @@ class NotificationBloc extends Bloc { DayNotificationValueEvent event, Emitter emit, ) async { - await storageService.saveDay(Const.notificationDayValueKey, event.day); + await storageService.saveDay( + Const.notificationDayValueKey, + event.day, + ); HapticFeedback.lightImpact(); emit( - state.copyWith(dayNotificationValue: event.day), + state.copyWith( + dayNotificationValue: event.day, + ), ); } @@ -150,7 +177,9 @@ class NotificationBloc extends Bloc { ); HapticFeedback.lightImpact(); emit( - state.copyWith(dayNotificationTimeValue: event.time), + state.copyWith( + dayNotificationTimeValue: event.time, + ), ); } @@ -163,8 +192,12 @@ class NotificationBloc extends Bloc { event.enabled, ); HapticFeedback.lightImpact(); + final enabled = await notificationService.areNotificationsEnabled(); emit( - state.copyWith(timeNotificationEnabled: event.enabled), + state.copyWith( + timeNotificationEnabled: event.enabled, + notificationEnabled: enabled, + ), ); } @@ -177,7 +210,9 @@ class NotificationBloc extends Bloc { event.time, ); emit( - state.copyWith(timeNotificationValue: event.time), + state.copyWith( + timeNotificationValue: event.time, + ), ); } @@ -190,9 +225,11 @@ class NotificationBloc extends Bloc { event.enabled, ); HapticFeedback.lightImpact(); + final enabled = await notificationService.areNotificationsEnabled(); emit( state.copyWith( durationNotificationEnabled: event.enabled, + notificationEnabled: enabled, ), ); } @@ -244,10 +281,21 @@ class NotificationBloc extends Bloc { ); } + Future _onComputeNotificationEvent( + ComputeNotificationEvent event, + Emitter emit, + ) async { + await notificationService.computeNotifications( + event.forecasts, + state, + event.context, + ); + } + void _onAppEvent( AppEvent event, Emitter emit, - ) { + ) async { final durationNotificationEnabled = storageService.readBool(Const.notificationDurationEnabledKey) ?? Const.notificationDurationEnabledDefaultValue; @@ -292,6 +340,8 @@ class NotificationBloc extends Bloc { storageService.readBool(Const.notificationFavoriteSlotsEnabledKey) ?? Const.notificationFavoriteSlotsEnabledDefaultValue; + final enabled = await notificationService.areNotificationsEnabled(); + emit( state.copyWith( durationNotificationEnabled: durationNotificationEnabled, @@ -305,6 +355,7 @@ class NotificationBloc extends Bloc { closingNotificationEnabled: closingNotificationEnabled, timeSlotsValue: timeSlots, timeSlotsEnabledForNotifications: enabledForNotifications, + notificationEnabled: enabled, ), ); } diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart index a1f1a056..b0b42dfb 100644 --- a/lib/bloc/notification/notification_event.dart +++ b/lib/bloc/notification/notification_event.dart @@ -69,6 +69,14 @@ class ValueTimeSlotEvent extends NotificationEvent { ValueTimeSlotEvent({required this.timeSlot, required this.index}) : super(); } +class ComputeNotificationEvent extends NotificationEvent { + final List forecasts; + final BuildContext context; + + ComputeNotificationEvent({required this.forecasts, required this.context}) + : super(); +} + class AppEvent extends NotificationEvent { AppEvent() : super(); } diff --git a/lib/bloc/notification/notification_state.dart b/lib/bloc/notification/notification_state.dart index 2bbdbddb..47269678 100644 --- a/lib/bloc/notification/notification_state.dart +++ b/lib/bloc/notification/notification_state.dart @@ -12,8 +12,10 @@ class NotificationState { final bool closingNotificationEnabled; final bool timeSlotsEnabledForNotifications; final List timeSlotsValue; + final bool notificationEnabled; NotificationState({ + required this.notificationEnabled, required this.durationNotificationEnabled, required this.durationNotificationValue, required this.timeNotificationEnabled, @@ -39,6 +41,7 @@ class NotificationState { bool? closingNotificationEnabled, bool? timeSlotsEnabledForNotifications, List? timeSlotsValue, + bool? notificationEnabled, }) { return NotificationState( durationNotificationEnabled: @@ -61,6 +64,7 @@ class NotificationState { timeSlotsEnabledForNotifications: timeSlotsEnabledForNotifications ?? this.timeSlotsEnabledForNotifications, timeSlotsValue: timeSlotsValue ?? this.timeSlotsValue, + notificationEnabled: notificationEnabled ?? this.notificationEnabled, ); } } diff --git a/lib/chabo.dart b/lib/chabo.dart index ebaee445..8569b802 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -4,7 +4,6 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; -import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; @@ -37,13 +36,6 @@ class Chabo extends StatelessWidget { ), ), - /// Bloc intended to manage the Notifications service - BlocProvider( - create: (_) => NotificationServiceCubit( - notificationService, - ), - ), - /// Bloc intended to manage the FloatingActions BlocProvider( create: (_) => FloatingActionsCubit( @@ -77,6 +69,7 @@ class Chabo extends StatelessWidget { BlocProvider( create: (_) => NotificationBloc( storageService: storageService, + notificationService: notificationService, )..add( AppEvent(), ), diff --git a/lib/cubits/notification_service_cubit.dart b/lib/cubits/notification_service_cubit.dart deleted file mode 100644 index 43df0e87..00000000 --- a/lib/cubits/notification_service_cubit.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:chabo/service/notification_service.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class NotificationServiceCubit extends Cubit { - NotificationServiceCubit(super.initialState); -} diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/chaban_bridge_forecast_screen.dart index a75ab9bc..a5d5716f 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/chaban_bridge_forecast_screen.dart @@ -3,7 +3,6 @@ import 'package:chabo/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; -import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; @@ -78,16 +77,15 @@ class _ChabanBridgeForecastScreenState duration: state.durationNotificationValue, ), ); - context - .read() - .state - .computeNotifications( - BlocProvider.of( - context, - ).state.chabanBridgeForecasts, - state, + BlocProvider.of(context).add( + ComputeNotificationEvent( + forecasts: + BlocProvider.of( context, - ); + ).state.chabanBridgeForecasts, + context: context, + ), + ); }, ), ], diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index 90d210e2..c55b8df2 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -3,7 +3,6 @@ import 'dart:ui'; import 'package:app_settings/app_settings.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; -import 'package:chabo/cubits/notification_service_cubit.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/dialogs/days_of_the_week_dialog.dart'; @@ -66,62 +65,51 @@ class _NotificationScreenState extends CustomWidgetState { builder: (context, notificationState) { return Column( children: [ - FutureBuilder( - future: context - .read() - .state - .areNotificationsEnabled(), - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - if (snapshot.hasData && - snapshot.data != null && - !snapshot.data!) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 10), - showCloseIcon: true, - backgroundColor: - Theme.of(context).colorScheme.error, - content: Column( - children: [ - Text( + Builder(builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + if (!notificationState.notificationEnabled) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 10), + showCloseIcon: true, + backgroundColor: + Theme.of(context).colorScheme.error, + content: Column( + children: [ + Text( + AppLocalizations.of(context)! + .notificationNotEnabledMessage, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onError, + ), + overflow: TextOverflow.visible, + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () => + AppSettings.openNotificationSettings(), + child: Text( AppLocalizations.of(context)! - .notificationNotEnabledMessage, - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith( - color: Theme.of(context) - .colorScheme - .onError, - ), - overflow: TextOverflow.visible, - ), - const SizedBox( - height: 20, - ), - ElevatedButton( - onPressed: () => AppSettings - .openNotificationSettings(), - child: Text( - AppLocalizations.of(context)! - .notificationNotEnabledOpenSettings, - ), + .notificationNotEnabledOpenSettings, ), - ], - ), + ), + ], ), - ); - }); + ), + ); } + }); - return const SizedBox.shrink(); - }, - ), + return const SizedBox.shrink(); + }), Column( children: [ _CustomListTile( diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index 10a771e5..8150181c 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -117,6 +117,7 @@ class ForecastListItemWidget extends StatelessWidget { ), ), Flexible( + flex: 2, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -168,6 +169,7 @@ class ForecastListItemWidget extends StatelessWidget { ), ), Flexible( + flex: 2, child: Column( mainAxisSize: MainAxisSize.min, children: [ From c81426031316531f764bbcd2dff5cbcb02d5ba40 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 23:44:10 +0200 Subject: [PATCH 25/34] chore(ci): fix `Missing blank line before return` --- lib/service/notification_service.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index 3c6ff452..fc4f4bdc 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -320,6 +320,7 @@ class NotificationService { styleInformation: const BigTextStyleInformation(''), ticker: Const.androidTicket, ); + return NotificationDetails(android: androidNotificationDetails); } From f3716d762d1b4edd3ac50544825e25799aec1f2e Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Mon, 8 May 2023 23:49:11 +0200 Subject: [PATCH 26/34] chore(version): increment version to `v1.6.0` --- CHANGELOG_en.md | 9 +++++++++ CHANGELOG_es.md | 9 +++++++++ CHANGELOG_fr.md | 9 +++++++++ pubspec.yaml | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index e280649e..13b23163 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,12 @@ +# **v1.6.0** : + +- *Features*: + - It is now possible to add two favorite slots to be notified only of impacting events +- *Interface*: + - Revised list of closures to make it easier to read. + - Highlighting impacting events with an orange border. + - Adding a message if notifications are disabled +*** # **v1.4.0** : - *Features*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index a1c663b6..03814b50 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,12 @@ +# **v1.6.0** : + +- *Funcionalidades*: + - Ahora es posible añadir dos franjas horarias favoritas para ser notificado solo de los eventos impactantes +- *Interfaz*: + - Reordenar la lista de cierres para que sea más fácil de leer. + - Resalta eventos impactantes con un borde naranja. + - Añadido un mensaje si las notificaciones están desactivadas +*** # **v1.4.0** : - *Características*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index f07c095f..0025b1c2 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,12 @@ +# **v1.6.0** : + +- *Fonctionnalités*: + - Il est maintenant possible d'ajouter deux créneaux favoris pour n'être averti que des évènements impactant +- *Interface*: + - Remaniement de la liste des fermetures afin de la rendre plus facile à lire. + - Mise en valeur d'événements impactants grâce à une bordure orange. + - Ajout d'un message si les notifications sont désactivées +*** # **v1.4.0** : - *Fonctionnalités*: diff --git a/pubspec.yaml b/pubspec.yaml index 4e4cf7ef..903b5ee9 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.4.0 +version: 1.6.0 environment: sdk: '>=2.17.6 <3.0.0' From a09af2877bb9daaf5efb2b73b743367116b2da56 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 20:46:28 +0200 Subject: [PATCH 27/34] fix(bug): the snackbar is now properly showing when the sloted notifications are activated --- lib/screens/notification_screen.dart | 87 ++++++++++++++-------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen.dart index c55b8df2..4f39bfc2 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen.dart @@ -65,51 +65,54 @@ class _NotificationScreenState extends CustomWidgetState { builder: (context, notificationState) { return Column( children: [ - Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - if (!notificationState.notificationEnabled) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 10), - showCloseIcon: true, - backgroundColor: - Theme.of(context).colorScheme.error, - content: Column( - children: [ - Text( - AppLocalizations.of(context)! - .notificationNotEnabledMessage, - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith( - color: Theme.of(context) - .colorScheme - .onError, + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback( + (_) { + if (!notificationState.notificationEnabled) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 10), + showCloseIcon: true, + backgroundColor: + Theme.of(context).colorScheme.error, + content: Column( + children: [ + Text( + AppLocalizations.of(context)! + .notificationNotEnabledMessage, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onError, + ), + overflow: TextOverflow.visible, + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: () => AppSettings + .openNotificationSettings(), + child: Text( + AppLocalizations.of(context)! + .notificationNotEnabledOpenSettings, ), - overflow: TextOverflow.visible, - ), - const SizedBox( - height: 20, - ), - ElevatedButton( - onPressed: () => - AppSettings.openNotificationSettings(), - child: Text( - AppLocalizations.of(context)! - .notificationNotEnabledOpenSettings, - ), + ), + ], ), - ], - ), - ), - ); - } - }); + ), + ); + } + }, + ); - return const SizedBox.shrink(); - }), + return const SizedBox.shrink(); + }, + ), Column( children: [ _CustomListTile( From 6d3d8ce5886c40e0b17293b30873cb64db815e9f Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:08:30 +0200 Subject: [PATCH 28/34] chore(naming): rename all classes / files --- .../chaban_bridge_forecast_bloc.dart | 180 ------------------ .../chaban_bridge_forecast_event.dart | 5 - .../chaban_bridge_forecast_state.dart | 62 ------ .../chaban_bridge_status_event.dart | 29 --- lib/bloc/forecast/forecast_bloc.dart | 162 ++++++++++++++++ lib/bloc/forecast/forecast_event.dart | 5 + lib/bloc/forecast/forecast_state.dart | 54 ++++++ lib/bloc/notification/notification_bloc.dart | 2 +- lib/bloc/notification/notification_event.dart | 2 +- .../scroll_status/scroll_status_bloc.dart | 2 +- .../scroll_status/scroll_status_event.dart | 2 +- .../scroll_status/scroll_status_state.dart | 4 +- .../status_bloc.dart} | 96 +++++----- lib/bloc/status/status_event.dart | 29 +++ .../status_state.dart} | 55 +++--- lib/chabo.dart | 14 +- lib/const.dart | 2 +- ....dart => forecast_information_dialog.dart} | 18 +- ...e_forecast.dart => abstract_forecast.dart} | 12 +- ..._boat_forecast.dart => boat_forecast.dart} | 31 ++- ...chaban_bridge_forecast_closing_reason.dart | 4 - .../chaban_bridge_forecast_closing_type.dart | 4 - lib/models/enums/forecast_closing_reason.dart | 4 + lib/models/enums/forecast_closing_type.dart | 4 + ...orecast.dart => maintenance_forecast.dart} | 31 ++- ...ecast_screen.dart => forecast_screen.dart} | 52 +++-- lib/service/notification_service.dart | 62 +++--- .../forecast/forecast_list_item_widget.dart | 29 ++- .../forecast/forecast_list_widget.dart | 48 +++-- lib/widgets/forecast/status_widget.dart | 14 +- .../ephemeral/Flutter-Generated.xcconfig | 4 +- .../ephemeral/flutter_export_environment.sh | 4 +- 32 files changed, 487 insertions(+), 539 deletions(-) delete mode 100644 lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart delete mode 100644 lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart delete mode 100644 lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart delete mode 100644 lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart create mode 100644 lib/bloc/forecast/forecast_bloc.dart create mode 100644 lib/bloc/forecast/forecast_event.dart create mode 100644 lib/bloc/forecast/forecast_state.dart rename lib/bloc/{chaban_bridge_status/chaban_bridge_status_bloc.dart => status/status_bloc.dart} (62%) create mode 100644 lib/bloc/status/status_event.dart rename lib/bloc/{chaban_bridge_status/chaban_bridge_status_state.dart => status/status_state.dart} (60%) rename lib/dialogs/{chaban_bridge_forecast_information_dialog.dart => forecast_information_dialog.dart} (82%) rename lib/models/{abstract_chaban_bridge_forecast.dart => abstract_forecast.dart} (92%) rename lib/models/{chaban_bridge_boat_forecast.dart => boat_forecast.dart} (88%) delete mode 100644 lib/models/enums/chaban_bridge_forecast_closing_reason.dart delete mode 100644 lib/models/enums/chaban_bridge_forecast_closing_type.dart create mode 100644 lib/models/enums/forecast_closing_reason.dart create mode 100644 lib/models/enums/forecast_closing_type.dart rename lib/models/{chaban_bridge_maintenance_forecast.dart => maintenance_forecast.dart} (84%) rename lib/screens/{chaban_bridge_forecast_screen.dart => forecast_screen.dart} (63%) diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart deleted file mode 100644 index b78e0272..00000000 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:chabo/bloc/chabo_event.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_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 - extends Bloc { - final http.Client httpClient; - - ChabanBridgeForecastBloc({required this.httpClient}) - : super(const ChabanBridgeForecastState()) { - Timer.periodic(const Duration(seconds: 1), _onRefreshCurrentStatus); - on( - _onChabanBridgeForecastFetched, - ); - } - - 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 && - currentStatus != previousStatus) { - // ignore: invalid_use_of_visible_for_testing_member - emit( - state.copyWith( - currentChabanBridgeForecast: currentStatus, - previousChabanBridgeForecast: previousStatus, - ), - ); - } - } - } catch (_) { - // ignore: invalid_use_of_visible_for_testing_member - emit(state.copyWith( - status: ChabanBridgeForecastStatus.failure, - message: _.toString(), - )); - } - } - - Future> _fetchChabanBridgeForecasts( - int offset, - ) async { - var uri = Uri.https( - 'opendata.bordeaux-metropole.fr', - '/api/records/1.0/search', - { - 'dataset': 'previsions_pont_chaban', - 'rows': '${Const.chabanBridgeForecastLimit}', - 'sort': '-date_passage', - 'start': '$offset', - 'timezone': 'Europe/Paris', - }, - ); - final response = await httpClient.get(uri); - if (response.statusCode == 200) { - final body = json.decode(response.body); - - return (body['records'] as List).map((json) { - if (json['fields']['bateau'].toString().toLowerCase() == - 'maintenance') { - final maintenanceForecast = - ChabanBridgeMaintenanceForecast.fromJSON(json); - - return maintenanceForecast; - } - final boatForecast = ChabanBridgeBoatForecast.fromJSON(json); - - return boatForecast; - }).toList() - ..sort((a, b) => - a.circulationClosingDate.compareTo(b.circulationClosingDate)); - } - - return []; - } - - AbstractChabanBridgeForecast _getCurrentStatus( - List chabanBridgeForecast, - ) { - int middle = chabanBridgeForecast.length ~/ 2; - if ((chabanBridgeForecast[middle] - .circulationClosingDate - .isBefore(DateTime.now()) && - chabanBridgeForecast[middle] - .circulationReOpeningDate - .isAfter(DateTime.now()))) { - return chabanBridgeForecast[middle]; - } - if (chabanBridgeForecast.length == 2) { - 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())) { - return _getCurrentStatus(chabanBridgeForecast.sublist(0, middle + 1)); - } else { - return _getCurrentStatus(chabanBridgeForecast.sublist(middle)); - } - } - - AbstractChabanBridgeForecast? _getPreviousStatus( - List chabanBridgeForecasts, - AbstractChabanBridgeForecast currentStatus, - ) { - return chabanBridgeForecasts.indexOf(currentStatus) == 0 - ? null - : chabanBridgeForecasts - .elementAt(chabanBridgeForecasts.indexOf(currentStatus) - 1); - } - - Future _onChabanBridgeForecastFetched( - ChabanBridgeForecastFetched event, - Emitter emit, - ) async { - if (state.hasReachedMax) return; - try { - if (state.status == ChabanBridgeForecastStatus.initial) { - final chabanBridgeForecasts = - await _fetchChabanBridgeForecasts(state.offset); - final currentStatus = _getCurrentStatus(chabanBridgeForecasts); - emit(state.copyWith( - status: ChabanBridgeForecastStatus.success, - chabanBridgeForecasts: chabanBridgeForecasts, - currentChabanBridgeForecast: currentStatus, - previousChabanBridgeForecast: - _getPreviousStatus(chabanBridgeForecasts, currentStatus), - hasReachedMax: false, - offset: state.offset + Const.chabanBridgeForecastLimit, - )); - } - final chabanBridgeForecasts = - await _fetchChabanBridgeForecasts(state.chabanBridgeForecasts.length); - 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(), - )); - } - } -} diff --git a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart b/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart deleted file mode 100644 index 34ae8233..00000000 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_event.dart +++ /dev/null @@ -1,5 +0,0 @@ -part of 'chaban_bridge_forecast_bloc.dart'; - -abstract class ChabanBridgeForecastEvent extends ChaboEvent {} - -class ChabanBridgeForecastFetched 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 deleted file mode 100644 index 70e0703e..00000000 --- a/lib/bloc/chaban_bridge_forecast/chaban_bridge_forecast_state.dart +++ /dev/null @@ -1,62 +0,0 @@ -part of 'chaban_bridge_forecast_bloc.dart'; - -class ChabanBridgeForecastState extends Equatable { - final ChabanBridgeForecastStatus status; - final List chabanBridgeForecasts; - final AbstractChabanBridgeForecast? currentChabanBridgeForecast; - final AbstractChabanBridgeForecast? previousChabanBridgeForecast; - final bool hasReachedMax; - final int offset; - final String message; - - const ChabanBridgeForecastState({ - this.status = ChabanBridgeForecastStatus.initial, - this.chabanBridgeForecasts = const [], - this.currentChabanBridgeForecast, - this.previousChabanBridgeForecast, - this.hasReachedMax = false, - this.offset = 0, - this.message = 'OK', - }); - - ChabanBridgeForecastState copyWith({ - ChabanBridgeForecastStatus? status, - List? chabanBridgeForecasts, - AbstractChabanBridgeForecast? currentChabanBridgeForecast, - AbstractChabanBridgeForecast? previousChabanBridgeForecast, - bool? hasReachedMax, - int? offset, - String? message, - }) { - return ChabanBridgeForecastState( - status: status ?? this.status, - chabanBridgeForecasts: - chabanBridgeForecasts ?? this.chabanBridgeForecasts, - currentChabanBridgeForecast: - currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, - previousChabanBridgeForecast: - previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, - hasReachedMax: hasReachedMax ?? this.hasReachedMax, - offset: offset ?? this.offset, - message: message ?? this.message, - ); - } - - @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, - currentChabanBridgeForecast, - previousChabanBridgeForecast, - ]; -} - -enum ChabanBridgeForecastStatus { initial, success, failure } diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart b/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart deleted file mode 100644 index bb9151d6..00000000 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_event.dart +++ /dev/null @@ -1,29 +0,0 @@ -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(); -} - -class ChabanBridgeStatusDurationChanged extends ChabanBridgeStatusEvent { - final Duration duration; - - ChabanBridgeStatusDurationChanged({ - required this.duration, - }) : super(); -} diff --git a/lib/bloc/forecast/forecast_bloc.dart b/lib/bloc/forecast/forecast_bloc.dart new file mode 100644 index 00000000..efb9bd39 --- /dev/null +++ b/lib/bloc/forecast/forecast_bloc.dart @@ -0,0 +1,162 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/const.dart'; +import 'package:chabo/models/abstract_forecast.dart'; +import 'package:chabo/models/boat_forecast.dart'; +import 'package:chabo/models/maintenance_forecast.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:http/http.dart' as http; + +part 'forecast_event.dart'; +part 'forecast_state.dart'; + +class ForecastBloc extends Bloc { + final http.Client httpClient; + + ForecastBloc({required this.httpClient}) : super(const ForecastState()) { + Timer.periodic(const Duration(seconds: 1), _onRefreshCurrentStatus); + on( + _onForecastFetched, + ); + } + + void _onRefreshCurrentStatus(Timer timer) { + try { + if (state.status == ForecastStatus.success) { + final currentStatus = _getCurrentStatus(state.forecasts); + final previousStatus = + _getPreviousStatus(state.forecasts, currentStatus); + if (currentStatus != state.currentForecast && + currentStatus != previousStatus) { + // ignore: invalid_use_of_visible_for_testing_member + emit( + state.copyWith( + currentForecast: currentStatus, + previousForecast: previousStatus, + ), + ); + } + } + } catch (_) { + // ignore: invalid_use_of_visible_for_testing_member + emit(state.copyWith( + status: ForecastStatus.failure, + message: _.toString(), + )); + } + } + + Future> _fetchForecasts( + int offset, + ) async { + var uri = Uri.https( + 'opendata.bordeaux-metropole.fr', + '/api/records/1.0/search', + { + 'dataset': 'previsions_pont_chaban', + 'rows': '${Const.forecastLimit}', + 'sort': '-date_passage', + 'start': '$offset', + 'timezone': 'Europe/Paris', + }, + ); + final response = await httpClient.get(uri); + if (response.statusCode == 200) { + final body = json.decode(response.body); + + return (body['records'] as List).map((json) { + if (json['fields']['bateau'].toString().toLowerCase() == + 'maintenance') { + final maintenanceForecast = MaintenanceForecast.fromJSON(json); + + return maintenanceForecast; + } + final boatForecast = BoatForecast.fromJSON(json); + + return boatForecast; + }).toList() + ..sort((a, b) => + a.circulationClosingDate.compareTo(b.circulationClosingDate)); + } + + return []; + } + + AbstractForecast _getCurrentStatus( + List forecast, + ) { + int middle = forecast.length ~/ 2; + if ((forecast[middle].circulationClosingDate.isBefore(DateTime.now()) && + forecast[middle].circulationReOpeningDate.isAfter(DateTime.now()))) { + return forecast[middle]; + } + if (forecast.length == 2) { + return forecast[1].circulationClosingDate.isAfter(DateTime.now()) && + forecast[0].circulationReOpeningDate.isBefore(DateTime.now()) + ? forecast[1] + : forecast[0]; + } else if (forecast[middle] + .circulationClosingDate + .isAfter(DateTime.now())) { + return _getCurrentStatus(forecast.sublist(0, middle + 1)); + } else { + return _getCurrentStatus(forecast.sublist(middle)); + } + } + + AbstractForecast? _getPreviousStatus( + List forecasts, + AbstractForecast currentStatus, + ) { + return forecasts.indexOf(currentStatus) == 0 + ? null + : forecasts.elementAt(forecasts.indexOf(currentStatus) - 1); + } + + Future _onForecastFetched( + ForecastFetched event, + Emitter emit, + ) async { + if (state.hasReachedMax) return; + try { + if (state.status == ForecastStatus.initial) { + final forecasts = await _fetchForecasts(state.offset); + final currentStatus = _getCurrentStatus(forecasts); + emit(state.copyWith( + status: ForecastStatus.success, + forecasts: forecasts, + currentForecast: currentStatus, + previousForecast: _getPreviousStatus(forecasts, currentStatus), + hasReachedMax: false, + offset: state.offset + Const.forecastLimit, + )); + } + final forecasts = await _fetchForecasts(state.forecasts.length); + emit( + forecasts.isEmpty + ? state.copyWith(hasReachedMax: true) + : state.copyWith( + currentForecast: + state.currentForecast ?? _getCurrentStatus(forecasts), + previousForecast: state.previousForecast ?? + _getPreviousStatus( + forecasts, + _getCurrentStatus(forecasts), + ), + status: ForecastStatus.success, + forecasts: List.of(state.forecasts)..addAll(forecasts), + hasReachedMax: false, + offset: state.offset + Const.forecastLimit, + ), + ); + } catch (_) { + emit(state.copyWith( + status: ForecastStatus.failure, + message: _.toString(), + )); + } + } +} diff --git a/lib/bloc/forecast/forecast_event.dart b/lib/bloc/forecast/forecast_event.dart new file mode 100644 index 00000000..5bbf454d --- /dev/null +++ b/lib/bloc/forecast/forecast_event.dart @@ -0,0 +1,5 @@ +part of 'forecast_bloc.dart'; + +abstract class ForecastEvent extends ChaboEvent {} + +class ForecastFetched extends ForecastEvent {} diff --git a/lib/bloc/forecast/forecast_state.dart b/lib/bloc/forecast/forecast_state.dart new file mode 100644 index 00000000..3cac3b30 --- /dev/null +++ b/lib/bloc/forecast/forecast_state.dart @@ -0,0 +1,54 @@ +part of 'forecast_bloc.dart'; + +class ForecastState extends Equatable { + final ForecastStatus status; + final List forecasts; + final AbstractForecast? currentForecast; + final AbstractForecast? previousForecast; + final bool hasReachedMax; + final int offset; + final String message; + + const ForecastState({ + this.status = ForecastStatus.initial, + this.forecasts = const [], + this.currentForecast, + this.previousForecast, + this.hasReachedMax = false, + this.offset = 0, + this.message = 'OK', + }); + + ForecastState copyWith({ + ForecastStatus? status, + List? forecasts, + AbstractForecast? currentForecast, + AbstractForecast? previousForecast, + bool? hasReachedMax, + int? offset, + String? message, + }) { + return ForecastState( + status: status ?? this.status, + forecasts: forecasts ?? this.forecasts, + currentForecast: currentForecast ?? this.currentForecast, + previousForecast: previousForecast ?? this.previousForecast, + hasReachedMax: hasReachedMax ?? this.hasReachedMax, + offset: offset ?? this.offset, + message: message ?? this.message, + ); + } + + @override + List get props => [ + status, + forecasts, + hasReachedMax, + offset, + message, + currentForecast, + previousForecast, + ]; +} + +enum ForecastStatus { initial, success, failure } diff --git a/lib/bloc/notification/notification_bloc.dart b/lib/bloc/notification/notification_bloc.dart index 4fb0136d..fdd724ba 100644 --- a/lib/bloc/notification/notification_bloc.dart +++ b/lib/bloc/notification/notification_bloc.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; import 'package:chabo/const.dart'; -import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:chabo/models/abstract_forecast.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:chabo/models/time_slot.dart'; import 'package:chabo/service/notification_service.dart'; diff --git a/lib/bloc/notification/notification_event.dart b/lib/bloc/notification/notification_event.dart index b0b42dfb..706b5458 100644 --- a/lib/bloc/notification/notification_event.dart +++ b/lib/bloc/notification/notification_event.dart @@ -70,7 +70,7 @@ class ValueTimeSlotEvent extends NotificationEvent { } class ComputeNotificationEvent extends NotificationEvent { - final List forecasts; + final List forecasts; final BuildContext context; ComputeNotificationEvent({required this.forecasts, required this.context}) diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index 09d8148e..f56c9c17 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -1,6 +1,6 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; -import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:chabo/models/abstract_forecast.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/bloc/scroll_status/scroll_status_event.dart b/lib/bloc/scroll_status/scroll_status_event.dart index fa6789c2..e6fe331c 100644 --- a/lib/bloc/scroll_status/scroll_status_event.dart +++ b/lib/bloc/scroll_status/scroll_status_event.dart @@ -7,7 +7,7 @@ class ScrollStatusChanged extends ScrollStatusEvent { } class GoTo extends ScrollStatusEvent { - final AbstractChabanBridgeForecast? goTo; + final AbstractForecast? goTo; GoTo({this.goTo}) : super(); } diff --git a/lib/bloc/scroll_status/scroll_status_state.dart b/lib/bloc/scroll_status/scroll_status_state.dart index dfeb5261..df7344ab 100644 --- a/lib/bloc/scroll_status/scroll_status_state.dart +++ b/lib/bloc/scroll_status/scroll_status_state.dart @@ -1,7 +1,7 @@ part of 'scroll_status_bloc.dart'; class ScrollStatusState { - final AbstractChabanBridgeForecast? currentTarget; + final AbstractForecast? currentTarget; final bool showCurrentStatus; final ScrollStatus status; @@ -14,7 +14,7 @@ class ScrollStatusState { ScrollStatusState copyWith({ bool? showCurrentStatus, ScrollStatus? status, - AbstractChabanBridgeForecast? currentTarget, + AbstractForecast? currentTarget, }) { return ScrollStatusState( status: status ?? this.status, diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart b/lib/bloc/status/status_bloc.dart similarity index 62% rename from lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart rename to lib/bloc/status/status_bloc.dart index 31627c1e..99111b4b 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_bloc.dart +++ b/lib/bloc/status/status_bloc.dart @@ -2,33 +2,32 @@ 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'; +import 'package:chabo/models/abstract_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'; +part 'status_event.dart'; +part 'status_state.dart'; -class ChabanBridgeStatusBloc - extends Bloc { - ChabanBridgeStatusBloc() : super(const ChabanBridgeStatusStateInitial()) { - on( - _onChabanBridgeStatusChanged, +class StatusBloc extends Bloc { + StatusBloc() : super(const StatusStateInitial()) { + on( + _onStatusChanged, ); - on( + on( _onRefresh, ); - on( + on( _onDurationChanged, ); } void _onDurationChanged( - ChabanBridgeStatusDurationChanged event, - Emitter emit, + StatusDurationChanged event, + Emitter emit, ) { emit( state.copyWith( @@ -37,21 +36,21 @@ class ChabanBridgeStatusBloc ); } - void _onChabanBridgeStatusChanged( - ChabanBridgeStatusChanged event, - Emitter emit, + void _onStatusChanged( + StatusChanged event, + Emitter emit, ) { emit( state.copyWith( - currentChabanBridgeForecast: event.currentChabanBridgeForecast, - previousChabanBridgeForecast: event.previousChabanBridgeForecast, + currentForecast: event.currentForecast, + previousForecast: event.previousForecast, ), ); } void _onRefresh( - ChabanBridgeStatusRefresh event, - Emitter emit, + StatusRefresh event, + Emitter emit, ) { final Duration? durationUntilNextEvent = _getDurationUntilNextEvent(); final Duration? durationBetweenPreviousAndNextEvent = @@ -74,19 +73,19 @@ class ChabanBridgeStatusBloc mainMessageStatus: mainMessageStatus, timeMessagePrefix: timeMessagePrefix, foregroundColor: foregroundColor, - chabanBridgeStatusLifecycle: state.durationUntilNextEvent != + statusLifecycle: state.durationUntilNextEvent != Duration.zero // Prevents from displaying the wrong status color - ? ChabanBridgeStatusLifecycle.populated - : ChabanBridgeStatusLifecycle.empty, + ? StatusLifecycle.populated + : StatusLifecycle.empty, backgroundColor: backgroundColor, ), ); } Color _getBackgroundColor(BuildContext context) { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - if (currentChabanBridgeForecast != null) { - final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); + final currentForecast = state.currentForecast; + if (currentForecast != null) { + final isOpen = !currentForecast.isCurrentlyClosed(); if (isOpen && state.durationUntilNextEvent.inMinutes < state.durationForCloseClosing.inMinutes) { @@ -102,9 +101,9 @@ class ChabanBridgeStatusBloc } Color _getForegroundColor(BuildContext context) { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - if (currentChabanBridgeForecast != null) { - final isOpen = !currentChabanBridgeForecast.isCurrentlyClosed(); + final currentForecast = state.currentForecast; + if (currentForecast != null) { + final isOpen = !currentForecast.isCurrentlyClosed(); final colorScheme = Theme.of(context).colorScheme; return isOpen || @@ -118,9 +117,9 @@ class ChabanBridgeStatusBloc } String _getTimeMessagePrefix(BuildContext context) { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - if (currentChabanBridgeForecast != null) { - return currentChabanBridgeForecast.isCurrentlyClosed() + final currentForecast = state.currentForecast; + if (currentForecast != null) { + return currentForecast.isCurrentlyClosed() ? '${AppLocalizations.of(context)!.scheduledToOpen.capitalize()} ' : '${AppLocalizations.of(context)!.nextClosingScheduled.capitalize()} '; } else { @@ -129,14 +128,14 @@ class ChabanBridgeStatusBloc } String _getMainStatus(BuildContext context) { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - if (currentChabanBridgeForecast != null && - !currentChabanBridgeForecast.isCurrentlyClosed() && + final currentForecast = state.currentForecast; + if (currentForecast != null && + !currentForecast.isCurrentlyClosed() && state.durationUntilNextEvent.inMinutes >= state.durationForCloseClosing.inMinutes) { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}'; - } else if (currentChabanBridgeForecast != null && - !currentChabanBridgeForecast.isCurrentlyClosed() && + } else if (currentForecast != null && + !currentForecast.isCurrentlyClosed() && state.durationUntilNextEvent.inMinutes < state.durationForCloseClosing.inMinutes) { return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.aboutToClose}'; @@ -146,26 +145,25 @@ class ChabanBridgeStatusBloc } Duration? _getDurationUntilNextEvent() { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; + final currentForecast = state.currentForecast; final DateTime now = DateTime.now(); - if (currentChabanBridgeForecast != null) { - return currentChabanBridgeForecast.isCurrentlyClosed() - ? currentChabanBridgeForecast.circulationReOpeningDate.difference(now) - : currentChabanBridgeForecast.circulationClosingDate.difference(now); + if (currentForecast != null) { + return currentForecast.isCurrentlyClosed() + ? currentForecast.circulationReOpeningDate.difference(now) + : currentForecast.circulationClosingDate.difference(now); } else { return null; } } Duration? _getDurationBetweenPreviousAndNextEvent() { - final currentChabanBridgeForecast = state.currentChabanBridgeForecast; - final previousChabanBridgeForecast = state.previousChabanBridgeForecast; - if (currentChabanBridgeForecast != null && - previousChabanBridgeForecast != null) { - return currentChabanBridgeForecast.isCurrentlyClosed() - ? currentChabanBridgeForecast.closedDuration - : currentChabanBridgeForecast.circulationClosingDate.difference( - previousChabanBridgeForecast.circulationReOpeningDate, + final currentForecast = state.currentForecast; + final previousForecast = state.previousForecast; + if (currentForecast != null && previousForecast != null) { + return currentForecast.isCurrentlyClosed() + ? currentForecast.closedDuration + : currentForecast.circulationClosingDate.difference( + previousForecast.circulationReOpeningDate, ); } else { return null; diff --git a/lib/bloc/status/status_event.dart b/lib/bloc/status/status_event.dart new file mode 100644 index 00000000..fbb1f278 --- /dev/null +++ b/lib/bloc/status/status_event.dart @@ -0,0 +1,29 @@ +part of 'status_bloc.dart'; + +class StatusEvent extends ChaboEvent {} + +class StatusChanged extends StatusEvent { + final AbstractForecast? currentForecast; + final AbstractForecast? previousForecast; + + StatusChanged({ + required this.currentForecast, + required this.previousForecast, + }) : super(); +} + +class StatusRefresh extends StatusEvent { + final BuildContext context; + + StatusRefresh({ + required this.context, + }) : super(); +} + +class StatusDurationChanged extends StatusEvent { + final Duration duration; + + StatusDurationChanged({ + required this.duration, + }) : super(); +} diff --git a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart b/lib/bloc/status/status_state.dart similarity index 60% rename from lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart rename to lib/bloc/status/status_state.dart index 9c68a042..318f6b35 100644 --- a/lib/bloc/chaban_bridge_status/chaban_bridge_status_state.dart +++ b/lib/bloc/status/status_state.dart @@ -1,9 +1,9 @@ -part of 'chaban_bridge_status_bloc.dart'; +part of 'status_bloc.dart'; -class ChabanBridgeStatusState extends Equatable { - final ChabanBridgeStatusLifecycle chabanBridgeStatusLifecycle; - final AbstractChabanBridgeForecast? currentChabanBridgeForecast; - final AbstractChabanBridgeForecast? previousChabanBridgeForecast; +class StatusState extends Equatable { + final StatusLifecycle statusLifecycle; + final AbstractForecast? currentForecast; + final AbstractForecast? previousForecast; final Duration durationUntilNextEvent; final Duration durationForCloseClosing; final Duration? durationBetweenPreviousAndNextEvent; @@ -13,10 +13,10 @@ class ChabanBridgeStatusState extends Equatable { final Color foregroundColor; final Color backgroundColor; - const ChabanBridgeStatusState({ - required this.chabanBridgeStatusLifecycle, - required this.currentChabanBridgeForecast, - required this.previousChabanBridgeForecast, + const StatusState({ + required this.statusLifecycle, + required this.currentForecast, + required this.previousForecast, required this.durationUntilNextEvent, required this.durationForCloseClosing, required this.durationBetweenPreviousAndNextEvent, @@ -27,10 +27,10 @@ class ChabanBridgeStatusState extends Equatable { required this.backgroundColor, }); - ChabanBridgeStatusState copyWith({ - ChabanBridgeStatusLifecycle? chabanBridgeStatusLifecycle, - AbstractChabanBridgeForecast? currentChabanBridgeForecast, - AbstractChabanBridgeForecast? previousChabanBridgeForecast, + StatusState copyWith({ + StatusLifecycle? statusLifecycle, + AbstractForecast? currentForecast, + AbstractForecast? previousForecast, Duration? durationUntilNextEvent, Duration? durationForCloseClosing, Duration? durationBetweenPreviousAndNextEvent, @@ -40,13 +40,10 @@ class ChabanBridgeStatusState extends Equatable { Color? foregroundColor, Color? backgroundColor, }) { - return ChabanBridgeStatusState( - chabanBridgeStatusLifecycle: - chabanBridgeStatusLifecycle ?? this.chabanBridgeStatusLifecycle, - currentChabanBridgeForecast: - currentChabanBridgeForecast ?? this.currentChabanBridgeForecast, - previousChabanBridgeForecast: - previousChabanBridgeForecast ?? this.previousChabanBridgeForecast, + return StatusState( + statusLifecycle: statusLifecycle ?? this.statusLifecycle, + currentForecast: currentForecast ?? this.currentForecast, + previousForecast: previousForecast ?? this.previousForecast, durationUntilNextEvent: durationUntilNextEvent ?? this.durationUntilNextEvent, durationForCloseClosing: @@ -64,9 +61,9 @@ class ChabanBridgeStatusState extends Equatable { @override List get props => [ - chabanBridgeStatusLifecycle, - currentChabanBridgeForecast, - previousChabanBridgeForecast, + statusLifecycle, + currentForecast, + previousForecast, durationUntilNextEvent, durationForCloseClosing, durationBetweenPreviousAndNextEvent, @@ -78,15 +75,15 @@ class ChabanBridgeStatusState extends Equatable { ]; } -class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { - const ChabanBridgeStatusStateInitial() +class StatusStateInitial extends StatusState { + const StatusStateInitial() : super( - previousChabanBridgeForecast: null, - currentChabanBridgeForecast: null, + previousForecast: null, + currentForecast: null, durationUntilNextEvent: Duration.zero, durationBetweenPreviousAndNextEvent: null, durationForCloseClosing: Const.notificationDurationValueDefaultValue, - chabanBridgeStatusLifecycle: ChabanBridgeStatusLifecycle.empty, + statusLifecycle: StatusLifecycle.empty, completionPercentage: 0, mainMessageStatus: '', timeMessagePrefix: '', @@ -95,4 +92,4 @@ class ChabanBridgeStatusStateInitial extends ChabanBridgeStatusState { ); } -enum ChabanBridgeStatusLifecycle { empty, populated } +enum StatusLifecycle { empty, populated } diff --git a/lib/chabo.dart b/lib/chabo.dart index 8569b802..a09ebe5d 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,10 +1,10 @@ -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/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; +import 'package:chabo/bloc/status/status_bloc.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; -import 'package:chabo/screens/chaban_bridge_forecast_screen.dart'; +import 'package:chabo/screens/forecast_screen.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/material.dart'; @@ -46,16 +46,16 @@ class Chabo extends StatelessWidget { /// Bloc intended to manage the forecast displayed BlocProvider( - create: (_) => ChabanBridgeForecastBloc( + create: (_) => ForecastBloc( httpClient: http.Client(), )..add( - ChabanBridgeForecastFetched(), + ForecastFetched(), ), ), /// Bloc intended to manage the status BlocProvider( - create: (_) => ChabanBridgeStatusBloc(), + create: (_) => StatusBloc(), ), /// Bloc intended to manage scroll to status to display (or not) the current status @@ -80,7 +80,7 @@ class Chabo extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: state.themeData, - home: const ChabanBridgeForecastScreen(), + home: const ForecastScreen(), localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/lib/const.dart b/lib/const.dart index f730c99b..91cf4b1b 100644 --- a/lib/const.dart +++ b/lib/const.dart @@ -10,7 +10,7 @@ class Const { static String legalLease = '© ${DateTime.now().year} - Valentin REVERSAT'; /// List - static const int chabanBridgeForecastLimit = 1000; + static const int forecastLimit = 1000; /// Paths static const String changelogPlaceholder = ':lang:'; diff --git a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart b/lib/dialogs/forecast_information_dialog.dart similarity index 82% rename from lib/dialogs/chaban_bridge_forecast_information_dialog.dart rename to lib/dialogs/forecast_information_dialog.dart index 19731c49..982deb62 100644 --- a/lib/dialogs/chaban_bridge_forecast_information_dialog.dart +++ b/lib/dialogs/forecast_information_dialog.dart @@ -1,14 +1,14 @@ import 'package:chabo/extensions/color_scheme_extension.dart'; -import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:chabo/models/abstract_forecast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class ChabanBridgeForecastInformationDialog extends StatelessWidget { - final AbstractChabanBridgeForecast chabanBridgeForecast; +class ForecastInformationDialog extends StatelessWidget { + final AbstractForecast forecast; - const ChabanBridgeForecastInformationDialog({ + const ForecastInformationDialog({ super.key, - required this.chabanBridgeForecast, + required this.forecast, }); @override @@ -27,7 +27,7 @@ class ChabanBridgeForecastInformationDialog extends StatelessWidget { ), title: Container( decoration: BoxDecoration( - color: chabanBridgeForecast.getColor(context, false), + color: forecast.getColor(context, false), borderRadius: const BorderRadius.only( topLeft: Radius.circular( 15.0, @@ -45,7 +45,7 @@ class ChabanBridgeForecastInformationDialog extends StatelessWidget { ), child: Row( children: [ - chabanBridgeForecast.getIconWidget(context, true), + forecast.getIconWidget(context, true), const SizedBox(width: 20), Text( AppLocalizations.of(context)!.information, @@ -67,9 +67,9 @@ class ChabanBridgeForecastInformationDialog extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(20), - child: chabanBridgeForecast.getInformationWidget(context), + child: forecast.getInformationWidget(context), ), - if (chabanBridgeForecast.interferingTimeSlots.isNotEmpty) + if (forecast.interferingTimeSlots.isNotEmpty) Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( diff --git a/lib/models/abstract_chaban_bridge_forecast.dart b/lib/models/abstract_forecast.dart similarity index 92% rename from lib/models/abstract_chaban_bridge_forecast.dart rename to lib/models/abstract_forecast.dart index 05dafa74..f9acb8d8 100644 --- a/lib/models/abstract_chaban_bridge_forecast.dart +++ b/lib/models/abstract_forecast.dart @@ -1,22 +1,22 @@ import 'package:chabo/extensions/date_time_extension.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:chabo/models/enums/forecast_closing_reason.dart'; +import 'package:chabo/models/enums/forecast_closing_type.dart'; import 'package:chabo/models/time_slot.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -abstract class AbstractChabanBridgeForecast extends Equatable { +abstract class AbstractForecast extends Equatable { final bool totalClosing; late final bool isDuringTwoDays; - final ChabanBridgeForecastClosingReason closingReason; + final ForecastClosingReason closingReason; late final Duration closedDuration; late final DateTime _circulationClosingDate; late final DateTime _circulationReOpeningDate; - final ChabanBridgeForecastClosingType closingType; + final ForecastClosingType closingType; final List interferingTimeSlots = []; - AbstractChabanBridgeForecast({ + AbstractForecast({ required this.totalClosing, required this.closingReason, required DateTime circulationClosingDate, diff --git a/lib/models/chaban_bridge_boat_forecast.dart b/lib/models/boat_forecast.dart similarity index 88% rename from lib/models/chaban_bridge_boat_forecast.dart rename to lib/models/boat_forecast.dart index 5ddaba4a..a810cd93 100644 --- a/lib/models/chaban_bridge_boat_forecast.dart +++ b/lib/models/boat_forecast.dart @@ -2,43 +2,42 @@ import 'package:chabo/extensions/boats_extension.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/abstract_forecast.dart'; import 'package:chabo/models/boat.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:chabo/models/enums/forecast_closing_reason.dart'; +import 'package:chabo/models/enums/forecast_closing_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; -class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { +class BoatForecast extends AbstractForecast { final List boats; static final List allBoatNames = []; - ChabanBridgeBoatForecast({ + BoatForecast({ required bool totalClosing, required DateTime circulationClosingDate, required DateTime circulationReOpeningDate, required this.boats, - required ChabanBridgeForecastClosingType closingType, + required ForecastClosingType closingType, }) : assert(boats.isNotEmpty), super( circulationClosingDate: circulationClosingDate, circulationReOpeningDate: circulationReOpeningDate, - closingReason: ChabanBridgeForecastClosingReason.boat, + closingReason: ForecastClosingReason.boat, closingType: closingType, totalClosing: totalClosing, ); - factory ChabanBridgeBoatForecast.fromJSON(Map json) { - var apiTimezone = - AbstractChabanBridgeForecast.getApiTimeZone(json['record_timestamp']); - var closingDate = AbstractChabanBridgeForecast.parseFieldDate( + factory BoatForecast.fromJSON(Map json) { + var apiTimezone = AbstractForecast.getApiTimeZone(json['record_timestamp']); + var closingDate = AbstractForecast.parseFieldDate( json, 'fermeture_a_la_circulation', apiTimezone, ); - var reopeningDate = AbstractChabanBridgeForecast.parseFieldDate( + var reopeningDate = AbstractForecast.parseFieldDate( json, 're_ouverture_a_la_circulation', apiTimezone, @@ -46,9 +45,9 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { var closingType = (json['fields']['type_de_fermeture'] as String).toLowerCase() == 'totale' - ? ChabanBridgeForecastClosingType.complete - : ChabanBridgeForecastClosingType.partial; - var totalClosing = AbstractChabanBridgeForecast.getBooleanTotalClosingValue( + ? ForecastClosingType.complete + : ForecastClosingType.partial; + var totalClosing = AbstractForecast.getBooleanTotalClosingValue( json['fields']['fermeture_totale'], ); @@ -66,7 +65,7 @@ class ChabanBridgeBoatForecast extends AbstractChabanBridgeForecast { } } - return ChabanBridgeBoatForecast( + return BoatForecast( boats: boats, totalClosing: totalClosing, circulationReOpeningDate: reopeningDate, diff --git a/lib/models/enums/chaban_bridge_forecast_closing_reason.dart b/lib/models/enums/chaban_bridge_forecast_closing_reason.dart deleted file mode 100644 index bb522c46..00000000 --- a/lib/models/enums/chaban_bridge_forecast_closing_reason.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum ChabanBridgeForecastClosingReason { - boat, - maintenance; -} diff --git a/lib/models/enums/chaban_bridge_forecast_closing_type.dart b/lib/models/enums/chaban_bridge_forecast_closing_type.dart deleted file mode 100644 index 6ddffd9c..00000000 --- a/lib/models/enums/chaban_bridge_forecast_closing_type.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum ChabanBridgeForecastClosingType { - partial, - complete; -} diff --git a/lib/models/enums/forecast_closing_reason.dart b/lib/models/enums/forecast_closing_reason.dart new file mode 100644 index 00000000..65173c27 --- /dev/null +++ b/lib/models/enums/forecast_closing_reason.dart @@ -0,0 +1,4 @@ +enum ForecastClosingReason { + boat, + maintenance; +} diff --git a/lib/models/enums/forecast_closing_type.dart b/lib/models/enums/forecast_closing_type.dart new file mode 100644 index 00000000..58f7e2cb --- /dev/null +++ b/lib/models/enums/forecast_closing_type.dart @@ -0,0 +1,4 @@ +enum ForecastClosingType { + partial, + complete; +} diff --git a/lib/models/chaban_bridge_maintenance_forecast.dart b/lib/models/maintenance_forecast.dart similarity index 84% rename from lib/models/chaban_bridge_maintenance_forecast.dart rename to lib/models/maintenance_forecast.dart index 5b2d303d..0b784551 100644 --- a/lib/models/chaban_bridge_maintenance_forecast.dart +++ b/lib/models/maintenance_forecast.dart @@ -1,36 +1,35 @@ 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'; -import 'package:chabo/models/enums/chaban_bridge_forecast_closing_type.dart'; +import 'package:chabo/models/abstract_forecast.dart'; +import 'package:chabo/models/enums/forecast_closing_reason.dart'; +import 'package:chabo/models/enums/forecast_closing_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; -class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { - ChabanBridgeMaintenanceForecast({ +class MaintenanceForecast extends AbstractForecast { + MaintenanceForecast({ required bool totalClosing, required DateTime circulationClosingDate, required DateTime circulationReOpeningDate, - required ChabanBridgeForecastClosingType closingType, + required ForecastClosingType closingType, }) : super( circulationClosingDate: circulationClosingDate, circulationReOpeningDate: circulationReOpeningDate, - closingReason: ChabanBridgeForecastClosingReason.maintenance, + closingReason: ForecastClosingReason.maintenance, closingType: closingType, totalClosing: totalClosing, ); - factory ChabanBridgeMaintenanceForecast.fromJSON(Map json) { - var apiTimezone = - AbstractChabanBridgeForecast.getApiTimeZone(json['record_timestamp']); - var closingDate = AbstractChabanBridgeForecast.parseFieldDate( + factory MaintenanceForecast.fromJSON(Map json) { + var apiTimezone = AbstractForecast.getApiTimeZone(json['record_timestamp']); + var closingDate = AbstractForecast.parseFieldDate( json, 'fermeture_a_la_circulation', apiTimezone, ); - var reopeningDate = AbstractChabanBridgeForecast.parseFieldDate( + var reopeningDate = AbstractForecast.parseFieldDate( json, 're_ouverture_a_la_circulation', apiTimezone, @@ -38,13 +37,13 @@ class ChabanBridgeMaintenanceForecast extends AbstractChabanBridgeForecast { var closingType = (json['fields']['type_de_fermeture'] as String).toLowerCase() == 'totale' - ? ChabanBridgeForecastClosingType.complete - : ChabanBridgeForecastClosingType.partial; - var totalClosing = AbstractChabanBridgeForecast.getBooleanTotalClosingValue( + ? ForecastClosingType.complete + : ForecastClosingType.partial; + var totalClosing = AbstractForecast.getBooleanTotalClosingValue( json['fields']['fermeture_totale'], ); - return ChabanBridgeMaintenanceForecast( + return MaintenanceForecast( totalClosing: totalClosing, circulationReOpeningDate: reopeningDate, circulationClosingDate: closingDate, diff --git a/lib/screens/chaban_bridge_forecast_screen.dart b/lib/screens/forecast_screen.dart similarity index 63% rename from lib/screens/chaban_bridge_forecast_screen.dart rename to lib/screens/forecast_screen.dart index a5d5716f..8de35462 100644 --- a/lib/screens/chaban_bridge_forecast_screen.dart +++ b/lib/screens/forecast_screen.dart @@ -1,7 +1,7 @@ -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/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; +import 'package:chabo/bloc/status/status_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; @@ -14,17 +14,16 @@ 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); +class ForecastScreen extends StatefulWidget { + const ForecastScreen({Key? key}) : super(key: key); @override State createState() { - return _ChabanBridgeForecastScreenState(); + return _ForecastScreenState(); } } -class _ChabanBridgeForecastScreenState - extends CustomWidgetState { +class _ForecastScreenState extends CustomWidgetState { @override Widget build(BuildContext context) { return BlocBuilder( @@ -36,53 +35,46 @@ class _ChabanBridgeForecastScreenState : FloatingActionButtonLocation.startFloat, floatingActionButtonAnimator: NoScalingAnimation(), body: SafeArea( - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) => - previous.status == ChabanBridgeForecastStatus.initial && - current.status == ChabanBridgeForecastStatus.success, + previous.status == ForecastStatus.initial && + current.status == ForecastStatus.success, builder: (context, state) { switch (state.status) { - case ChabanBridgeForecastStatus.failure: + case ForecastStatus.failure: return ErrorScreen(errorMessage: state.message); - case ChabanBridgeForecastStatus.success: - if (state.chabanBridgeForecasts.isEmpty) { + case ForecastStatus.success: + if (state.forecasts.isEmpty) { return const ErrorScreen(errorMessage: 'Empty return'); } return MultiBlocListener( listeners: [ - BlocListener( + BlocListener( listener: (context, state) { - BlocProvider.of(context) - .add( - ChabanBridgeStatusChanged( - currentChabanBridgeForecast: - state.currentChabanBridgeForecast, - previousChabanBridgeForecast: - state.previousChabanBridgeForecast, + BlocProvider.of(context).add( + StatusChanged( + currentForecast: state.currentForecast, + previousForecast: state.previousForecast, ), ); BlocProvider.of(context).add( - GoTo(goTo: state.currentChabanBridgeForecast), + GoTo(goTo: state.currentForecast), ); }, ), BlocListener( listener: (context, state) { - BlocProvider.of(context) - .add( - ChabanBridgeStatusDurationChanged( + BlocProvider.of(context).add( + StatusDurationChanged( duration: state.durationNotificationValue, ), ); BlocProvider.of(context).add( ComputeNotificationEvent( - forecasts: - BlocProvider.of( + forecasts: BlocProvider.of( context, - ).state.chabanBridgeForecasts, + ).state.forecasts, context: context, ), ); diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart index fc4f4bdc..d05019cd 100644 --- a/lib/service/notification_service.dart +++ b/lib/service/notification_service.dart @@ -7,7 +7,7 @@ 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/abstract_forecast.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/foundation.dart'; @@ -98,21 +98,19 @@ class NotificationService { } Future computeNotifications( - List chabanBridgeForecasts, + List forecasts, NotificationState notificationSate, BuildContext context, ) async { tz.initializeTimeZones(); int index = 0; localNotifications.cancelAll(); - List weekSeparatedChabanBridgeForecast = []; + List weekSeparatedForecast = []; if (await _requestPermissions()) { - for (final chabanBridgeForecast in chabanBridgeForecasts) { + for (final forecast in forecasts) { /// Compute the slot time linked to a forecast before starting the notification computation - chabanBridgeForecast - .computeSlotInterference(notificationSate.timeSlotsValue); - final hasTimeSlots = - chabanBridgeForecast.interferingTimeSlots.isNotEmpty; + forecast.computeSlotInterference(notificationSate.timeSlotsValue); + final hasTimeSlots = forecast.interferingTimeSlots.isNotEmpty; if ((notificationSate.openingNotificationEnabled && !notificationSate.timeSlotsEnabledForNotifications) || (notificationSate.openingNotificationEnabled && @@ -121,7 +119,7 @@ class NotificationService { index += 1; await _createOpeningScheduledNotifications( index, - chabanBridgeForecast, + forecast, context, ); } @@ -133,7 +131,7 @@ class NotificationService { index += 1; await _createClosingScheduledNotifications( index, - chabanBridgeForecast, + forecast, context, ); } @@ -145,7 +143,7 @@ class NotificationService { index += 1; await _createTimeScheduledNotifications( index, - chabanBridgeForecast, + forecast, context, notificationSate.timeNotificationValue, ); @@ -155,22 +153,22 @@ class NotificationService { (notificationSate.dayNotificationEnabled && notificationSate.timeSlotsEnabledForNotifications && hasTimeSlots)) { - var last = chabanBridgeForecast.circulationClosingDate + var last = forecast.circulationClosingDate .previous(notificationSate.dayNotificationValue.weekPosition); - if (weekSeparatedChabanBridgeForecast.isEmpty || - weekSeparatedChabanBridgeForecast.last == last) { - weekSeparatedChabanBridgeForecast.add(last); + if (weekSeparatedForecast.isEmpty || + weekSeparatedForecast.last == last) { + weekSeparatedForecast.add(last); } else { index += 1; await _createDayScheduledNotifications( index, - weekSeparatedChabanBridgeForecast.length, - weekSeparatedChabanBridgeForecast.last, + weekSeparatedForecast.length, + weekSeparatedForecast.last, notificationSate.dayNotificationTimeValue, context, ); - weekSeparatedChabanBridgeForecast.clear(); - weekSeparatedChabanBridgeForecast.add(last); + weekSeparatedForecast.clear(); + weekSeparatedForecast.add(last); } } if ((notificationSate.durationNotificationEnabled && @@ -181,7 +179,7 @@ class NotificationService { index += 1; await _createDurationScheduledNotifications( index, - chabanBridgeForecast, + forecast, context, notificationSate.durationNotificationValue, notificationSate.durationNotificationValue @@ -194,11 +192,10 @@ class NotificationService { Future _createOpeningScheduledNotifications( int index, - AbstractChabanBridgeForecast chabanBridgeForecast, + AbstractForecast forecast, BuildContext context, ) async { - final notificationScheduleTime = - chabanBridgeForecast.circulationReOpeningDate; + final notificationScheduleTime = forecast.circulationReOpeningDate; NotificationDetails notificationDetails = _notificationDetails( Const.notificationOpeningChannelId, AppLocalizations.of(context)!.notificationOpeningChannelName, @@ -214,11 +211,10 @@ class NotificationService { Future _createClosingScheduledNotifications( int index, - AbstractChabanBridgeForecast chabanBridgeForecast, + AbstractForecast forecast, BuildContext context, ) async { - final notificationScheduleTime = - chabanBridgeForecast.circulationClosingDate; + final notificationScheduleTime = forecast.circulationClosingDate; NotificationDetails notificationDetails = _notificationDetails( Const.notificationClosingChannelId, AppLocalizations.of(context)!.notificationClosingChannelName, @@ -226,7 +222,7 @@ class NotificationService { await _scheduleNotification( index, AppLocalizations.of(context)!.notificationClosingTitle, - chabanBridgeForecast.getNotificationClosingMessage(context), + forecast.getNotificationClosingMessage(context), notificationScheduleTime, notificationDetails, ); @@ -234,11 +230,11 @@ class NotificationService { Future _createTimeScheduledNotifications( int index, - AbstractChabanBridgeForecast chabanBridgeForecast, + AbstractForecast forecast, BuildContext context, TimeOfDay value, ) async { - final notificationScheduleTime = chabanBridgeForecast.circulationClosingDate + final notificationScheduleTime = forecast.circulationClosingDate .subtract( const Duration( days: 1, @@ -252,7 +248,7 @@ class NotificationService { await _scheduleNotification( index, AppLocalizations.of(context)!.notificationTimeTitle, - chabanBridgeForecast.getNotificationTimeMessage(context), + forecast.getNotificationTimeMessage(context), notificationScheduleTime, notificationDetails, ); @@ -260,13 +256,13 @@ class NotificationService { Future _createDurationScheduledNotifications( int index, - AbstractChabanBridgeForecast chabanBridgeForecast, + AbstractForecast forecast, BuildContext context, Duration durationValue, String durationString, ) async { final notificationScheduleTime = - chabanBridgeForecast.circulationClosingDate.subtract(durationValue); + forecast.circulationClosingDate.subtract(durationValue); NotificationDetails notificationDetails = _notificationDetails( Const.notificationDurationChannelId, AppLocalizations.of(context)!.notificationDurationChannelName, @@ -274,7 +270,7 @@ class NotificationService { await _scheduleNotification( index, AppLocalizations.of(context)!.notificationDurationTitle, - chabanBridgeForecast.getNotificationDurationMessage( + forecast.getNotificationDurationMessage( context, durationString, ), diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index 8150181c..7e8ecb08 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -1,17 +1,17 @@ import 'dart:ui'; import 'package:chabo/custom_properties.dart'; -import 'package:chabo/dialogs/chaban_bridge_forecast_information_dialog.dart'; +import 'package:chabo/dialogs/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:chabo/models/abstract_forecast.dart'; import 'package:chabo/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class ForecastListItemWidget extends StatelessWidget { - final AbstractChabanBridgeForecast chabanBridgeForecast; + final AbstractForecast forecast; final Function()? onTap; final bool hasPassed; final List timeSlots; @@ -20,7 +20,7 @@ class ForecastListItemWidget extends StatelessWidget { const ForecastListItemWidget({ Key? key, - required this.chabanBridgeForecast, + required this.forecast, required this.index, required this.hasPassed, required this.isCurrent, @@ -46,7 +46,7 @@ class ForecastListItemWidget extends StatelessWidget { side: isCurrent ? BorderSide( width: 2, - color: chabanBridgeForecast.getColor(context, false), + color: forecast.getColor(context, false), ) : BorderSide.none, ), @@ -74,8 +74,8 @@ class ForecastListItemWidget extends StatelessWidget { sigmaX: CustomProperties.blurSigmaX, sigmaY: CustomProperties.blurSigmaY, ), - child: ChabanBridgeForecastInformationDialog( - chabanBridgeForecast: chabanBridgeForecast, + child: ForecastInformationDialog( + forecast: forecast, ), ), ); @@ -104,13 +104,13 @@ class ForecastListItemWidget extends StatelessWidget { CustomProperties.borderRadius, ), ), - color: chabanBridgeForecast.getColor( + color: forecast.getColor( context, false, ), ), child: Center( - child: chabanBridgeForecast.getIconWidget( + child: forecast.getIconWidget( context, true, ), @@ -132,14 +132,14 @@ class ForecastListItemWidget extends StatelessWidget { Text( MaterialLocalizations.of(context) .formatMediumDate( - chabanBridgeForecast.circulationClosingDate, + forecast.circulationClosingDate, ), style: textTheme.bodySmall, ), ], ), Text( - chabanBridgeForecast.circulationClosingDateString( + forecast.circulationClosingDateString( context, ), style: textTheme.headlineSmall, @@ -153,8 +153,7 @@ class ForecastListItemWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - chabanBridgeForecast.closedDuration - .durationToString(context), + forecast.closedDuration.durationToString(context), style: TextStyle( color: Theme.of(context).colorScheme.timeColor, fontWeight: FontWeight.bold, @@ -184,14 +183,14 @@ class ForecastListItemWidget extends StatelessWidget { Text( MaterialLocalizations.of(context) .formatMediumDate( - chabanBridgeForecast.circulationReOpeningDate, + forecast.circulationReOpeningDate, ), style: textTheme.bodySmall, ), ], ), Text( - chabanBridgeForecast.circulationReOpeningDateString( + forecast.circulationReOpeningDateString( context, ), style: textTheme.headlineSmall, diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index 75c56c8a..ba789e1c 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -1,7 +1,7 @@ -import 'package:chabo/bloc/chaban_bridge_forecast/chaban_bridge_forecast_bloc.dart'; +import 'package:chabo/bloc/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; -import 'package:chabo/models/abstract_chaban_bridge_forecast.dart'; +import 'package:chabo/models/abstract_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'; @@ -31,7 +31,7 @@ class _ForecastListWidgetState extends State { return true; }, - child: BlocBuilder( + child: BlocBuilder( builder: (context, forecastState) { return BlocBuilder( buildWhen: (previous, next) => @@ -41,44 +41,41 @@ class _ForecastListWidgetState extends State { cacheExtent: 5000, padding: const EdgeInsets.all(0), itemBuilder: (BuildContext context, int index) { - forecastState.chabanBridgeForecasts[index] + forecastState.forecasts[index] .computeSlotInterference(timeSlotState.timeSlotsValue); return ForecastListItemWidget( key: GlobalObjectKey( - forecastState.chabanBridgeForecasts[index].hashCode, + forecastState.forecasts[index].hashCode, ), - isCurrent: forecastState.chabanBridgeForecasts[index] == - forecastState.currentChabanBridgeForecast, + isCurrent: forecastState.forecasts[index] == + forecastState.currentForecast, hasPassed: forecastState - .chabanBridgeForecasts[index].circulationReOpeningDate + .forecasts[index].circulationReOpeningDate .isBefore(DateTime.now()), - chabanBridgeForecast: - forecastState.chabanBridgeForecasts[index], + forecast: forecastState.forecasts[index], index: index, - timeSlots: forecastState - .chabanBridgeForecasts[index].interferingTimeSlots, + timeSlots: + forecastState.forecasts[index].interferingTimeSlots, ); }, - itemCount: forecastState.chabanBridgeForecasts.length, + itemCount: forecastState.forecasts.length, controller: BlocProvider.of(context).scrollController, separatorBuilder: (BuildContext context, int index) { - if ((index + 1 <= - forecastState.chabanBridgeForecasts.length && - forecastState.chabanBridgeForecasts[index] - .circulationClosingDate.month != - forecastState.chabanBridgeForecasts[index + 1] + if ((index + 1 <= forecastState.forecasts.length && + forecastState + .forecasts[index].circulationClosingDate.month != + forecastState.forecasts[index + 1] .circulationClosingDate.month)) { return _MonthWidget( - chabanBridgeForecast: - forecastState.chabanBridgeForecasts[index + 1], + forecast: forecastState.forecasts[index + 1], ); } if (((index % 10 == 0 || index == - forecastState.chabanBridgeForecasts.indexOf( - forecastState.currentChabanBridgeForecast!, + forecastState.forecasts.indexOf( + forecastState.currentForecast!, )) && index != 0) && !kIsWeb) { @@ -97,10 +94,9 @@ class _ForecastListWidgetState extends State { } class _MonthWidget extends StatelessWidget { - final AbstractChabanBridgeForecast chabanBridgeForecast; + final AbstractForecast forecast; - const _MonthWidget({Key? key, required this.chabanBridgeForecast}) - : super(key: key); + const _MonthWidget({Key? key, required this.forecast}) : super(key: key); @override Widget build(BuildContext context) { @@ -115,7 +111,7 @@ class _MonthWidget extends StatelessWidget { child: Text( DateFormat.MMMM(Localizations.localeOf(context).languageCode) .format( - chabanBridgeForecast.circulationClosingDate, + forecast.circulationClosingDate, ), style: const TextStyle(fontWeight: FontWeight.bold), ), diff --git a/lib/widgets/forecast/status_widget.dart b/lib/widgets/forecast/status_widget.dart index ed56c234..a58451c7 100644 --- a/lib/widgets/forecast/status_widget.dart +++ b/lib/widgets/forecast/status_widget.dart @@ -1,7 +1,7 @@ import 'dart:async'; -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/bloc/status/status_bloc.dart'; import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/extensions/duration_extension.dart'; @@ -29,8 +29,8 @@ class StatusWidgetState extends CustomWidgetState { (_) { Timer.periodic( const Duration(seconds: 1), - (Timer t) => BlocProvider.of(context).add( - ChabanBridgeStatusRefresh( + (Timer t) => BlocProvider.of(context).add( + StatusRefresh( context: context, ), ), @@ -42,7 +42,7 @@ class StatusWidgetState extends CustomWidgetState { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { return AnimatedSize( curve: Curves.ease, @@ -56,8 +56,7 @@ class StatusWidgetState extends CustomWidgetState { child: child, ); }, - child: state.chabanBridgeStatusLifecycle == - ChabanBridgeStatusLifecycle.empty + child: state.statusLifecycle == StatusLifecycle.empty ? Padding( padding: EdgeInsets.symmetric( vertical: MediaQuery.of(context).size.height / 5, @@ -188,8 +187,7 @@ class StatusWidgetState extends CustomWidgetState { ), hasPassed: false, isCurrent: true, - chabanBridgeForecast: - state.currentTarget!, + forecast: state.currentTarget!, index: -1, timeSlots: const [], ), diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index 06420d84..57995472 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=1.3.2 -FLUTTER_BUILD_NUMBER=1.3.2 +FLUTTER_BUILD_NAME=1.6.0 +FLUTTER_BUILD_NUMBER=1.6.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 d29ae3eb..eb328ef2 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=1.3.2" -export "FLUTTER_BUILD_NUMBER=1.3.2" +export "FLUTTER_BUILD_NAME=1.6.0" +export "FLUTTER_BUILD_NUMBER=1.6.0" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" From 0e68fb464c78a39c0f836f205f5f3540eac30067 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:43:48 +0200 Subject: [PATCH 29/34] test --- lib/widgets/ad_banner_widget.dart | 90 ++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/lib/widgets/ad_banner_widget.dart b/lib/widgets/ad_banner_widget.dart index 3822e6da..06ed7449 100644 --- a/lib/widgets/ad_banner_widget.dart +++ b/lib/widgets/ad_banner_widget.dart @@ -15,6 +15,7 @@ class AdBannerWidget extends StatefulWidget { class _AdBannerWidgetState extends State { late NativeAd _bannerAd; + late BannerAd _bannerAd2; Ad? _ad; void _createBannerAd() { @@ -42,9 +43,34 @@ class _AdBannerWidgetState extends State { _bannerAd.load(); } + void _createBannerAd2() { + _bannerAd2 = BannerAd( + adUnitId: AdHelper.inlineBannerAdUnitId(), + size: AdSize.banner, + request: const AdRequest(), + listener: BannerAdListener( + onAdLoaded: (ad) { + setState( + () { + _ad = ad; + }, + ); + }, + onAdFailedToLoad: (ad, error) { + developer.log( + 'Enable to load the ad : ${error.message}', + name: 'banner-widget', + ); + _ad?.dispose(); + }, + ), + ); + _bannerAd2.load(); + } + @override void initState() { - _createBannerAd(); + _createBannerAd2(); super.initState(); } @@ -56,29 +82,45 @@ class _AdBannerWidgetState extends State { @override Widget build(BuildContext context) { - return _ad != null - ? Card( - child: Container( - key: const ValueKey('adContent'), - height: 55, - alignment: Alignment.center, - child: AnimatedSize( - curve: Curves.ease, - duration: const Duration(seconds: 1), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 500), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: AdWidget(ad: _bannerAd), - ), - ), + return AnimatedSwitcher( + duration: 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(-1.0, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: child, + ), + ); + }, + child: _ad != null + ? Card( + child: Container( + key: const ValueKey('adContent'), + height: 55, + alignment: Alignment.center, + child: AnimatedSize( + curve: Curves.ease, + duration: const Duration(seconds: 1), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 500), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: AdWidget(ad: _bannerAd2), ), - ) - : const SizedBox.shrink(); + ), + ), + ) + : const SizedBox.shrink(), + ); } } From 13e4ef78c1a13429366e4f54152280ce72f600f5 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:44:03 +0200 Subject: [PATCH 30/34] Revert "test" This reverts commit 0e68fb464c78a39c0f836f205f5f3540eac30067. --- lib/widgets/ad_banner_widget.dart | 90 +++++++++---------------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/lib/widgets/ad_banner_widget.dart b/lib/widgets/ad_banner_widget.dart index 06ed7449..3822e6da 100644 --- a/lib/widgets/ad_banner_widget.dart +++ b/lib/widgets/ad_banner_widget.dart @@ -15,7 +15,6 @@ class AdBannerWidget extends StatefulWidget { class _AdBannerWidgetState extends State { late NativeAd _bannerAd; - late BannerAd _bannerAd2; Ad? _ad; void _createBannerAd() { @@ -43,34 +42,9 @@ class _AdBannerWidgetState extends State { _bannerAd.load(); } - void _createBannerAd2() { - _bannerAd2 = BannerAd( - adUnitId: AdHelper.inlineBannerAdUnitId(), - size: AdSize.banner, - request: const AdRequest(), - listener: BannerAdListener( - onAdLoaded: (ad) { - setState( - () { - _ad = ad; - }, - ); - }, - onAdFailedToLoad: (ad, error) { - developer.log( - 'Enable to load the ad : ${error.message}', - name: 'banner-widget', - ); - _ad?.dispose(); - }, - ), - ); - _bannerAd2.load(); - } - @override void initState() { - _createBannerAd2(); + _createBannerAd(); super.initState(); } @@ -82,45 +56,29 @@ class _AdBannerWidgetState extends State { @override Widget build(BuildContext context) { - return AnimatedSwitcher( - duration: 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(-1.0, 0.0), - end: const Offset(0.0, 0.0), - ).animate(animation), - child: child, - ), - ); - }, - child: _ad != null - ? Card( - child: Container( - key: const ValueKey('adContent'), - height: 55, - alignment: Alignment.center, - child: AnimatedSize( - curve: Curves.ease, - duration: const Duration(seconds: 1), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 500), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: AdWidget(ad: _bannerAd2), + return _ad != null + ? Card( + child: Container( + key: const ValueKey('adContent'), + height: 55, + alignment: Alignment.center, + child: AnimatedSize( + curve: Curves.ease, + duration: const Duration(seconds: 1), + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration(milliseconds: 500), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: AdWidget(ad: _bannerAd), + ), + ), ), - ), - ), - ) - : const SizedBox.shrink(), - ); + ) + : const SizedBox.shrink(); } } From 37961fc8e5fba1aac9d5c64f17c658d24f978bc7 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:48:41 +0200 Subject: [PATCH 31/34] feat(ad): display only one ad --- lib/screens/forecast_screen.dart | 18 +++++++++++++++++- .../floating_actions_widget.dart | 6 ++++-- lib/widgets/forecast/forecast_list_widget.dart | 11 ----------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/screens/forecast_screen.dart b/lib/screens/forecast_screen.dart index 8de35462..e4ddb0da 100644 --- a/lib/screens/forecast_screen.dart +++ b/lib/screens/forecast_screen.dart @@ -6,6 +6,7 @@ import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_widget_state.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; +import 'package:chabo/widgets/ad_banner_widget.dart'; import 'package:chabo/widgets/floating_actions/floating_actions_widget.dart'; import 'package:chabo/widgets/forecast/forecast_list_widget.dart'; import 'package:chabo/widgets/forecast/status_widget.dart'; @@ -29,7 +30,22 @@ class _ForecastScreenState extends CustomWidgetState { return BlocBuilder( builder: (context, state) { return Scaffold( - floatingActionButton: const FloatingActionsWidget(), + floatingActionButton: Column( + crossAxisAlignment: state.isRightHanded + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + const Expanded(child: FloatingActionsWidget()), + Padding( + padding: EdgeInsets.only( + left: state.isRightHanded ? 32 : 0, + right: state.isRightHanded ? 0 : 32, + top: 15, + ), + child: const AdBannerWidget(), + ), + ], + ), floatingActionButtonLocation: state.isRightHanded ? FloatingActionButtonLocation.endFloat : FloatingActionButtonLocation.startFloat, diff --git a/lib/widgets/floating_actions/floating_actions_widget.dart b/lib/widgets/floating_actions/floating_actions_widget.dart index b24d2271..a89555e4 100644 --- a/lib/widgets/floating_actions/floating_actions_widget.dart +++ b/lib/widgets/floating_actions/floating_actions_widget.dart @@ -41,8 +41,10 @@ class _FloatingActionsWidgetState extends State ), transitionBuilder: (Widget child, Animation animation) { return FadeTransition( - opacity: - CurvedAnimation(parent: animation, curve: Curves.easeIn), + opacity: CurvedAnimation( + parent: animation, + curve: Curves.easeIn, + ), child: SlideTransition( position: Tween( begin: const Offset(0.0, 1.0), diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index ba789e1c..de75cdc4 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -2,9 +2,7 @@ import 'package:chabo/bloc/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/models/abstract_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'; @@ -72,15 +70,6 @@ class _ForecastListWidgetState extends State { forecast: forecastState.forecasts[index + 1], ); } - if (((index % 10 == 0 || - index == - forecastState.forecasts.indexOf( - forecastState.currentForecast!, - )) && - index != 0) && - !kIsWeb) { - return const AdBannerWidget(); - } return const SizedBox.shrink(); }, From 3f209868c21f5fa264367bc2c295e404cbfed936 Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:52:18 +0200 Subject: [PATCH 32/34] feat(ui): add a bottom padding to make the last forecast visible --- lib/widgets/forecast/forecast_list_widget.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index de75cdc4..d825ec22 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -37,8 +37,13 @@ class _ForecastListWidgetState extends State { builder: (context, timeSlotState) { return ListView.separated( cacheExtent: 5000, - padding: const EdgeInsets.all(0), - itemBuilder: (BuildContext context, int index) { + padding: const EdgeInsets.symmetric(horizontal: 5).copyWith( + bottom: 200, + ), + itemBuilder: ( + BuildContext context, + int index, + ) { forecastState.forecasts[index] .computeSlotInterference(timeSlotState.timeSlotsValue); From 84c72297033a7b0a5890c27fba3ada658ffc6a2d Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 21:56:20 +0200 Subject: [PATCH 33/34] feat(ui): reduce the forecast_list_item_widget.dart height --- lib/widgets/forecast/forecast_list_item_widget.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart index 7e8ecb08..7643782c 100644 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ b/lib/widgets/forecast/forecast_list_item_widget.dart @@ -88,13 +88,13 @@ class ForecastListItemWidget extends StatelessWidget { ), }, child: SizedBox( - height: 65, + height: 60, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 55, - height: 65, + height: 60, decoration: BoxDecoration( borderRadius: const BorderRadius.only( topLeft: Radius.circular( @@ -201,7 +201,7 @@ class ForecastListItemWidget extends StatelessWidget { timeSlots.isNotEmpty ? Container( width: 30, - height: 65, + height: 60, decoration: BoxDecoration( borderRadius: const BorderRadius.only( topRight: Radius.circular( From 6f6957f756235ad94ed97d79b7a1aeb4401fd56a Mon Sep 17 00:00:00 2001 From: Valentin REVERSAT Date: Tue, 9 May 2023 22:53:26 +0200 Subject: [PATCH 34/34] chore(version): increment version to `v1.6.1` --- CHANGELOG_en.md | 8 ++++++++ CHANGELOG_es.md | 8 ++++++++ CHANGELOG_fr.md | 8 ++++++++ pubspec.yaml | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index 13b23163..c22a47a6 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,11 @@ +# **v1.6.1** : + +- *Fix*: + - The SnackBar warning of activation of notifications on slots is now correctly in place +- *Interface*: + - Adding a padding to display the last item in the closures list + - Only one ad is now displayed +*** # **v1.6.0** : - *Features*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index 03814b50..fe6d1750 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,11 @@ +# **v1.6.1** : + +- *Fix*: + - El snackbar de advertencia de la activación de las notificaciones en las ranuras está ahora correctamente en su lugar +- *Interfaz*: + - Añadir un relleno para mostrar el último elemento de la lista de cierres + - Ahora solo se muestra un anuncio +*** # **v1.6.0** : - *Funcionalidades*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 0025b1c2..2fd68345 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,11 @@ +# **v1.6.1** : + +- *Fix*: + - La SnackBar d'avertissement de l'activation des notifications sur les créneaux est maintenant correctement en place +- *Interface*: + - Ajout d'un padding pour afficher le dernier élément de la liste des fermetures + - Seulement une seule publicité est maintenant affichée +*** # **v1.6.0** : - *Fonctionnalités*: diff --git a/pubspec.yaml b/pubspec.yaml index 903b5ee9..6dc9baa8 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.6.0 +version: 1.6.1 environment: sdk: '>=2.17.6 <3.0.0'