Skip to content

Commit

Permalink
✨ New: Sign in anonymously (#80)
Browse files Browse the repository at this point in the history
* Add signInAnonymously method to SessionCubit

* Add spacing and "Continue as guest" button to
AuthPage

* Add Google account linking functionality to
SessionCubit

* Add isGuest property to Authenticated state

* Add "Continue as guest" button functionality

* Add Google account linking and sign out
functionality

* Fix stream subscription in user library and recent
media services

* Add forceLogin parameter to redirect logic in
routerConfig

* Fix authentication bugs and add email account
linking

* Add import for flutter/scheduler.dart

* Refactor router configuration

* Refactor session_cubit.dart to update display name
if provided

* Add AppSnackbar to show success message after
account linking

* Fix authentication flow and update routes
  • Loading branch information
devaryakjha authored Nov 29, 2023
1 parent 0b07935 commit 5b79f34
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 49 deletions.
69 changes: 69 additions & 0 deletions lib/features/session/cubit/session_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,32 @@ class SessionCubit extends AppCubit<SessionState> {
final userCredential = await _auth.signInWithCredential(credential);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
emit(UnAuthenticated());
_handleException(e);
} catch (e) {
await _googleSignIn.signOut();
emit(UnAuthenticated());
_logger.d(e.toString());
}
}

Future<void> linkGoogleAccount() async {
final prevState = state;
try {
final account = await _googleSignIn.signIn();
final authentication = await account?.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: authentication?.accessToken,
idToken: authentication?.idToken,
);
final user = _auth.currentUser;
await user?.linkWithCredential(credential);
} on FirebaseAuthException catch (e) {
emit(prevState);
await _googleSignIn.signOut();
_handleException(e);
} catch (e) {
emit(prevState);
await _googleSignIn.signOut();
_logger.d(e.toString());
}
Expand All @@ -83,11 +107,40 @@ class SessionCubit extends AppCubit<SessionState> {
password: password,
);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
emit(UnAuthenticated());
_handleException(e);
} catch (e) {
emit(UnAuthenticated());
_logger.d(e.toString());
}
}

Future<bool> linkEmailPassword({
required String email,
required String password,
String? name,
}) async {
try {
final user = _auth.currentUser;
final credential = EmailAuthProvider.credential(
email: email,
password: password,
);
final ucredential = await user?.linkWithCredential(credential);
if (ucredential?.user != null) {
if (name != null) {
unawaited(ucredential?.user?.updateDisplayName(name));
}
emit((state as Authenticated).copyWith(user: ucredential!.user!));
}
return true;
} on FirebaseAuthException catch (e) {
_handleException(e);
} catch (e) {
_logger.d(e.toString());
}
return false;
}

Future<void> signUpWithEmailAndPassword({
Expand All @@ -103,6 +156,20 @@ class SessionCubit extends AppCubit<SessionState> {
);
await userCredential.user?.updateDisplayName(name);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
emit(UnAuthenticated());
_handleException(e);
} catch (e) {
emit(UnAuthenticated());
_logger.d(e.toString());
}
}

