Skip to content

Commit

Permalink
Implement apple login for iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-sneha-s committed Mar 8, 2024
1 parent d845ed7 commit d57518b
Show file tree
Hide file tree
Showing 27 changed files with 743 additions and 148 deletions.
5 changes: 5 additions & 0 deletions assets/images/apple_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/images/google_logo.png
Binary file not shown.
8 changes: 8 additions & 0 deletions assets/images/google_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
7 changes: 6 additions & 1 deletion ios/Runner/RunnerDebug.entitlements
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions lib/data/di/service_locator.config.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions lib/data/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

"sign_in_description_text": "Help you to manage your office leaves and employees easily with unity.",
"sign_in_title_text": "Effortless Leave\nManagement with Unity",
"login_button_text": "Sign in with Google",
"company_name": "Canopas Software",
"company_subtitle": "www.canopas.com",
"google_login_button_text": "Sign in with Google",
"apple_login_button_text": "Sign in with Apple",

"settings_tag": "Settings",
"members_tag": "Members",
Expand Down
4 changes: 2 additions & 2 deletions lib/data/services/account_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class AccountService {
fromFirestore: Account.fromFireStore,
toFirestore: (Account user, _) => user.toJson());

Future<Account> getUser(firebase_auth.User authData) async {
Future<Account> getUser(firebase_auth.User authData, {String? name}) async {
final userDataDoc = await _accountsDb.doc(authData.uid).get();
final Account user;
final Account? userData = userDataDoc.data();
Expand All @@ -29,7 +29,7 @@ class AccountService {
user = Account(
uid: authData.uid,
email: authData.email!,
name: authData.displayName);
name:name?? authData.displayName);
await _accountsDb.doc(authData.uid).set(user);
}
await _setUserSession(authData.uid);
Expand Down
65 changes: 59 additions & 6 deletions lib/data/services/auth_service.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:crypto/crypto.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:flutter/foundation.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:injectable/injectable.dart';
import 'package:oauth2/oauth2.dart';
import 'package:projectunity/data/services/account_service.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import '../model/account/account.dart';
import '../state_manager/auth/desktop/desktop_auth_manager.dart';

@LazySingleton()
class AuthService {
final DesktopAuthManager _desktopAuthManager;
final FirebaseFirestore fireStore;
final AccountService _accountService;

final firebase_auth.FirebaseAuth firebaseAuth;

AuthService(this._desktopAuthManager, this.fireStore, this.firebaseAuth);
AuthService(this._desktopAuthManager, this.fireStore, this.firebaseAuth,
this._accountService);

Future<firebase_auth.User?> signInWithGoogle() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
Expand Down Expand Up @@ -46,7 +56,7 @@ class AuthService {
idToken: googleSignInAuthentication.idToken,
);

user = await _signInWithCredentials(credential);
user = await signInWithCredentials(credential);
await googleSignIn.signOut();
}
} catch (e) {
Expand All @@ -63,7 +73,7 @@ class AuthService {
idToken: credentials.idToken,
accessToken: credentials.accessToken);

user = await _signInWithCredentials(authCredential);
user = await signInWithCredentials(authCredential);

await _desktopAuthManager.signOutFromGoogle(credentials.accessToken);
} on Exception {
Expand All @@ -73,8 +83,9 @@ class AuthService {
return user;
}

Future<firebase_auth.User?> _signInWithCredentials(
firebase_auth.AuthCredential authCredential) async {
Future<firebase_auth.User?> signInWithCredentials(
firebase_auth.AuthCredential authCredential,
) async {
firebase_auth.User? user;
try {
final firebase_auth.UserCredential userCredential =
Expand All @@ -86,7 +97,49 @@ class AuthService {
return user;
}

Future<bool> signOutWithGoogle() async {
Future<Account?> signInWithApple() async {
final rawNounce = generateNonce();
final nounce = sha256ofString(rawNounce);

final appleCredential = await SignInWithApple.getAppleIDCredential(scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName
], nonce: nounce);

final oAuthCredential = firebase_auth.OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken, rawNonce: rawNounce);

final name = appleCredential.givenName;
final authUser = await signInWithCredentials(oAuthCredential);
if (name != null) {
return await setUser(authUser, name: name);
}
return await setUser(authUser);
}

Future<Account?> setUser(firebase_auth.User? authUser, {String? name}) async {
if (authUser != null) {
final Account user = await _accountService.getUser(authUser, name: name);
return user;
}
return null;
}

String generateNounce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}

String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}

Future<bool> signOut() async {
try {
await firebaseAuth.signOut();
return true;
Expand Down
9 changes: 6 additions & 3 deletions lib/gen/assets.gen.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions lib/style/other/smart_scroll_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

// A scrollview that supports Spacer to fill the remaining space
import 'package:flutter/cupertino.dart';

class SmartScrollView extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final ScrollController? controller;

const SmartScrollView({
super.key,
required this.child,
this.padding,
this.controller,
});

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
controller: controller,
padding: padding,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - (padding?.vertical ?? 0),
),
child: IntrinsicHeight(
child: child,
),
),
);
},
);
}
}
47 changes: 40 additions & 7 deletions lib/ui/sign_in/bloc/sign_in_view_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:crypto/crypto.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:projectunity/data/services/account_service.dart';
import 'package:projectunity/ui/sign_in/widget/apple_signin_button.dart';
import '../../../data/core/exception/error_const.dart';
import '../../../data/model/account/account.dart';
import '../../../data/provider/user_state.dart';
import '../../../data/services/auth_service.dart';
import 'sign_in_view_event.dart';
import 'sign_in_view_state.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

