Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add: authentication blocker #66

Merged
merged 17 commits into from
Nov 18, 2023
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
Binary file removed assets/icon/Integrate-with-Samsung-IAP-05212021.pdf
Binary file not shown.
Binary file removed assets/icon/app_icon.jpg
Binary file not shown.
1 change: 1 addition & 0 deletions assets/icon/google.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/error/something_went_wrong.png
Binary file not shown.
1 change: 1 addition & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extensions:
2 changes: 2 additions & 0 deletions lib/app.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:varanasi_mobile_app/features/library/cubit/library_cubit.dart';
import 'package:varanasi_mobile_app/features/session/cubit/session_cubit.dart';
import 'package:varanasi_mobile_app/features/user-library/cubit/user_library_cubit.dart';
import 'package:varanasi_mobile_app/utils/constants/constants.dart';
import 'package:varanasi_mobile_app/utils/router.dart';
Expand Down Expand Up @@ -36,6 +37,7 @@ class Varanasi extends StatelessWidget {
create: (ctx) => MediaPlayerCubit()..init(),
),
BlocProvider(lazy: false, create: (ctx) => LibraryCubit()),
BlocProvider(lazy: false, create: (_) => SessionCubit()..init()),
],
child: Builder(builder: (context) {
final scheme = context.select(
Expand Down
5 changes: 4 additions & 1 deletion lib/cubits/player/player_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>
media.moreInfoUrl,
options: CommonOptions(
transformer: (response) {
final data = response as List<dynamic>;
if (response is! List<dynamic>) {
throw Exception('Invalid response');
}
final data = response;
if (data.isEmpty) {
throw Exception('No data found');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/features/home/bloc/home_state.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
part of 'home_bloc.dart';

abstract class HomeState extends Equatable {
sealed class HomeState extends Equatable {
const HomeState();
}

Expand Down
8 changes: 4 additions & 4 deletions lib/features/home/ui/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ class HomePage extends StatelessWidget {
],
),
body: TriStateVisibility(
state: switch (state.runtimeType) {
HomeErrorState => TriState.error,
HomeLoadingState => TriState.loading,
_ => TriState.loaded,
state: switch (state) {
(HomeErrorState _) => TriState.error,
(HomeLoadingState _) => TriState.loading,
(_) => TriState.loaded,
},
loadingChild: const HomePageLoader(),
errorChild: switch (state) {
Expand Down
5 changes: 1 addition & 4 deletions lib/features/search/data/search_result/result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Result extends PlayableMedia with EquatableMixin {
String? get artworkUrl => image?.lastOrNull?.link ?? '';

@override
String get itemId => itemType.isSong
String get itemId => itemType.isSong && itemUrl.isNotEmpty
? (itemUrl.split('/').lastOrNull ?? id ?? '')
: (id ?? '');

Expand All @@ -98,7 +98,4 @@ class Result extends PlayableMedia with EquatableMixin {

@override
String get itemUrl => url ?? '';

@override
bool get preferLinkOverId => itemType.isSong || itemType.isAlbum;
}
7 changes: 3 additions & 4 deletions lib/features/search/data/top_search_result/top_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ class TopSearch extends PlayableMedia with EquatableMixin {
String? get artworkUrl => image?.lastOrNull?.link;

@override
String get itemId => itemUrl.split('/').lastOrNull ?? id ?? '';
String get itemId => itemUrl.isEmpty
? (id ?? '')
: (itemUrl.split('/').lastOrNull ?? id ?? '');

@override
String get itemSubtitle => "Top Search • $description";
Expand All @@ -83,7 +85,4 @@ class TopSearch extends PlayableMedia with EquatableMixin {

@override
String get itemUrl => url ?? '';

@override
bool get preferLinkOverId => true;
}
94 changes: 74 additions & 20 deletions lib/features/session/cubit/session_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:go_router/go_router.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:varanasi_mobile_app/utils/app_cubit.dart';
import 'package:varanasi_mobile_app/utils/extensions/router.dart';
import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart';
import 'package:varanasi_mobile_app/utils/app_snackbar.dart';
import 'package:varanasi_mobile_app/utils/logger.dart';
import 'package:varanasi_mobile_app/utils/routes.dart';

part 'session_state.dart';

Expand All @@ -23,22 +20,9 @@ class SessionCubit extends AppCubit<SessionState> {

@override
FutureOr<void> init() {
_auth.userChanges().listen((user) {
if (user == null) {
emit(UnAuthenticated());
} else {
emit(Authenticated(user: user));
}
});
stream.distinct().listen((state) {
if (state is! Authenticated) {
return appContext.go(AppRoutes.authentication.path);
}
if ([AppRoutes.authentication.path]
.contains(appContext.routerState.matchedLocation)) {
return appContext.go(AppRoutes.home.path);
}
});
_auth.userChanges().map((user) {
return user == null ? UnAuthenticated() : Authenticated(user: user);
}).listen(emit);
}

Future<void> continueWithGoogle() async {
Expand All @@ -52,8 +36,78 @@ class SessionCubit extends AppCubit<SessionState> {
);
final userCredential = await _auth.signInWithCredential(credential);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
_handleException(e);
} catch (e) {
await _googleSignIn.signOut();
_logger.d(e.toString());
}
}

Future<void> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
emit(Authenticating());
try {
final userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
_handleException(e);
} catch (e) {
_logger.d(e.toString());
}
}

Future<void> signUpWithEmailAndPassword({
required String email,
required String password,
required String name,
}) async {
emit(Authenticating());
try {
final userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await userCredential.user?.updateDisplayName(name);
_logger.d(userCredential.user?.toString());
} on FirebaseAuthException catch (e) {
_handleException(e);
} catch (e) {
_logger.d(e.toString());
}
}

Future<void> signOut() async {
emit(Authenticating());
try {
await _googleSignIn.signOut();
await _auth.signOut();
} catch (e) {
_logger.d(e.toString());
}
}

void _handleException(FirebaseAuthException exception) {
final message = switch (exception.code) {
'invalid-email' => 'The email address is not valid.',
'user-disabled' =>
'The user corresponding to the given email has been disabled.',
'user-not-found' =>
'The user corresponding to the given email does not exist.',
'wrong-password' =>
'The password is invalid for the given email, or the account corresponding to the email does not have a password set.',
'email-already-in-use' =>
'The email address is already in use by another account.',
'operation-not-allowed' =>
'Indicates that Email & Password accounts are not enabled.',
'weak-password' => 'The password must be 6 characters long or more.',
(_) => 'An undefined Error happened.'
};
AppSnackbar.show(message);
}
}
116 changes: 56 additions & 60 deletions lib/features/session/ui/auth_page.dart
Original file line number Diff line number Diff line change
@@ -1,81 +1,77 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.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/gen/assets.gen.dart';
import 'package:varanasi_mobile_app/utils/extensions/extensions.dart';
import 'package:varanasi_mobile_app/utils/helpers/ressponsive_sizer.dart';
import 'package:varanasi_mobile_app/utils/routes.dart';

class AuthPage extends StatelessWidget {
const AuthPage({super.key});

@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: const SystemUiOverlayStyle(statusBarBrightness: Brightness.dark),
child: Scaffold(
body: SafeArea(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: Device.width * 0.8,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Assets.icon.appIconMonotone.svg(
placeholderBuilder: (ctx) =>
const SizedBox(width: 48, height: 48),
return Scaffold(
body: SafeArea(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: Device.width * 0.8,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Assets.icon.appIconMonotone.svg(
placeholderBuilder: (ctx) =>
const SizedBox(width: 48, height: 48),
),
const SizedBox(height: 20),
Text(
"Millions of Songs.\nForever Free.",
style: context.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
const SizedBox(height: 20),
Text(
"Millions of Songs.\nForever Free.",
style: context.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
textAlign: TextAlign.center,
),
const SizedBox(height: 36),
FilledButton.tonal(
style: _buildButtonStyles(),
onPressed: () => context.pushNamed(AppRoutes.signup.name),
child: _buildText(context, "Sign up for free"),
),
const SizedBox(height: 8),
OutlinedButton.icon(
icon: Assets.icon.google.svg(width: 24, height: 24),
onPressed: context.read<SessionCubit>().continueWithGoogle,
style: _buildButtonStyles(),
label: Center(
child: _buildText(context, "Continue with Google"),
),
const SizedBox(height: 36),
FilledButton.tonal(
onPressed: () {},
child: const Text(
"Sign up for free",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
OutlinedButton.icon(
icon: const Icon(Icons.facebook),
onPressed: context.read<SessionCubit>().continueWithGoogle,
style: OutlinedButton.styleFrom(
foregroundColor: context.colorScheme.onBackground,
),
label: const Center(
child: Text(
"Continue with Google",
style: TextStyle(
fontWeight: FontWeight.w800,
),
),
),
),
TextButton(
style: TextButton.styleFrom(
foregroundColor: context.colorScheme.onBackground,
),
onPressed: () {},
child: const Text(
"Log in",
style: TextStyle(fontWeight: FontWeight.bold),
),
)
],
),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => context.pushNamed(AppRoutes.login.name),
child: _buildText(context, "Log in"),
)
],
),
),
),
),
);
}

ButtonStyle _buildButtonStyles() {
return OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
);
}

Text _buildText(BuildContext context, String text) {
return Text(
text,
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
);
}
}
Loading