Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<!-- ################################################## -->
<!-- ########## Authors ########## -->
Expand Down
38 changes: 17 additions & 21 deletions lib/common/data/entity/exception/custom_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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, ');
Expand Down Expand Up @@ -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<void> showErrorSnackbar({
required BuildContext context,
Expand Down
17 changes: 5 additions & 12 deletions lib/common/data/entity/exception/validator_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
18 changes: 6 additions & 12 deletions lib/common/data/entity/notification_payload_entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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._();

Expand All @@ -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<String, dynamic> 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();
}
}
}
29 changes: 17 additions & 12 deletions lib/common/provider/notifications_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> handleAppOpenNotification() async {
Expand All @@ -74,14 +75,18 @@ class NotificationsService extends _$NotificationsService {
}

static Future<void> 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),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ FutureOr<UserEntity> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
33 changes: 12 additions & 21 deletions lib/common/validator/text_field_validator_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
20 changes: 0 additions & 20 deletions lib/core/analytics/analytics_material_page.dart

This file was deleted.

15 changes: 0 additions & 15 deletions lib/core/analytics/analytics_screen_view.dart

This file was deleted.

5 changes: 0 additions & 5 deletions lib/core/analytics/has_analytics_screen_view_mixin.dart

This file was deleted.

4 changes: 3 additions & 1 deletion lib/features/authentication/authentication_event.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<EventNotifier<AuthenticationEvent?>, AuthenticationEvent?>(
Expand Down
8 changes: 4 additions & 4 deletions lib/features/authentication/authentication_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 9 additions & 5 deletions lib/features/authentication/authentication_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 =
Expand Down
4 changes: 3 additions & 1 deletion lib/features/profile/profile_event.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<EventNotifier<ProfileEvent?>, ProfileEvent?>(
Expand Down
8 changes: 4 additions & 4 deletions lib/features/profile/profile_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down