Future<void> signInAnonymously() async {
emit(Authenticating());
try {
final userCredential = await _auth.signInAnonymously();
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
_handleException(e);
} catch (e) {
Expand Down Expand Up @@ -146,6 +213,8 @@ class SessionCubit extends AppCubit<SessionState> {
'operation-not-allowed' =>
'Indicates that Email & Password accounts are not enabled.',
'weak-password' => 'The password must be 6 characters long or more.',
'credential-already-in-use' =>
'This credential is already associated with a different user account.',
(_) => 'An undefined Error happened.'
};
AppSnackbar.show(message);
Expand Down
2 changes: 2 additions & 0 deletions lib/features/session/cubit/session_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,6 @@ final class Authenticated extends SessionState {
userData: userData ?? this.userData,
);
}

bool get isGuest => user.isAnonymous;
}
29 changes: 27 additions & 2 deletions lib/features/session/ui/auth_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class AuthPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
final isGuest = context.select((SessionCubit cubit) =>
cubit.state is Authenticated && (cubit.state as Authenticated).isGuest);
return Scaffold(
body: SafeArea(
child: Align(
Expand All @@ -22,6 +24,7 @@ class AuthPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(),
Assets.icon.appIconMonotone.svg(
placeholderBuilder: (ctx) =>
const SizedBox(width: 48, height: 48),
Expand Down Expand Up @@ -53,6 +56,18 @@ class AuthPage extends StatelessWidget {
TextButton(
onPressed: () => context.pushNamed(AppRoutes.login.name),
child: _buildText(context, "Log in"),
),
const Spacer(),
Visibility(
visible: !isGuest,
child: TextButton(
onPressed: () async {
await context.read<SessionCubit>().signInAnonymously();
if (!context.mounted) return;
context.goNamed(AppRoutes.home.name);
},
child: _buildText(context, "Continue as guest", true),
),
)
],
),
Expand All @@ -68,10 +83,20 @@ class AuthPage extends StatelessWidget {
);
}

Text _buildText(BuildContext context, String text) {
Text _buildText(BuildContext context, String text,
[bool isUnderlined = false]) {
var style =
context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold);
if (isUnderlined) {
style = style?.copyWith(
decoration: TextDecoration.underline,
decorationColor: Colors.white,
decorationThickness: 2,
);
}
return Text(
text,
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
style: style,
);
}
}
32 changes: 24 additions & 8 deletions lib/features/session/ui/login_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:varanasi_mobile_app/features/session/cubit/session_cubit.dart';
import 'package:varanasi_mobile_app/utils/app_snackbar.dart';
import 'package:varanasi_mobile_app/utils/extensions/extensions.dart';
import 'package:varanasi_mobile_app/widgets/input_field.dart';

Expand Down Expand Up @@ -142,15 +144,29 @@ class LoginButton extends StatelessWidget {

@override
Widget build(BuildContext context) {
final isGuestUser = context.select((SessionCubit cubit) =>
cubit.state is Authenticated && (cubit.state as Authenticated).isGuest);
return Align(
alignment: Alignment.center,
child: FilledButton.tonal(
onPressed: isFormValid
? () {
context.read<SessionCubit>().signInWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text,
);
? () async {
if (isGuestUser) {
final connected =
await context.read<SessionCubit>().linkEmailPassword(
email: _emailController.text,
password: _passwordController.text,
);
if (connected && context.mounted && context.canPop()) {
context.pop();
}
AppSnackbar.show("Account linked successfully.");
} else {
context.read<SessionCubit>().signInWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text,
);
}
}
: null,
style: FilledButton.styleFrom(
Expand All @@ -159,9 +175,9 @@ class LoginButton extends StatelessWidget {
horizontal: 36,
),
),
child: const Text(
'Log in',
style: TextStyle(fontWeight: FontWeight.bold),
child: Text(
isGuestUser ? "Link" : 'Log in',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
Expand Down
40 changes: 29 additions & 11 deletions lib/features/session/ui/signup_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:varanasi_mobile_app/features/session/cubit/session_cubit.dart';
import 'package:varanasi_mobile_app/utils/app_snackbar.dart';
import 'package:varanasi_mobile_app/utils/extensions/extensions.dart';
import 'package:varanasi_mobile_app/widgets/input_field.dart';

Expand Down Expand Up @@ -161,18 +163,34 @@ class LoginButton extends StatelessWidget {

@override
Widget build(BuildContext context) {
final authenticating = context.select((SessionCubit cubit) =>
cubit.state is Authenticating || cubit.state is Authenticated);
final isGuestUser = context.select((SessionCubit cubit) =>
cubit.state is Authenticated && (cubit.state as Authenticated).isGuest);
final authenticating = context.select((SessionCubit cubit) {
return cubit.state is Authenticating;
});
return Align(
alignment: Alignment.center,
child: FilledButton.tonal(
onPressed: isFormValid && !authenticating
? () {
context.read<SessionCubit>().signUpWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
name: nameController.text,
);
? () async {
if (isGuestUser) {
final connected =
await context.read<SessionCubit>().linkEmailPassword(
email: emailController.text,
password: passwordController.text,
name: nameController.text,
);
if (connected && context.mounted && context.canPop()) {
context.pop();
}
AppSnackbar.show("Account linked successfully.");
} else {
context.read<SessionCubit>().signUpWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
name: nameController.text,
);
}
}
: null,
style: FilledButton.styleFrom(
Expand All @@ -193,9 +211,9 @@ class LoginButton extends StatelessWidget {
height: 24,
child: CircularProgressIndicator(),
),
child: const Text(
'Create Account',
style: TextStyle(fontWeight: FontWeight.bold),
child: Text(
isGuestUser ? 'Link your account' : 'Create Account',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
Expand Down
Loading

0 comments on commit 5b79f34

Please sign in to comment.