Skip to content
Merged
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
67 changes: 45 additions & 22 deletions lib/data/repository/authentication_repository.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@

import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:supabase_flutter/supabase_flutter.dart' hide User;

import '../../core/constants/app_constants.dart';
import '../../core/errors/error_model.dart';
import '../../core/errors/result.dart';
import '../../core/errors/supabase_auth_error.dart';
import '../../domain/repository/authentication_repository.dart';
import '../../domain/entity/user.dart';
import '../../domain/repository/authentication_repository.dart';
import '../service/app_secrets_provider.dart';
import '../service/supabase_service.dart';

Expand All @@ -24,7 +22,7 @@ class AuthenticationRepositoryImpl implements AuthenticationRepository {
});

@override
void signInWithGoogle() async {
Future<Result<bool>> signInWithGoogle() async {
try {
final env = await appSecrets.getEnvVariables();
final GoogleSignIn signIn = GoogleSignIn.instance;
Expand All @@ -43,25 +41,23 @@ class AuthenticationRepositoryImpl implements AuthenticationRepository {
final accessToken = googleAuthorization?.accessToken;

if (idToken == null) {
throw 'No ID Token found.';
return Result.error(ErrorModel('No ID Token found from Google.'));
}
final client = await supabaseService.getClient();
await client.auth.signInWithIdToken(
provider: OAuthProvider.google,
idToken: idToken,
accessToken: accessToken,
);
return Result.success(true);
} on AuthException catch (error) {
return Result.error(SupabaseAuthError.fromAuthException(error));
} catch (error) {
if (kDebugMode) {
print('Caught error during Google Sign-In: $error');
}
rethrow;
log('error in data $error');
return Result.error(ErrorModel(error.toString()));
}
}

static const String _googleWebClientId = "GOOGLE_WEB_CLIENT_ID";
static const String _googleIosClientId = "GOOGLE_IOS_CLIENT_ID";
static const List<String> _googleScopes = ['email', 'profile', 'openid'];
@override
Stream<AuthState> get onAuthStateChange {
final supabaseClientFuture = supabaseService.getClient();
Expand All @@ -71,17 +67,34 @@ class AuthenticationRepositoryImpl implements AuthenticationRepository {
}

@override
Future<void> resetPasswordForEmail(String email) async {
final client = await supabaseService.getClient();
await client.auth.resetPasswordForEmail(
email,
redirectTo: AppConstants.resetPasswordRedirect,
);
Future<Result<bool>> resetPasswordForEmail(String email) async {
try {
final client = await supabaseService.getClient();
await client.auth.resetPasswordForEmail(
email,
redirectTo: AppConstants.resetPasswordRedirect,
);
return Result.success(true);
} on AuthException catch (error) {
return Result.error(SupabaseAuthError.fromAuthException(error));
} catch (error) {
log('error in data $error');
return Result.error(ErrorModel(error.toString()));
}
}

@override
Future<void> updatePassword(String password) async {
final client = await supabaseService.getClient();
await client.auth.updateUser(UserAttributes(password: password));
Future<Result<bool>> updatePassword(String password) async {
try {
final client = await supabaseService.getClient();
await client.auth.updateUser(UserAttributes(password: password));
return Result.success(true);
} on AuthException catch (error) {
return Result.error(SupabaseAuthError.fromAuthException(error));
} catch (error) {
log('error in data $error');
return Result.error(ErrorModel(error.toString()));
}
}

@override
Expand Down Expand Up @@ -111,4 +124,14 @@ class AuthenticationRepositoryImpl implements AuthenticationRepository {
return Result.error(ErrorModel(error.toString()));
}
}
}

static const String _googleWebClientId = "GOOGLE_WEB_CLIENT_ID";
static const String _googleIosClientId = "GOOGLE_IOS_CLIENT_ID";
static const List<String> _googleScopes = ['email', 'profile', 'openid'];

@override
Future<String?> get userEmail async {
final supabaseClientFuture = await supabaseService.getClient();
return supabaseClientFuture.auth.currentUser?.email;
}
}
18 changes: 12 additions & 6 deletions lib/domain/repository/authentication_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import 'dart:async';
import 'package:supabase_flutter/supabase_flutter.dart' hide User;

import '../../core/errors/result.dart';
import '../../domain/entity/user.dart';
import '../entity/user.dart';

abstract class AuthenticationRepository {
void signInWithGoogle();
Future<void> resetPasswordForEmail(String email);
Future<Result<User>> signIn({
required String email,
required String password,
});

Stream<AuthState> get onAuthStateChange;

Future<void> updatePassword(String password);
Future<String?> get userEmail;

Future<Result<User>> signIn({required String email, required String password});
}
Future<Result<bool>> resetPasswordForEmail(String email);

Future<Result<bool>> signInWithGoogle();

Future<Result<bool>> updatePassword(String password);
}
39 changes: 37 additions & 2 deletions lib/money_app.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,49 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:moneyplus/design_system/theme/money_theme.dart';
import 'package:moneyplus/domain/repository/authentication_repository.dart';
import 'package:moneyplus/presentation/navigation/routes.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

import 'core/di/injection.dart';
import 'core/l10n/app_localizations.dart';


class AuthRedirectNotifier extends ChangeNotifier {
final AuthenticationRepository _authRepository;
late final StreamSubscription<AuthState> _subscription;
bool _isPasswordRecovery = false;

AuthRedirectNotifier(this._authRepository) {
_subscription = _authRepository.onAuthStateChange.listen((data) {
if (data.event == AuthChangeEvent.passwordRecovery) {
_isPasswordRecovery = true;
notifyListeners();
}
});
}

@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
final _authRedirectNotifier = AuthRedirectNotifier(getIt<AuthenticationRepository>());
final _router = GoRouter(
routes: $appRoutes,
initialLocation: '/login'
routes: $appRoutes,
initialLocation: '/login',
refreshListenable: _authRedirectNotifier,
redirect: (context, state) {
if (_authRedirectNotifier._isPasswordRecovery) {
_authRedirectNotifier._isPasswordRecovery = false;
return '/update_password';
}
return null;
},
);


Expand Down
28 changes: 9 additions & 19 deletions lib/presentation/forget_password/cubit/forget_password_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,28 @@ import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:moneyplus/domain/repository/authentication_repository.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:moneyplus/domain/validator/authentication_validator.dart';

import 'forget_password_state.dart';

class ForgetPasswordCubit extends Cubit<ForgetPasswordState> {
final AuthenticationRepository authenticationRepository;
late final StreamSubscription<AuthState> _authSubscription;
final AuthenticationValidator validator;
ForgetPasswordCubit(this.authenticationRepository, this.validator)
: super(const ForgetPasswordState());

ForgetPasswordCubit(this.authenticationRepository)
: super(ForgetPasswordState.initial()) {
_authSubscription = authenticationRepository.onAuthStateChange.listen((
data,
) {
if (data.event == AuthChangeEvent.passwordRecovery) {
emit(state.copyWith(status: ForgetPasswordStatus.passwordRecovery));
}
});
void onEmailChanged(String email) {
final isEmailValid = validator.isEmailValid(email);
emit(state.copyWith(email: email, isEmailValid: isEmailValid));
}

Future<void> onClickForgetPassword(String email) async {
Future<void> onClickForgetPassword() async {
emit(state.copyWith(status: ForgetPasswordStatus.loading));
try {
await authenticationRepository.resetPasswordForEmail(email);
await authenticationRepository.resetPasswordForEmail(state.email);
emit(state.copyWith(status: ForgetPasswordStatus.success));
} catch (e) {
emit(state.copyWith(status: ForgetPasswordStatus.error));
}
}

@override
Future<void> close() {
_authSubscription.cancel();
return super.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ enum ForgetPasswordStatus { initial, loading, success, error, passwordRecovery }
class ForgetPasswordState {
const ForgetPasswordState({
this.status = ForgetPasswordStatus.initial,
this.email = '',
this.isEmailValid = false,
});

final ForgetPasswordStatus status;
final String email;
final bool isEmailValid;

ForgetPasswordState copyWith({
ForgetPasswordStatus? status,
String? email,
bool? isEmailValid,
}) {
return ForgetPasswordState(
status: status ?? this.status,
email: email ?? this.email,
isEmailValid: isEmailValid ?? this.isEmailValid,
);
}

factory ForgetPasswordState.initial() => const ForgetPasswordState();
}
37 changes: 10 additions & 27 deletions lib/presentation/forget_password/screen/forget_password_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:moneyplus/design_system/widgets/app_logo.dart';
import 'package:moneyplus/design_system/widgets/text_field.dart';
import 'package:moneyplus/domain/repository/authentication_repository.dart';
import 'package:moneyplus/presentation/forget_password/cubit/forget_password_cubit.dart';
import 'package:moneyplus/presentation/update_password/screen/update_password_screen.dart';
import 'package:svg_flutter/svg.dart';

import '../../../core/di/injection.dart';
Expand All @@ -21,38 +20,24 @@ class ForgetPasswordScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ForgetPasswordCubit(getIt<AuthenticationRepository>()),
create: (_) =>
ForgetPasswordCubit(getIt<AuthenticationRepository>(), getIt()),
child: const _ForgetPasswordView(),
);
}
}

class _ForgetPasswordView extends StatefulWidget {
class _ForgetPasswordView extends StatelessWidget {
const _ForgetPasswordView();

@override
State<_ForgetPasswordView> createState() => _ForgetPasswordViewState();
}

class _ForgetPasswordViewState extends State<_ForgetPasswordView> {
String _email = '';

@override
Widget build(BuildContext context) {
final colors = context.colors;
final typography = context.typography;
final l10n = AppLocalizations.of(context)!;

return BlocConsumer<ForgetPasswordCubit, ForgetPasswordState>(
listener: (context, state) {
if (state.status == ForgetPasswordStatus.passwordRecovery) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => UpdatePasswordScreen(email: _email),
),
);
}
},
listener: (context, state) {},
builder: (context, state) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
Expand All @@ -67,12 +52,10 @@ class _ForgetPasswordViewState extends State<_ForgetPasswordView> {
padding: const EdgeInsets.all(16),
child: DefaultButton(
text: l10n.forgetPasswordButton,
isEnabled: _email.isNotEmpty,
isEnabled: state.isEmailValid,
isLoading: state.status == ForgetPasswordStatus.loading,
onPressed: () {
context.read<ForgetPasswordCubit>().onClickForgetPassword(
_email,
);
context.read<ForgetPasswordCubit>().onClickForgetPassword();
},
),
),
Expand Down Expand Up @@ -125,11 +108,11 @@ class _ForgetPasswordViewState extends State<_ForgetPasswordView> {
height: 24,
AppAssets.icEmail,
),
value: _email,
value: state.email,
onChanged: (String value) {
setState(() {
_email = value;
});
context.read<ForgetPasswordCubit>().onEmailChanged(
value,
);
},
),
],
Expand Down
17 changes: 16 additions & 1 deletion lib/presentation/login/cubit/login_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class LoginCubit extends Cubit<LoginState> {
checkIsInputsValid();
}

Future<void> login() async {
void login() async {
emit(state.copyWith(status: LoginStatus.loading));

final result = await authRepository.signIn(
Expand All @@ -44,4 +44,19 @@ class LoginCubit extends Cubit<LoginState> {
},
);
}

void signInWithGoogle() async {
emit(state.copyWith(status: LoginStatus.loading));

final result = await authRepository.signInWithGoogle();

result.when(
onSuccess: (success) {
emit(state.copyWith(status: LoginStatus.success));
},
onError: (error) {
emit(state.copyWith(status: LoginStatus.failure, error: error));
},
);
}
}
4 changes: 3 additions & 1 deletion lib/presentation/login/screen/login_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,9 @@ class _SocialMediaButtons extends StatelessWidget {
return Column(
children: [
MoneyButton(
onPressed: () {},
onPressed: () {
context.read<LoginCubit>().signInWithGoogle();
},
backgroundColor: colors.surfaceLow,
disabledBackgroundColor: Colors.red,
borderWidth: 0.5,
Expand Down
Loading