diff --git a/README.md b/README.md index 32d7b6f..096dc79 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,6 @@ For the purpose of Fraud prevention, user safety, and compliance the dedicated A # Todos - Fix Riverpod async gaps - analytics manager (keep live) - Revisit Google and Apple logins - Providers, Cancellation exception, separating Credentials from Sign In. USe Firebase directly for Apple on Android. -- Refactor Sealed classes - private classes, use generated `when` function instead of switch. diff --git a/lib/common/data/entity/exception/custom_exception.dart b/lib/common/data/entity/exception/custom_exception.dart index ffc1d56..da99201 100644 --- a/lib/common/data/entity/exception/custom_exception.dart +++ b/lib/common/data/entity/exception/custom_exception.dart @@ -16,15 +16,15 @@ part 'custom_exception.freezed.dart'; sealed class CustomException with _$CustomException implements Exception { const CustomException._(); - const factory CustomException.general() = CustomExceptionGeneral; - const factory CustomException.withMessage({String? message}) = CustomExceptionWithMessage; - const factory CustomException.unauthenticated() = CustomExceptionUnauthenticated; - const factory CustomException.notConnectedToTheInternet() = CustomExceptionNotConnectedToTheInternet; - const factory CustomException.decodingFailed() = CustomExceptionDecodingFailed; + const factory CustomException.general() = _General; + const factory CustomException.withMessage({String? message}) = _WithMessage; + const factory CustomException.unauthenticated() = _Unauthenticated; + const factory CustomException.notConnectedToTheInternet() = _NotConnectedToTheInternet; + const factory CustomException.decodingFailed() = _DecodingFailed; // Note: Mapped Firebase exception with error code `credential-already-in-use`. - const factory CustomException.signInCancelled() = CustomExceptionSignInCancelled; - const factory CustomException.credentialAlreadyInUse({required AuthCredential? credential}) = CustomExceptionCredentialAlreadyInUse; + const factory CustomException.signInCancelled() = _SignInCancelled; + const factory CustomException.credentialAlreadyInUse({required AuthCredential? credential}) = _CredentialAlreadyInUse; factory CustomException.fromErrorObject({required Object? error}) { Flogger.e('[CustomException] Received error $error, '); @@ -78,21 +78,17 @@ sealed class CustomException with _$CustomException implements Exception { } } - String getMessage({required BuildContext context}) { - return switch (this) { - CustomExceptionWithMessage(message: final message) => message ?? context.locale.customExceptionGeneralMessage, - CustomExceptionUnauthenticated() => context.locale.customExceptionUnauthenticatedMessage, - CustomExceptionNotConnectedToTheInternet() => context.locale.customExceptionInternetConnectionMessage, - _ => context.locale.customExceptionGeneralMessage, - }; - } + String getMessage({required BuildContext context}) => maybeWhen( + withMessage: (message) => message ?? context.locale.customExceptionGeneralMessage, + unauthenticated: () => context.locale.customExceptionUnauthenticatedMessage, + notConnectedToTheInternet: () => context.locale.customExceptionInternetConnectionMessage, + orElse: () => context.locale.customExceptionGeneralMessage, + ); - String getDetails({required BuildContext context}) { - return switch (this) { - CustomExceptionNotConnectedToTheInternet() => context.locale.customExceptionInternetConnectionDetails, - _ => context.locale.customExceptionGeneralDetails, - }; - } + String getDetails({required BuildContext context}) => maybeWhen( + notConnectedToTheInternet: () => context.locale.customExceptionInternetConnectionDetails, + orElse: () => context.locale.customExceptionGeneralDetails, + ); Future showErrorSnackbar({ required BuildContext context, diff --git a/lib/common/data/entity/exception/validator_exception.dart b/lib/common/data/entity/exception/validator_exception.dart index a33e1d7..2fe54cd 100644 --- a/lib/common/data/entity/exception/validator_exception.dart +++ b/lib/common/data/entity/exception/validator_exception.dart @@ -7,17 +7,10 @@ part 'validator_exception.freezed.dart'; sealed class ValidatorException with _$ValidatorException implements Exception { const ValidatorException._(); - const factory ValidatorException.generalIsEmpty(String Function(BuildContext) getText) = ValidatorExceptionGeneralIsEmpty; - const factory ValidatorException.generalIsTooShort(String Function(BuildContext) getText) = ValidatorExceptionGeneralIsTooShort; - const factory ValidatorException.generalIsTooLong(String Function(BuildContext) getText) = ValidatorExceptionGeneralIsTooLong; - const factory ValidatorException.generalIsInvalid(String Function(BuildContext) getText) = ValidatorExceptionGeneralIsInvalid; + const factory ValidatorException.generalIsEmpty(String Function(BuildContext) getText) = _GeneralIsEmpty; + const factory ValidatorException.generalIsTooShort(String Function(BuildContext) getText) = _GeneralIsTooShort; + const factory ValidatorException.generalIsTooLong(String Function(BuildContext) getText) = _GeneralIsTooLong; + const factory ValidatorException.generalIsInvalid(String Function(BuildContext) getText) = _GeneralIsInvalid; - String getMessage({required BuildContext context}) { - return switch (this) { - ValidatorExceptionGeneralIsEmpty(getText: final getText) => getText(context), - ValidatorExceptionGeneralIsTooShort(getText: final getText) => getText(context), - ValidatorExceptionGeneralIsTooLong(getText: final getText) => getText(context), - ValidatorExceptionGeneralIsInvalid(getText: final getText) => getText(context), - }; - } + String getMessage({required BuildContext context}) => getText(context); } diff --git a/lib/common/data/entity/notification_payload_entity.dart b/lib/common/data/entity/notification_payload_entity.dart index 5d4843f..9497d7c 100644 --- a/lib/common/data/entity/notification_payload_entity.dart +++ b/lib/common/data/entity/notification_payload_entity.dart @@ -16,7 +16,7 @@ enum NotificationType { NotificationType.values.firstWhereOrNull((e) => e.value == value) ?? NotificationType.unknown; } -@Freezed(fromJson: true, toJson: true) +@Freezed(fromJson: true) sealed class NotificationPayloadEntity with _$NotificationPayloadEntity { const NotificationPayloadEntity._(); @@ -25,24 +25,18 @@ sealed class NotificationPayloadEntity with _$NotificationPayloadEntity { required int id, required String title, required String body, - @Default(NotificationType.sample) NotificationType type, - }) = NotificationPayloadEntitySample; + }) = _Sample; - // Subtitle: unknown - const factory NotificationPayloadEntity.unknown({ - @Default(-1) int id, - @Default('') String title, - @Default('') String body, - @Default(NotificationType.unknown) NotificationType type, - }) = NotificationPayloadEntityUnknown; + // Title: Unknown + const factory NotificationPayloadEntity.unknown() = _Unknown; factory NotificationPayloadEntity.fromJson(Map json) { switch (NotificationType.fromString(json['type'] as String?)) { case NotificationType.sample: - return NotificationPayloadEntitySample.fromJson(json); + return _Sample.fromJson(json); case NotificationType.unknown: - return const NotificationPayloadEntityUnknown(); + return const _Unknown(); } } } diff --git a/lib/common/provider/notifications_service.dart b/lib/common/provider/notifications_service.dart index 489b486..40490d9 100644 --- a/lib/common/provider/notifications_service.dart +++ b/lib/common/provider/notifications_service.dart @@ -53,14 +53,15 @@ class NotificationsService extends _$NotificationsService { } static void handleNotificationOpen(NotificationPayloadEntity notification) { - switch (notification) { - case NotificationPayloadEntitySample(): + notification.when( + sample: (id, title, body) { Flogger.d('[Notifications] Handle open of Sample notification'); - // TODO(strv): [Notifications] Handle Notification open action here - - case NotificationPayloadEntityUnknown(): - // Do nothing - } + // TODO(strv): [Notifications] Handle Notification open action here + }, + unknown: () { + // Do nothing + }, + ); } static Future handleAppOpenNotification() async { @@ -74,14 +75,18 @@ class NotificationsService extends _$NotificationsService { } static Future showNotification(NotificationPayloadEntity notification) async { - if (notification is NotificationPayloadEntityUnknown) return; - Flogger.i('[Notifications] New local notification to display: $notification'); + final notificationData = notification.when(sample: (id, title, body) => (id: id, title: title, body: body), unknown: () => null); + + if (notificationData == null) { + return; + } + await _flutterLocalNotifications.cancelAll(); await _flutterLocalNotifications.show( - notification.id, - notification.title, - notification.body, + notificationData.id, + notificationData.title, + notificationData.body, defaultNotificationDetails, payload: jsonEncode(notification), ); diff --git a/lib/common/usecase/authentication/sign_in_with_auth_credential_use_case.dart b/lib/common/usecase/authentication/sign_in_with_auth_credential_use_case.dart index e32b97c..8af7b71 100644 --- a/lib/common/usecase/authentication/sign_in_with_auth_credential_use_case.dart +++ b/lib/common/usecase/authentication/sign_in_with_auth_credential_use_case.dart @@ -26,10 +26,9 @@ FutureOr signInWithAuthCredentialUseCase( Flogger.d('[Authentication] Anonymous user was linked with google credential'); } on Exception catch (error) { final customException = CustomException.fromErrorObject(error: error); - final credentialIsAlreadyInUse = switch (customException) { - CustomExceptionCredentialAlreadyInUse(credential: final credential) => credential, - _ => null, - }; + final credentialIsAlreadyInUse = customException.whenOrNull( + credentialAlreadyInUse: (credential) => credential, + ); if (credentialIsAlreadyInUse != null) { await FirebaseAuth.instance.signInWithCredential(credentialIsAlreadyInUse); diff --git a/lib/common/validator/controller/text_validator_controller_general.dart b/lib/common/validator/controller/text_validator_controller_general.dart index b864f29..9819097 100644 --- a/lib/common/validator/controller/text_validator_controller_general.dart +++ b/lib/common/validator/controller/text_validator_controller_general.dart @@ -64,7 +64,5 @@ class TextValidatorControllerGeneral extends TextValidatorController { notifyListeners(); } - bool get isValid => _state is TextFieldValidatorStateValid; - - bool get isInvalid => _state is TextFieldValidatorStateInvalid; + bool get isValid => _state.isValid; } diff --git a/lib/common/validator/text_field_validator_state.dart b/lib/common/validator/text_field_validator_state.dart index fa2da15..c106130 100644 --- a/lib/common/validator/text_field_validator_state.dart +++ b/lib/common/validator/text_field_validator_state.dart @@ -8,28 +8,19 @@ part 'text_field_validator_state.freezed.dart'; sealed class TextFieldValidatorState with _$TextFieldValidatorState { const TextFieldValidatorState._(); - const factory TextFieldValidatorState.initial() = TextFieldValidatorStateInitial; - const factory TextFieldValidatorState.valid() = TextFieldValidatorStateValid; - const factory TextFieldValidatorState.invalid({required ValidatorException exception}) = TextFieldValidatorStateInvalid; + const factory TextFieldValidatorState.initial() = _Initial; + const factory TextFieldValidatorState.valid() = _Valid; + const factory TextFieldValidatorState.invalid({required ValidatorException exception}) = _Invalid; - bool get isValid { - return switch (this) { - TextFieldValidatorStateValid() => true, - _ => false, - }; - } + bool get isValid => maybeWhen( + valid: () => true, + orElse: () => false, + ); - bool get hasError { - return switch (this) { - TextFieldValidatorStateInvalid() => true, - _ => false, - }; - } + bool get hasError => maybeWhen( + invalid: (_) => true, + orElse: () => false, + ); - String? getErrorMessage(BuildContext context) { - return switch (this) { - TextFieldValidatorStateInvalid(exception: final exception) => exception.getMessage(context: context), - _ => null, - }; - } + String? getErrorMessage(BuildContext context) => whenOrNull(invalid: (exception) => exception.getMessage(context: context)); } diff --git a/lib/core/analytics/analytics_material_page.dart b/lib/core/analytics/analytics_material_page.dart deleted file mode 100644 index da442bf..0000000 --- a/lib/core/analytics/analytics_material_page.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_app/core/analytics/analytics_screen_view.dart'; -import 'package:flutter_app/core/analytics/has_analytics_screen_view_mixin.dart'; - -class AnalyticsMaterialPage extends MaterialPage with HasAnalyticsScreenViewMixin { - const AnalyticsMaterialPage({ - required this.event, - required super.child, - super.maintainState = true, - super.fullscreenDialog = false, - super.allowSnapshotting = true, - super.key, - super.name, - super.arguments, - super.restorationId, - }); - - @override - final AnalyticsScreenView event; -} diff --git a/lib/core/analytics/analytics_screen_view.dart b/lib/core/analytics/analytics_screen_view.dart deleted file mode 100644 index 2c5567d..0000000 --- a/lib/core/analytics/analytics_screen_view.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'analytics_screen_view.freezed.dart'; - -@freezed -sealed class AnalyticsScreenView with _$AnalyticsScreenView { - factory AnalyticsScreenView({ - required String firebaseScreenName, - }) = _AnalyticsScreenView; - - factory AnalyticsScreenView.homePage() => AnalyticsScreenView(firebaseScreenName: 'home_page_screen'); - factory AnalyticsScreenView.eventsPage() => AnalyticsScreenView(firebaseScreenName: 'events_page_screen'); - factory AnalyticsScreenView.favoritesPage() => AnalyticsScreenView(firebaseScreenName: 'favorites_page_screen'); - factory AnalyticsScreenView.eventDetailPage() => AnalyticsScreenView(firebaseScreenName: 'event_detail_page_screen'); -} diff --git a/lib/core/analytics/has_analytics_screen_view_mixin.dart b/lib/core/analytics/has_analytics_screen_view_mixin.dart deleted file mode 100644 index 9aa4978..0000000 --- a/lib/core/analytics/has_analytics_screen_view_mixin.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_app/core/analytics/analytics_screen_view.dart'; - -mixin HasAnalyticsScreenViewMixin { - AnalyticsScreenView get event; -} diff --git a/lib/features/authentication/authentication_event.dart b/lib/features/authentication/authentication_event.dart index fb68e81..668f359 100644 --- a/lib/features/authentication/authentication_event.dart +++ b/lib/features/authentication/authentication_event.dart @@ -1,3 +1,4 @@ +import 'package:flutter_app/common/data/entity/exception/custom_exception.dart'; import 'package:flutter_app/core/riverpod/event_notifier.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -6,7 +7,8 @@ part 'authentication_event.freezed.dart'; @freezed sealed class AuthenticationEvent with _$AuthenticationEvent { - const factory AuthenticationEvent.signedIn() = AuthenticationEventSignedIn; + const factory AuthenticationEvent.signedIn() = _SignedIn; + const factory AuthenticationEvent.error(CustomException e) = _Error; } final authenticationEventNotifierProvider = NotifierProvider.autoDispose, AuthenticationEvent?>( diff --git a/lib/features/authentication/authentication_page.dart b/lib/features/authentication/authentication_page.dart index db88266..3b31c00 100644 --- a/lib/features/authentication/authentication_page.dart +++ b/lib/features/authentication/authentication_page.dart @@ -14,10 +14,10 @@ class AuthenticationPage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.listen( authenticationEventNotifierProvider, - (_, next) => switch (next) { - AuthenticationEventSignedIn() => context.router.replaceAll([const LandingRoute()]), - _ => () {}, - }, + (_, next) => next?.when( + signedIn: () => context.router.replaceAll([const LandingRoute()]), + error: (error) => error.showErrorSnackbar(context: context), + ), ); return const Scaffold( diff --git a/lib/features/authentication/authentication_state.dart b/lib/features/authentication/authentication_state.dart index 1e529ce..1ec3b00 100644 --- a/lib/features/authentication/authentication_state.dart +++ b/lib/features/authentication/authentication_state.dart @@ -51,11 +51,15 @@ class AuthenticationStateNotifier extends _$AuthenticationStateNotifier with Aut ref.read(authenticationEventNotifierProvider.notifier).send(const AuthenticationEvent.signedIn()); } on Exception catch (error) { final customException = CustomException.fromErrorObject(error: error); - if (customException case CustomExceptionSignInCancelled()) { - Flogger.d('User cancelled the sign in process'); - } else { - Flogger.e('Error while signing in: $customException'); - } + customException.maybeWhen( + signInCancelled: () { + Flogger.d('[Authentication] User cancelled the sign in process'); + }, + orElse: () { + Flogger.e('[Authentication] Error while signing in: $customException'); + ref.read(authenticationEventNotifierProvider.notifier).send(AuthenticationEvent.error(customException)); + }, + ); } setStateData(currentData?.copyWith(isSigningIn: false)); diff --git a/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_content.dart b/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_content.dart index 983dbbb..be350b2 100644 --- a/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_content.dart +++ b/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_content.dart @@ -21,13 +21,13 @@ class DebugToolsWidgetsPageContent extends ConsumerWidget { ref.listen( debugToolsWidgetsPageEventNotifierProvider, - (_, next) => switch (next) { - DebugToolsWidgetsPageEventFieldValidated(message: final message) => CustomSnackbarMessage( + (_, next) => next?.when( + fieldValidated: (message) => CustomSnackbarMessage( context: context, message: message, ).show(), - _ => () {}, - }, + error: (error) => error.showErrorSnackbar(context: context), + ), ); return state.mapContentState( diff --git a/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_event.dart b/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_event.dart index a02f0dc..efa82b3 100644 --- a/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_event.dart +++ b/lib/features/debug_tools/page/widgets/debug_tools_widgets_page_event.dart @@ -1,3 +1,4 @@ +import 'package:flutter_app/common/data/entity/exception/custom_exception.dart'; import 'package:flutter_app/core/riverpod/event_notifier.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -6,7 +7,8 @@ part 'debug_tools_widgets_page_event.freezed.dart'; @freezed sealed class DebugToolsWidgetsPageEvent with _$DebugToolsWidgetsPageEvent { - const factory DebugToolsWidgetsPageEvent.fieldValidated({required String message}) = DebugToolsWidgetsPageEventFieldValidated; + const factory DebugToolsWidgetsPageEvent.fieldValidated({required String message}) = _Validated; + const factory DebugToolsWidgetsPageEvent.error(CustomException e) = _Error; } final debugToolsWidgetsPageEventNotifierProvider = diff --git a/lib/features/profile/profile_event.dart b/lib/features/profile/profile_event.dart index 84d9672..dd3220c 100644 --- a/lib/features/profile/profile_event.dart +++ b/lib/features/profile/profile_event.dart @@ -1,3 +1,4 @@ +import 'package:flutter_app/common/data/entity/exception/custom_exception.dart'; import 'package:flutter_app/core/riverpod/event_notifier.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -6,7 +7,8 @@ part 'profile_event.freezed.dart'; @freezed sealed class ProfileEvent with _$ProfileEvent { - const factory ProfileEvent.signedOut() = ProfileEventSignedOut; + const factory ProfileEvent.signedOut() = _SignedOut; + const factory ProfileEvent.error(CustomException e) = _Error; } final profileEventNotifierProvider = NotifierProvider.autoDispose, ProfileEvent?>( diff --git a/lib/features/profile/profile_page.dart b/lib/features/profile/profile_page.dart index 9432c28..22072be 100644 --- a/lib/features/profile/profile_page.dart +++ b/lib/features/profile/profile_page.dart @@ -14,10 +14,10 @@ class ProfilePage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.listen( profileEventNotifierProvider, - (_, next) => switch (next) { - ProfileEventSignedOut() => context.router.replaceAll([const LandingRoute()]), - _ => () {}, - }, + (_, next) => next?.when( + signedOut: () => context.router.replaceAll([const LandingRoute()]), + error: (error) => error.showErrorSnackbar(context: context), + ), ); return const Scaffold(