@Injectable()
class SignInBloc extends Bloc<SignInEvent, SignInState> {
Expand All @@ -19,23 +26,49 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
this._userStateNotifier,
this._authService,
this._accountService,
) : super(SignInInitialState()) {
on<SignInEvent>(_signIn);
) : super(const SignInState()) {
on<GoogleSignInEvent>(_googleSignIn);
on<AppleSignInEvent>(_appleSignIn);
}

Future<void> check(Emitter<SignInState> emit) async {
final isAvailable = await SignInWithApple.isAvailable();
emit(state.copyWith(appleSignInAvailable: isAvailable));
}

Future<void> _signIn(SignInEvent event, Emitter<SignInState> emit) async {
emit(SignInLoadingState());
Future<void> _googleSignIn(
SignInEvent event, Emitter<SignInState> emit) async {
try {
emit(state.copyWith(googleSignInLoading: true));
firebase_auth.User? authUser = await _authService.signInWithGoogle();
if (authUser != null) {
final Account user = await _accountService.getUser(authUser);
await _userStateNotifier.setUser(user);
emit(SignInSuccessState());
emit(state.copyWith(googleSignInLoading: false, signInSuccess: true));
} else {
emit(SignInInitialState());
emit(state.copyWith(googleSignInLoading: false));
}
} on Exception {
emit(SignInFailureState(error: firesbaseAuthError));
emit(state.copyWith(
googleSignInLoading: false, error: firesbaseAuthError));
}
}

Future<void> _appleSignIn(
AppleSignInEvent event, Emitter<SignInState> emit) async {
try {
emit(state.copyWith(appleSignInLoading: true));
Account? user = await _authService.signInWithApple();
if (user != null) {
await _userStateNotifier.setUser(user);
emit(state.copyWith(appleSignInLoading: false, signInSuccess: true));
} else {
emit(state.copyWith(appleSignInLoading: false));
}
} on Exception catch (e) {
emit(
state.copyWith(appleSignInLoading: false, error: firesbaseAuthError));
throw Exception(e.toString());
}
}
}
9 changes: 5 additions & 4 deletions lib/ui/sign_in/bloc/sign_in_view_event.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:equatable/equatable.dart';

class SignInEvent extends Equatable {
@override
List<Object?> get props => [];
}
abstract class SignInEvent{}

class GoogleSignInEvent extends SignInEvent {}
class AppleSignInEvent extends SignInEvent {}

31 changes: 11 additions & 20 deletions lib/ui/sign_in/bloc/sign_in_view_state.dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import 'package:equatable/equatable.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

abstract class SignInState extends Equatable {}
part "sign_in_view_state.freezed.dart";

class SignInInitialState extends SignInState {
@override
List<Object?> get props => [];
}

class SignInLoadingState extends SignInState {
@override
List<Object?> get props => [];
@freezed
class SignInState with _$SignInState{
const factory SignInState({
@Default(false) appleSignInAvailable,
@Default(false) googleSignInLoading,
@Default(false) appleSignInLoading,
@Default(false) signInSuccess,
String? error
}) = _SignInState;
}

class SignInFailureState extends SignInState {
final String error;

SignInFailureState({required this.error});

@override
List<Object?> get props => [error];
}

class SignInSuccessState extends SignInState {
@override
List<Object?> get props => [];
}
Loading

0 comments on commit d57518b

Please sign in to comment.