diff --git a/assets/images/apple_logo.svg b/assets/images/apple_logo.svg
new file mode 100644
index 000000000..b3f28ce8a
--- /dev/null
+++ b/assets/images/apple_logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/google_logo.png b/assets/images/google_logo.png
deleted file mode 100644
index 750c9dabe..000000000
Binary files a/assets/images/google_logo.png and /dev/null differ
diff --git a/assets/images/google_logo.svg b/assets/images/google_logo.svg
new file mode 100644
index 000000000..2a29a62bd
--- /dev/null
+++ b/assets/images/google_logo.svg
@@ -0,0 +1,8 @@
+
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 0c67376eb..a812db506 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -1,5 +1,10 @@
-
+
+ com.apple.developer.applesignin
+
+ Default
+
+
diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements
index 0c67376eb..a812db506 100644
--- a/ios/Runner/RunnerDebug.entitlements
+++ b/ios/Runner/RunnerDebug.entitlements
@@ -1,5 +1,10 @@
-
+
+ com.apple.developer.applesignin
+
+ Default
+
+
diff --git a/lib/data/di/service_locator.config.dart b/lib/data/di/service_locator.config.dart
index 5a84e1c24..9f610c2bd 100644
--- a/lib/data/di/service_locator.config.dart
+++ b/lib/data/di/service_locator.config.dart
@@ -168,6 +168,7 @@ extension GetItInjectableX on _i1.GetIt {
gh<_i6.DesktopAuthManager>(),
gh<_i11.FirebaseFirestore>(),
gh<_i9.FirebaseAuth>(),
+ gh<_i26.AccountService>(),
));
gh.factory<_i29.EditSpaceBloc>(() => _i29.EditSpaceBloc(
gh<_i22.SpaceService>(),
diff --git a/lib/data/l10n/app_en.arb b/lib/data/l10n/app_en.arb
index 96f3a6238..ae7516150 100644
--- a/lib/data/l10n/app_en.arb
+++ b/lib/data/l10n/app_en.arb
@@ -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",
diff --git a/lib/data/services/account_service.dart b/lib/data/services/account_service.dart
index ae16a98e7..e2f82142c 100644
--- a/lib/data/services/account_service.dart
+++ b/lib/data/services/account_service.dart
@@ -19,7 +19,7 @@ class AccountService {
fromFirestore: Account.fromFireStore,
toFirestore: (Account user, _) => user.toJson());
- Future getUser(firebase_auth.User authData) async {
+ Future getUser(firebase_auth.User authData, {String? name}) async {
final userDataDoc = await _accountsDb.doc(authData.uid).get();
final Account user;
final Account? userData = userDataDoc.data();
@@ -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);
diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart
index 109670b93..ddd7bbc5a 100644
--- a/lib/data/services/auth_service.dart
+++ b/lib/data/services/auth_service.dart
@@ -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 signInWithGoogle() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
@@ -46,7 +56,7 @@ class AuthService {
idToken: googleSignInAuthentication.idToken,
);
- user = await _signInWithCredentials(credential);
+ user = await signInWithCredentials(credential);
await googleSignIn.signOut();
}
} catch (e) {
@@ -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 {
@@ -73,8 +83,9 @@ class AuthService {
return user;
}
- Future _signInWithCredentials(
- firebase_auth.AuthCredential authCredential) async {
+ Future signInWithCredentials(
+ firebase_auth.AuthCredential authCredential,
+ ) async {
firebase_auth.User? user;
try {
final firebase_auth.UserCredential userCredential =
@@ -86,7 +97,49 @@ class AuthService {
return user;
}
- Future signOutWithGoogle() async {
+ Future 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 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 signOut() async {
try {
await firebaseAuth.signOut();
return true;
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index e820e17c7..13101aaab 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -15,6 +15,9 @@ class $AssetsImagesGen {
/// File path: assets/images/app_logo.svg
String get appLogo => 'assets/images/app_logo.svg';
+ /// File path: assets/images/apple_logo.svg
+ String get appleLogo => 'assets/images/apple_logo.svg';
+
/// File path: assets/images/calendar_filled.svg
String get calendarFilled => 'assets/images/calendar_filled.svg';
@@ -22,9 +25,8 @@ class $AssetsImagesGen {
AssetGenImage get emptyState =>
const AssetGenImage('assets/images/empty_state.png');
- /// File path: assets/images/google_logo.png
- AssetGenImage get googleLogo =>
- const AssetGenImage('assets/images/google_logo.png');
+ /// File path: assets/images/google_logo.svg
+ String get googleLogo => 'assets/images/google_logo.svg';
/// File path: assets/images/home_filled.svg
String get homeFilled => 'assets/images/home_filled.svg';
@@ -54,6 +56,7 @@ class $AssetsImagesGen {
/// List of all assets
List get values => [
appLogo,
+ appleLogo,
calendarFilled,
emptyState,
googleLogo,
diff --git a/lib/style/other/smart_scroll_view.dart b/lib/style/other/smart_scroll_view.dart
new file mode 100644
index 000000000..d477f9b62
--- /dev/null
+++ b/lib/style/other/smart_scroll_view.dart
@@ -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,
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/ui/sign_in/bloc/sign_in_view_bloc.dart b/lib/ui/sign_in/bloc/sign_in_view_bloc.dart
index 5b5799075..4bb05a9a4 100644
--- a/lib/ui/sign_in/bloc/sign_in_view_bloc.dart
+++ b/lib/ui/sign_in/bloc/sign_in_view_bloc.dart
@@ -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 {
@@ -19,23 +26,49 @@ class SignInBloc extends Bloc {
this._userStateNotifier,
this._authService,
this._accountService,
- ) : super(SignInInitialState()) {
- on(_signIn);
+ ) : super(const SignInState()) {
+ on(_googleSignIn);
+ on(_appleSignIn);
+ }
+
+ Future check(Emitter emit) async {
+ final isAvailable = await SignInWithApple.isAvailable();
+ emit(state.copyWith(appleSignInAvailable: isAvailable));
}
- Future _signIn(SignInEvent event, Emitter emit) async {
- emit(SignInLoadingState());
+ Future _googleSignIn(
+ SignInEvent event, Emitter 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 _appleSignIn(
+ AppleSignInEvent event, Emitter 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());
}
}
}
diff --git a/lib/ui/sign_in/bloc/sign_in_view_event.dart b/lib/ui/sign_in/bloc/sign_in_view_event.dart
index c58a605ca..23909f59d 100644
--- a/lib/ui/sign_in/bloc/sign_in_view_event.dart
+++ b/lib/ui/sign_in/bloc/sign_in_view_event.dart
@@ -1,6 +1,7 @@
import 'package:equatable/equatable.dart';
-class SignInEvent extends Equatable {
- @override
- List