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
101 changes: 101 additions & 0 deletions lib/core/services/google_authentication_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:flutter/foundation.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:injectable/injectable.dart';
import 'package:on_time_front/core/logging/app_logger.dart';
import 'package:on_time_front/domain/entities/google_auth_credential.dart';

abstract interface class GoogleAuthenticationService {
Stream<GoogleAuthCredential> get authenticationCredentials;

bool get supportsAuthenticate;

Future<void> initialize();

Future<GoogleAuthCredential> authenticate();

Future<void> disconnect();
}

class GoogleAuthenticationCanceledException implements Exception {
const GoogleAuthenticationCanceledException();
}

@Singleton(as: GoogleAuthenticationService)
class GoogleSignInAuthenticationService implements GoogleAuthenticationService {
GoogleSignInAuthenticationService({@ignoreParam GoogleSignIn? googleSignIn})
: _googleSignIn = googleSignIn ?? GoogleSignIn.instance;

static const _googleIosClientId =
'456571312261-r35ah9qi0qaq7al007e2db0e0jmjcmb4.apps.googleusercontent.com';
static const _googleServerClientId =
'456571312261-5kuf2r6i5i7lqjr7qealv06sdgkn3hcp.apps.googleusercontent.com';
static const _googleScopes = ['email', 'profile'];

final GoogleSignIn _googleSignIn;
Future<void>? _initialization;

@override
Stream<GoogleAuthCredential> get authenticationCredentials => _googleSignIn
.authenticationEvents
.where((event) => event is GoogleSignInAuthenticationEventSignIn)
.cast<GoogleSignInAuthenticationEventSignIn>()
.map((event) => _credentialFromAccount(event.user));

@override
bool get supportsAuthenticate => _googleSignIn.supportsAuthenticate();

@override
Future<void> initialize() {
return _initialization ??= _initialize();
}

Future<void> _initialize() async {
await _googleSignIn.initialize(
clientId: _googleClientId,
serverClientId: _googleServerClientId,
);
}

@override
Future<GoogleAuthCredential> authenticate() async {
try {
await initialize();
final account = await _googleSignIn.authenticate(
scopeHint: _googleScopes,
);
return _credentialFromAccount(account);
} on GoogleSignInException catch (error) {
if (error.code == GoogleSignInExceptionCode.canceled) {
throw const GoogleAuthenticationCanceledException();
}
rethrow;
}
}

@override
Future<void> disconnect() async {
try {
await _googleSignIn.disconnect();
AppLogger.debug('Google Sign-In disconnected');
} catch (error) {
AppLogger.debug(
'Google Sign-In disconnect failed errorType=${error.runtimeType}',
);
}
}

GoogleAuthCredential _credentialFromAccount(GoogleSignInAccount account) {
final idToken = account.authentication.idToken;
if (idToken == null) {
throw Exception('Google ID Token is null');
}
return GoogleAuthCredential(idToken: idToken);
}

String? get _googleClientId {
if (kIsWeb) return null;
return defaultTargetPlatform == TargetPlatform.iOS
? _googleIosClientId
: null;
}
}
59 changes: 9 additions & 50 deletions lib/data/repositories/user_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -1,65 +1,34 @@
import 'dart:async';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:injectable/injectable.dart';
import 'package:on_time_front/core/logging/app_logger.dart';
import 'package:on_time_front/core/services/google_authentication_service.dart';
import 'package:on_time_front/core/validation/backend_constraints.dart';
import 'package:on_time_front/data/data_sources/authentication_remote_data_source.dart';
import 'package:on_time_front/data/data_sources/token_local_data_source.dart';
import 'package:on_time_front/data/models/sign_in_with_google_request_model.dart';
import 'package:on_time_front/data/models/sign_in_with_apple_request_model.dart';
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
import 'package:on_time_front/domain/entities/user_entity.dart';
import 'package:on_time_front/domain/repositories/user_repository.dart';
import 'package:rxdart/subjects.dart';

@Singleton(as: UserRepository)
class UserRepositoryImpl implements UserRepository {
static const _googleIosClientId =
'456571312261-r35ah9qi0qaq7al007e2db0e0jmjcmb4.apps.googleusercontent.com';
static const _googleServerClientId =
'456571312261-5kuf2r6i5i7lqjr7qealv06sdgkn3hcp.apps.googleusercontent.com';
static const _googleScopes = ['email', 'profile'];

final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
final TokenLocalDataSource _tokenLocalDataSource;
final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
Future<void>? _googleSignInInitialization;
final GoogleAuthenticationService _googleAuthenticationService;
late final _userStreamController = BehaviorSubject<UserEntity>.seeded(
const UserEntity.empty(),
);

@override
Stream<GoogleSignInAuthenticationEvent> get googleAuthenticationEvents =>
_googleSignIn.authenticationEvents;

@override
bool get supportsGoogleAuthenticate => _googleSignIn.supportsAuthenticate();

UserRepositoryImpl(
this._authenticationRemoteDataSource,
this._tokenLocalDataSource,
this._googleAuthenticationService,
);

@override
Future<void> initializeGoogleSignIn() {
return _googleSignInInitialization ??= _initializeGoogleSignIn();
}

Future<void> _initializeGoogleSignIn() async {
await _googleSignIn.initialize(
clientId: _googleClientId,
serverClientId: _googleServerClientId,
);
}

@override
Future<GoogleSignInAccount> authenticateWithGoogle() async {
await initializeGoogleSignIn();
return _googleSignIn.authenticate(scopeHint: _googleScopes);
}

@override
Future<UserEntity> getUser() async {
try {
Expand Down Expand Up @@ -122,16 +91,14 @@ class UserRepositoryImpl implements UserRepository {
}

@override
Future<void> signInWithGoogle(GoogleSignInAccount googleUser) async {
Future<void> signInWithGoogle(GoogleAuthCredential credential) async {
try {
final GoogleSignInAuthentication googleAuth = googleUser.authentication;
final String? idToken = googleAuth.idToken;
if (idToken == null) {
if (credential.idToken.isEmpty) {
throw Exception('Google ID Token is null');
}
final signInWithGoogleRequestModel = SignInWithGoogleRequestModel(
idToken: idToken,
refreshToken: '',
idToken: credential.idToken,
refreshToken: credential.refreshToken,
);
await _tokenLocalDataSource.deleteToken();
final result = await _authenticationRemoteDataSource.signInWithGoogle(
Expand Down Expand Up @@ -225,8 +192,7 @@ class UserRepositoryImpl implements UserRepository {
@override
Future<void> disconnectGoogleSignIn() async {
try {
await _googleSignIn.disconnect();
AppLogger.debug('Google Sign-In disconnected');
await _googleAuthenticationService.disconnect();
} catch (error) {
AppLogger.debug(
'Google Sign-In disconnect failed errorType=${error.runtimeType}',
Expand All @@ -237,11 +203,4 @@ class UserRepositoryImpl implements UserRepository {
@override
Stream<UserEntity> get userStream =>
_userStreamController.asBroadcastStream();

String? get _googleClientId {
if (kIsWeb) return null;
return defaultTargetPlatform == TargetPlatform.iOS
? _googleIosClientId
: null;
}
}
6 changes: 6 additions & 0 deletions lib/domain/entities/google_auth_credential.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class GoogleAuthCredential {
const GoogleAuthCredential({required this.idToken, this.refreshToken = ''});

final String idToken;
final String refreshToken;
}
12 changes: 2 additions & 10 deletions lib/domain/repositories/user_repository.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import 'package:google_sign_in/google_sign_in.dart';
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
import 'package:on_time_front/domain/entities/user_entity.dart';

abstract interface class UserRepository {
Stream<UserEntity> get userStream;

Stream<GoogleSignInAuthenticationEvent> get googleAuthenticationEvents;

Future<void> initializeGoogleSignIn();

bool get supportsGoogleAuthenticate;

Future<GoogleSignInAccount> authenticateWithGoogle();

Future<void> signUp({
required String email,
required String password,
Expand All @@ -22,7 +14,7 @@ abstract interface class UserRepository {

Future<void> signOut();

Future<void> signInWithGoogle(GoogleSignInAccount account);
Future<void> signInWithGoogle(GoogleAuthCredential credential);

Future<void> signInWithApple({
required String idToken,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:google_sign_in_web/web_only.dart';
import 'package:on_time_front/core/di/di_setup.dart';
import 'package:on_time_front/core/services/google_authentication_service.dart';
import 'package:on_time_front/domain/entities/google_auth_credential.dart';
import 'package:on_time_front/domain/repositories/user_repository.dart';

class GoogleSignInButton extends StatefulWidget {
Expand All @@ -16,26 +17,27 @@ class GoogleSignInButton extends StatefulWidget {
}

class _GoogleSignInButtonState extends State<GoogleSignInButton> {
final authenticationRepository = getIt.get<UserRepository>();
late final Stream<GoogleSignInAuthenticationEvent> _authenticationEvents;
StreamSubscription<GoogleSignInAuthenticationEvent>?
_authenticationEventsSubscription;
final googleAuthenticationService = getIt.get<GoogleAuthenticationService>();
late final Stream<GoogleAuthCredential> _authenticationCredentials;
StreamSubscription<GoogleAuthCredential>?
_authenticationCredentialsSubscription;

@override
void initState() {
_authenticationEvents = authenticationRepository.googleAuthenticationEvents;
unawaited(authenticationRepository.initializeGoogleSignIn());
_authenticationEventsSubscription = _authenticationEvents.listen((event) {
if (event is GoogleSignInAuthenticationEventSignIn) {
unawaited(getIt.get<UserRepository>().signInWithGoogle(event.user));
}
_authenticationCredentials =
googleAuthenticationService.authenticationCredentials;
unawaited(googleAuthenticationService.initialize());
_authenticationCredentialsSubscription = _authenticationCredentials.listen((
credential,
) {
unawaited(getIt.get<UserRepository>().signInWithGoogle(credential));
});
super.initState();
}

@override
void dispose() {
unawaited(_authenticationEventsSubscription?.cancel());
unawaited(_authenticationCredentialsSubscription?.cancel());
super.dispose();
}

Expand Down
11 changes: 6 additions & 5 deletions lib/presentation/login/screens/sign_in_main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter/foundation.dart';
import 'dart:io' show Platform;
import 'package:google_sign_in/google_sign_in.dart';
import 'package:on_time_front/core/di/di_setup.dart';
import 'package:on_time_front/core/logging/app_logger.dart';
import 'package:on_time_front/core/services/google_authentication_service.dart';
import 'package:on_time_front/domain/repositories/user_repository.dart';
import 'package:on_time_front/l10n/app_localizations.dart';
import 'package:on_time_front/presentation/shared/components/modal_wide_button.dart';
Expand Down Expand Up @@ -105,8 +105,10 @@ class _SignInMainScreenState extends State<SignInMainScreen> {

Future<void> _defaultGoogleSignIn() async {
final userRepository = getIt.get<UserRepository>();
final googleAccount = await userRepository.authenticateWithGoogle();
await userRepository.signInWithGoogle(googleAccount);
final credential = await getIt
.get<GoogleAuthenticationService>()
.authenticate();
await userRepository.signInWithGoogle(credential);
}

Future<void> _defaultAppleSignIn() async {
Expand Down Expand Up @@ -137,8 +139,7 @@ class _SignInMainScreenState extends State<SignInMainScreen> {
}

bool _isUserCancellation(Object error) {
return error is GoogleSignInException &&
error.code == GoogleSignInExceptionCode.canceled ||
return error is GoogleAuthenticationCanceledException ||
error is SignInWithAppleAuthorizationException &&
error.code == AuthorizationErrorCode.canceled;
}
Expand Down
Loading
Loading