From e652dd0123889c3133c26d1f071c6ee9a8e18397 Mon Sep 17 00:00:00 2001 From: d-reader-luka <125265091+d-reader-luka@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:24:06 +0200 Subject: [PATCH] chore: consolidate auth flow * some ui changes * consolidate auth flow allow sign up to be google and regular refactor username validation create new endpoint for register with google * review changes * change unwrapped text --- assets/icons/double_tick.svg | 3 + assets/icons/intro/splash_2.svg | 28 --- lib/constants/constants.dart | 2 + .../data/datasource/auth_remote_source.dart | 54 ++++- .../repositories/auth_repository_impl.dart | 9 +- .../domain/repositories/auth_repository.dart | 6 +- .../providers/auth_providers.dart | 4 +- .../providers/sign_in/sign_in_notifier.dart | 45 +++- .../providers/sign_in/sign_in_notifier.g.dart | 2 +- .../sign_up/sign_up_data_notifier.dart | 6 +- .../sign_up/sign_up_data_notifier.g.dart | 2 +- .../providers/sign_up/sign_up_notifier.dart | 36 +++ .../providers/sign_up/state/sign_up_data.dart | 12 +- .../sign_up/state/sign_up_data.freezed.dart | 40 +++- .../{step_3.dart => connect_wallet_step.dart} | 5 +- ...ep_2.dart => email_and_password_step.dart} | 33 +-- .../presentation/screens/sign_up/sign_up.dart | 210 ++++++++++++------ .../{step_1.dart => username_step.dart} | 105 ++++++--- ...rification.dart => verification_step.dart} | 4 +- .../providers/comic_providers.dart | 6 +- .../home/presentation/screens/initial.dart | 6 +- .../home/presentation/screens/splash.dart | 29 +-- .../widgets/tabs/favorites/favorites.dart | 2 +- .../animations/mint_animation_screen.dart | 2 +- .../animations/open_nft_animation_screen.dart | 5 +- .../nft/presentation/screens/nft_details.dart | 19 +- .../providers/change_network.g.dart | 2 +- .../providers/security_and_privacy.g.dart | 2 +- .../presentation/screens/profile/profile.dart | 20 +- .../settings/presentation/screens/root.dart | 3 +- .../providers/wallet_notifier.g.dart | 2 +- .../data/remote/dio_network_service.dart | 8 +- lib/shared/data/remote/network_service.dart | 1 + .../solana/solana_transaction_notifier.dart | 2 +- .../solana/solana_transaction_notifier.g.dart | 2 +- lib/shared/utils/validation.dart | 12 + .../layout/bottom_navigation_item_icon.dart | 19 +- 37 files changed, 469 insertions(+), 279 deletions(-) create mode 100644 assets/icons/double_tick.svg delete mode 100644 assets/icons/intro/splash_2.svg rename lib/features/authentication/presentation/screens/sign_up/{step_3.dart => connect_wallet_step.dart} (97%) rename lib/features/authentication/presentation/screens/sign_up/{step_2.dart => email_and_password_step.dart} (86%) rename lib/features/authentication/presentation/screens/sign_up/{step_1.dart => username_step.dart} (58%) rename lib/features/authentication/presentation/screens/sign_up/{step_2_verification.dart => verification_step.dart} (97%) create mode 100644 lib/shared/utils/validation.dart diff --git a/assets/icons/double_tick.svg b/assets/icons/double_tick.svg new file mode 100644 index 00000000..24a1226a --- /dev/null +++ b/assets/icons/double_tick.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/intro/splash_2.svg b/assets/icons/intro/splash_2.svg deleted file mode 100644 index 5c29b424..00000000 --- a/assets/icons/intro/splash_2.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 68e93969..99398082 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -13,3 +13,5 @@ const String walkthroughAssetsPath = 'assets/images/walkthrough'; const String missingWalletAppText = 'Missing wallet application.'; const String powerSaveModeText = 'Cannot use wallet in the power save mode.'; const String successResult = 'OK'; +const String usernameCriteriaText = + 'Must be 3 to 20 characters long. Numbers, dashes and underscores are allowed.'; diff --git a/lib/features/authentication/data/datasource/auth_remote_source.dart b/lib/features/authentication/data/datasource/auth_remote_source.dart index 8471fb93..5a8795a1 100644 --- a/lib/features/authentication/data/datasource/auth_remote_source.dart +++ b/lib/features/authentication/data/datasource/auth_remote_source.dart @@ -29,8 +29,12 @@ abstract class AuthDataSource { Future disconnectWallet(String address); Future refreshToken(String token); - Future> googleSignIn( + Future> googleSignIn( {required String accessToken}); + Future> googleSignUp({ + required String accessToken, + required String username, + }); } class AuthRemoteDataSource implements AuthDataSource { @@ -219,11 +223,11 @@ class AuthRemoteDataSource implements AuthDataSource { } @override - Future> googleSignIn( + Future> googleSignIn( {required String accessToken}) async { try { final googleSignInResult = await networkService.patch( - '/auth/user/google-login', + '/auth/user/login-with-google', headers: networkService.updateHeader( { 'authorization': 'Google $accessToken', @@ -235,6 +239,9 @@ class AuthRemoteDataSource implements AuthDataSource { return Left(exception); }, (response) { + if (response.data is String || response.data is bool) { + return Right(bool.tryParse(response.data) ?? false); + } final authorizationResponse = AuthorizationResponse.fromJson(response.data); @@ -256,4 +263,45 @@ class AuthRemoteDataSource implements AuthDataSource { ); } } + + @override + Future> googleSignUp( + {required String accessToken, required String username}) async { + try { + final googleSignUpResult = + await networkService.post('/auth/user/register-with-google', + headers: networkService.updateHeader( + { + 'authorization': 'Google $accessToken', + }, + ), + data: { + "name": username, + }); + return googleSignUpResult.fold( + (exception) { + return Left(exception); + }, + (response) { + final authorizationResponse = + AuthorizationResponse.fromJson(response.data); + + networkService.updateHeader( + {'Authorization': authorizationResponse.accessToken}, + ); + + return Right(authorizationResponse); + }, + ); + } catch (exception) { + return Left( + AppException( + message: 'Unknown exception occurred', + statusCode: 500, + identifier: + '${exception.toString()}AuthRemoteDataSource.googleSignUp', + ), + ); + } + } } diff --git a/lib/features/authentication/data/repositories/auth_repository_impl.dart b/lib/features/authentication/data/repositories/auth_repository_impl.dart index 33a48192..f184378b 100644 --- a/lib/features/authentication/data/repositories/auth_repository_impl.dart +++ b/lib/features/authentication/data/repositories/auth_repository_impl.dart @@ -76,8 +76,15 @@ class AuthRepositoryImpl extends AuthRepository { } @override - Future> googleSignIn( + Future> googleSignIn( {required String accessToken}) { return authDataSource.googleSignIn(accessToken: accessToken); } + + @override + Future> googleSignUp( + {required String accessToken, required String username}) { + return authDataSource.googleSignUp( + accessToken: accessToken, username: username); + } } diff --git a/lib/features/authentication/domain/repositories/auth_repository.dart b/lib/features/authentication/domain/repositories/auth_repository.dart index 54a40de4..675154ef 100644 --- a/lib/features/authentication/domain/repositories/auth_repository.dart +++ b/lib/features/authentication/domain/repositories/auth_repository.dart @@ -29,7 +29,11 @@ abstract class AuthRepository { required String address, }); Future refreshToken(String refreshToken); - Future> googleSignIn({ + Future> googleSignIn({ required String accessToken, }); + Future> googleSignUp({ + required String accessToken, + required String username, + }); } diff --git a/lib/features/authentication/presentation/providers/auth_providers.dart b/lib/features/authentication/presentation/providers/auth_providers.dart index 376d1e58..d48b4310 100644 --- a/lib/features/authentication/presentation/providers/auth_providers.dart +++ b/lib/features/authentication/presentation/providers/auth_providers.dart @@ -1,5 +1,6 @@ import 'package:d_reader_flutter/config/config.dart'; import 'package:d_reader_flutter/constants/constants.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart'; import 'package:d_reader_flutter/features/user/domain/providers/user_provider.dart'; import 'package:d_reader_flutter/shared/data/local/local_store.dart'; import 'package:d_reader_flutter/routing/router.dart'; @@ -15,6 +16,7 @@ final logoutProvider = FutureProvider.autoDispose((ref) async { ref.invalidate(environmentProvider); ref.read(environmentProvider); ref.invalidate(tabBarProvider); + ref.invalidate(signUpDataNotifierProvider); await GoogleSignIn().signOut(); await Future.wait([ LocalStore.instance.delete( @@ -36,5 +38,3 @@ final verifyEmailProvider = FutureProvider.autoDispose return result.fold((exception) => exception.message, (_) => successResult); }); - -final isTOSSelected = StateProvider.autoDispose((ref) => false); diff --git a/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.dart b/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.dart index a582de00..f4d9bf6e 100644 --- a/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.dart +++ b/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.dart @@ -1,5 +1,7 @@ +import 'package:d_reader_flutter/features/authentication/domain/models/authorization_response.dart'; import 'package:d_reader_flutter/features/authentication/domain/providers/auth_provider.dart'; import 'package:d_reader_flutter/features/authentication/domain/repositories/auth_repository.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart'; import 'package:d_reader_flutter/features/user/presentation/providers/user_providers.dart'; import 'package:d_reader_flutter/routing/router.dart'; import 'package:d_reader_flutter/shared/domain/providers/environment/environment_notifier.dart'; @@ -16,15 +18,19 @@ class SignInController extends _$SignInController { @override void build() { - // TODO Think about using AutoState if it's possible/worth. _authRepository = ref.watch(authRepositoryProvider); } Future googleSignIn({ - required Function() onSuccess, + required Function(bool isRegistered) onSuccess, required Function(String message) onFail, }) async { final GoogleSignIn googleSignIn = GoogleSignIn(); + + if (await googleSignIn.isSignedIn()) { + await googleSignIn.signOut(); + } + try { final googleSignInResult = await googleSignIn.signIn(); final accessToken = @@ -33,12 +39,35 @@ class SignInController extends _$SignInController { return onFail('Failed to sign in with google.'); } - return await signIn( - nameOrEmail: '', - password: '', - onSuccess: onSuccess, - onFail: onFail, - googleAccessToken: accessToken, + ref.read(globalNotifierProvider.notifier).updateLoading(true); + final response = + await _authRepository.googleSignIn(accessToken: accessToken); + + return response.fold( + (failure) { + ref.read(globalNotifierProvider.notifier).updateLoading(false); + onFail(failure.message); + }, + (result) async { + if (result is bool) { + ref + .read(signUpDataNotifierProvider.notifier) + .updateGoogleAccessToken(accessToken); + ref.read(globalNotifierProvider.notifier).updateLoading(false); + return onSuccess(result); + } else if (result is AuthorizationResponse) { + ref.read(environmentProvider.notifier).updateEnvironmentState( + EnvironmentStateUpdateInput( + jwtToken: result.accessToken, + refreshToken: result.refreshToken, + ), + ); + await ref.read(myUserProvider.future); + ref.read(globalNotifierProvider.notifier).updateLoading(false); + ref.read(authRouteProvider).login(); + onSuccess(true); + } + }, ); } catch (exception) { ref.read(globalNotifierProvider.notifier).updateLoading(false); diff --git a/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.g.dart b/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.g.dart index 2d272b7a..12e89666 100644 --- a/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.g.dart +++ b/lib/features/authentication/presentation/providers/sign_in/sign_in_notifier.g.dart @@ -6,7 +6,7 @@ part of 'sign_in_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$signInControllerHash() => r'3e73f4e10ea0553a85a391b9f8562bddbf3c4327'; +String _$signInControllerHash() => r'0b4a50956bed4e7a5c7a9fce17957f439e4db742'; /// See also [SignInController]. @ProviderFor(SignInController) diff --git a/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart b/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart index 762e20ae..1d1da5d4 100644 --- a/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart +++ b/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart @@ -14,6 +14,10 @@ class SignUpDataNotifier extends _$SignUpDataNotifier { state = state.copyWith(username: username); } + void updateGoogleAccessToken(String accessToken) { + state = state.copyWith(googleAccessToken: accessToken); + } + void updateEmailAndPassword({ required String email, required String password, @@ -21,7 +25,7 @@ class SignUpDataNotifier extends _$SignUpDataNotifier { state = state.copyWith(email: email, password: password); } - void updateSucces(bool isSuccess) { + void updateSuccess(bool isSuccess) { state = state.copyWith( isSuccess: isSuccess, email: '', diff --git a/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.g.dart b/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.g.dart index 74854ac2..11c8d31e 100644 --- a/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.g.dart +++ b/lib/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.g.dart @@ -7,7 +7,7 @@ part of 'sign_up_data_notifier.dart'; // ************************************************************************** String _$signUpDataNotifierHash() => - r'fe1d5e2e19addea902619b27335dd8e2623e4ed2'; + r'fea3fbb7bf4084a57dade097d2da86b8017a0425'; /// See also [SignUpDataNotifier]. @ProviderFor(SignUpDataNotifier) diff --git a/lib/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart b/lib/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart index a5bf1f50..512cf33e 100644 --- a/lib/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart +++ b/lib/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart @@ -82,4 +82,40 @@ class SignUpNotifier extends _$SignUpNotifier { onSuccess(); }); } + + Future googleSignUp({ + required Function() onSuccess, + required Function(String message) onFail, + }) async { + ref.read(globalNotifierProvider.notifier).updateLoading(true); + try { + final SignUpData(:googleAccessToken, :username) = + ref.read(signUpDataNotifierProvider); + final response = await _authRepository.googleSignUp( + accessToken: googleAccessToken, + username: username, + ); + return response.fold( + (failure) { + ref.read(globalNotifierProvider.notifier).updateLoading(false); + onFail(failure.message); + }, + (authTokens) async { + ref.read(environmentProvider.notifier).updateEnvironmentState( + EnvironmentStateUpdateInput( + jwtToken: authTokens.accessToken, + refreshToken: authTokens.refreshToken, + ), + ); + await ref.read(myUserProvider.future); + ref.read(globalNotifierProvider.notifier).updateLoading(false); + ref.read(authRouteProvider).login(); + onSuccess(); + }, + ); + } catch (exception) { + ref.read(globalNotifierProvider.notifier).updateLoading(false); + onFail(exception.toString()); + } + } } diff --git a/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.dart b/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.dart index 30ac0692..10dfecdc 100644 --- a/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.dart +++ b/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.dart @@ -4,10 +4,10 @@ part 'sign_up_data.freezed.dart'; @freezed sealed class SignUpData with _$SignUpData { - const factory SignUpData({ - @Default('') String email, - @Default('') String password, - @Default('') String username, - @Default(false) bool isSuccess, - }) = _SignUpData; + const factory SignUpData( + {@Default('') String email, + @Default('') String password, + @Default('') String username, + @Default('') String googleAccessToken, + @Default(false) bool isSuccess}) = _SignUpData; } diff --git a/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.freezed.dart b/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.freezed.dart index 7142431c..b1912c57 100644 --- a/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.freezed.dart +++ b/lib/features/authentication/presentation/providers/sign_up/state/sign_up_data.freezed.dart @@ -19,6 +19,7 @@ mixin _$SignUpData { String get email => throw _privateConstructorUsedError; String get password => throw _privateConstructorUsedError; String get username => throw _privateConstructorUsedError; + String get googleAccessToken => throw _privateConstructorUsedError; bool get isSuccess => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -32,7 +33,12 @@ abstract class $SignUpDataCopyWith<$Res> { SignUpData value, $Res Function(SignUpData) then) = _$SignUpDataCopyWithImpl<$Res, SignUpData>; @useResult - $Res call({String email, String password, String username, bool isSuccess}); + $Res call( + {String email, + String password, + String username, + String googleAccessToken, + bool isSuccess}); } /// @nodoc @@ -51,6 +57,7 @@ class _$SignUpDataCopyWithImpl<$Res, $Val extends SignUpData> Object? email = null, Object? password = null, Object? username = null, + Object? googleAccessToken = null, Object? isSuccess = null, }) { return _then(_value.copyWith( @@ -66,6 +73,10 @@ class _$SignUpDataCopyWithImpl<$Res, $Val extends SignUpData> ? _value.username : username // ignore: cast_nullable_to_non_nullable as String, + googleAccessToken: null == googleAccessToken + ? _value.googleAccessToken + : googleAccessToken // ignore: cast_nullable_to_non_nullable + as String, isSuccess: null == isSuccess ? _value.isSuccess : isSuccess // ignore: cast_nullable_to_non_nullable @@ -82,7 +93,12 @@ abstract class _$$SignUpDataImplCopyWith<$Res> __$$SignUpDataImplCopyWithImpl<$Res>; @override @useResult - $Res call({String email, String password, String username, bool isSuccess}); + $Res call( + {String email, + String password, + String username, + String googleAccessToken, + bool isSuccess}); } /// @nodoc @@ -99,6 +115,7 @@ class __$$SignUpDataImplCopyWithImpl<$Res> Object? email = null, Object? password = null, Object? username = null, + Object? googleAccessToken = null, Object? isSuccess = null, }) { return _then(_$SignUpDataImpl( @@ -114,6 +131,10 @@ class __$$SignUpDataImplCopyWithImpl<$Res> ? _value.username : username // ignore: cast_nullable_to_non_nullable as String, + googleAccessToken: null == googleAccessToken + ? _value.googleAccessToken + : googleAccessToken // ignore: cast_nullable_to_non_nullable + as String, isSuccess: null == isSuccess ? _value.isSuccess : isSuccess // ignore: cast_nullable_to_non_nullable @@ -129,6 +150,7 @@ class _$SignUpDataImpl implements _SignUpData { {this.email = '', this.password = '', this.username = '', + this.googleAccessToken = '', this.isSuccess = false}); @override @@ -142,11 +164,14 @@ class _$SignUpDataImpl implements _SignUpData { final String username; @override @JsonKey() + final String googleAccessToken; + @override + @JsonKey() final bool isSuccess; @override String toString() { - return 'SignUpData(email: $email, password: $password, username: $username, isSuccess: $isSuccess)'; + return 'SignUpData(email: $email, password: $password, username: $username, googleAccessToken: $googleAccessToken, isSuccess: $isSuccess)'; } @override @@ -159,13 +184,15 @@ class _$SignUpDataImpl implements _SignUpData { other.password == password) && (identical(other.username, username) || other.username == username) && + (identical(other.googleAccessToken, googleAccessToken) || + other.googleAccessToken == googleAccessToken) && (identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess)); } @override - int get hashCode => - Object.hash(runtimeType, email, password, username, isSuccess); + int get hashCode => Object.hash( + runtimeType, email, password, username, googleAccessToken, isSuccess); @JsonKey(ignore: true) @override @@ -179,6 +206,7 @@ abstract class _SignUpData implements SignUpData { {final String email, final String password, final String username, + final String googleAccessToken, final bool isSuccess}) = _$SignUpDataImpl; @override @@ -188,6 +216,8 @@ abstract class _SignUpData implements SignUpData { @override String get username; @override + String get googleAccessToken; + @override bool get isSuccess; @override @JsonKey(ignore: true) diff --git a/lib/features/authentication/presentation/screens/sign_up/step_3.dart b/lib/features/authentication/presentation/screens/sign_up/connect_wallet_step.dart similarity index 97% rename from lib/features/authentication/presentation/screens/sign_up/step_3.dart rename to lib/features/authentication/presentation/screens/sign_up/connect_wallet_step.dart index 20033b0f..65f4eca6 100644 --- a/lib/features/authentication/presentation/screens/sign_up/step_3.dart +++ b/lib/features/authentication/presentation/screens/sign_up/connect_wallet_step.dart @@ -13,8 +13,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -class SignUpStep3 extends ConsumerWidget { - const SignUpStep3({super.key}); +class SignUpConnectWalletStep extends ConsumerWidget { + const SignUpConnectWalletStep({super.key}); Future _handleConnectWallet(WidgetRef ref, BuildContext context) async { await ref.read(walletControllerProvider.notifier).connectWallet( @@ -49,6 +49,7 @@ class SignUpStep3 extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SvgPicture.asset( '${Config.introAssetsPath}/wallet.svg', diff --git a/lib/features/authentication/presentation/screens/sign_up/step_2.dart b/lib/features/authentication/presentation/screens/sign_up/email_and_password_step.dart similarity index 86% rename from lib/features/authentication/presentation/screens/sign_up/step_2.dart rename to lib/features/authentication/presentation/screens/sign_up/email_and_password_step.dart index b9dc8c7f..c76eaa7d 100644 --- a/lib/features/authentication/presentation/screens/sign_up/step_2.dart +++ b/lib/features/authentication/presentation/screens/sign_up/email_and_password_step.dart @@ -13,20 +13,22 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -class SignUpStep2 extends ConsumerStatefulWidget { +class SignUpEmailAndPasswordStep extends ConsumerStatefulWidget { final Function() onSuccess; final Function(String text) onFail; - const SignUpStep2({ + const SignUpEmailAndPasswordStep({ super.key, required this.onSuccess, required this.onFail, }); @override - ConsumerState createState() => _SignUpStep1State(); + ConsumerState createState() => + _SignUpEmailAndPasswordStepState(); } -class _SignUpStep1State extends ConsumerState { +class _SignUpEmailAndPasswordStepState + extends ConsumerState { final GlobalKey _step2FormKey = GlobalKey(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); @@ -137,29 +139,6 @@ class _SignUpStep1State extends ConsumerState { color: ColorPalette.greyscale200, ), ), - const SizedBox( - height: 4, - ), - RichText( - text: const TextSpan( - text: 'By clicking on confirm you accept our ', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: ColorPalette.greyscale200, - ), - children: [ - TextSpan( - text: 'Privacy Policy', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: ColorPalette.dReaderYellow100, - ), - ), - ], - ), - ), const SizedBox( height: 48, ), diff --git a/lib/features/authentication/presentation/screens/sign_up/sign_up.dart b/lib/features/authentication/presentation/screens/sign_up/sign_up.dart index a0ad6d2f..ea513a24 100644 --- a/lib/features/authentication/presentation/screens/sign_up/sign_up.dart +++ b/lib/features/authentication/presentation/screens/sign_up/sign_up.dart @@ -1,9 +1,10 @@ import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart'; import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_providers.dart'; -import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/step_1.dart'; -import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/step_2.dart'; -import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/step_2_verification.dart'; -import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/step_3.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/username_step.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/email_and_password_step.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/verification_step.dart'; +import 'package:d_reader_flutter/features/authentication/presentation/screens/sign_up/connect_wallet_step.dart'; import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/shared/utils/show_snackbar.dart'; import 'package:flutter/material.dart'; @@ -47,62 +48,119 @@ class _SignUpScreenState extends ConsumerState { child: const Heading(), ), ), - body: Consumer( - builder: (context, ref, child) { - return PageView( - controller: _pageController, - physics: ref.watch(signUpPageProvider) > 2 - ? const NeverScrollableScrollPhysics() - : null, - onPageChanged: (value) { - ref.read(signUpPageProvider.notifier).update((state) => value); + body: ref.watch(signUpDataNotifierProvider).googleAccessToken.isNotEmpty + ? GoogleSignUpForm(pageController: _pageController) + : RegularSignUpForm(pageController: _pageController), + ), + ); + } +} + +class RegularSignUpForm extends ConsumerWidget { + final PageController pageController; + const RegularSignUpForm({ + super.key, + required this.pageController, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PageView( + controller: pageController, + physics: ref.watch(signUpPageProvider) > 2 + ? const NeverScrollableScrollPhysics() + : null, + onPageChanged: (value) { + ref.read(signUpPageProvider.notifier).update((state) => value); + }, + children: [ + SignUpUsernameStep( + onSuccess: () { + pageController.animateToPage( + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + ), + SignUpEmailAndPasswordStep( + onSuccess: () { + ref.read(signUpDataNotifierProvider.notifier).updateSuccess(true); + pageController.animateToPage( + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + onFail: (text) { + showSnackBar( + context: context, + text: text, + backgroundColor: ColorPalette.dReaderRed, + ); + }, + ), + if (ref.watch(signUpDataNotifierProvider).isSuccess) ...[ + SignUpVerificationStep( + handleNext: () { + pageController.animateToPage( + 3, + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + ), + const SignUpConnectWalletStep(), + ], + ], + ); + } +} + +class GoogleSignUpForm extends ConsumerWidget { + final PageController pageController; + const GoogleSignUpForm({ + super.key, + required this.pageController, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PageView( + controller: pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (value) { + ref.read(signUpPageProvider.notifier).update((state) => value); + }, + children: [ + SignUpUsernameStep( + overrideNext: () { + ref.read(signUpNotifierProvider.notifier).googleSignUp( + onSuccess: () { + ref + .read(signUpDataNotifierProvider.notifier) + .updateSuccess(true); + pageController.animateToPage( + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + onFail: (message) { + showSnackBar( + context: context, + text: message, + backgroundColor: ColorPalette.dReaderRed, + ); }, - children: [ - SignUpStep1( - onSuccess: () { - _pageController.animateToPage( - 1, - duration: const Duration(milliseconds: 300), - curve: Curves.easeIn, - ); - }, - ), - SignUpStep2( - onSuccess: () { - ref - .read(signUpDataNotifierProvider.notifier) - .updateSucces(true); - _pageController.animateToPage( - 2, - duration: const Duration(milliseconds: 300), - curve: Curves.easeIn, - ); - }, - onFail: (text) { - showSnackBar( - context: context, - text: text, - backgroundColor: ColorPalette.dReaderRed, - ); - }, - ), - if (ref.watch(signUpDataNotifierProvider).isSuccess) ...[ - SignUpStep2Verification( - handleNext: () { - _pageController.animateToPage( - 3, - duration: const Duration(milliseconds: 300), - curve: Curves.easeIn, - ); - }, - ), - const SignUpStep3(), - ], - ], ); }, + onSuccess: () {}, ), - ), + if (ref.watch(signUpDataNotifierProvider).isSuccess) ...[ + const SignUpConnectWalletStep(), + ], + ], ); } } @@ -129,24 +187,32 @@ class Heading extends ConsumerWidget { BlendMode.srcIn, ), ), - HeadingItem( - step: 2, - title: 'Email & pass', - color: ref.watch(signUpPageProvider) > 0 - ? Colors.white - : ColorPalette.greyscale200, - ), - SvgPicture.asset( - 'assets/icons/arrow_right.svg', - height: 16, - width: 16, - colorFilter: const ColorFilter.mode( - ColorPalette.greyscale200, - BlendMode.srcIn, + if (ref + .watch(signUpDataNotifierProvider) + .googleAccessToken + .isEmpty) ...[ + HeadingItem( + step: 2, + title: 'Email & pass', + color: ref.watch(signUpPageProvider) > 0 + ? Colors.white + : ColorPalette.greyscale200, ), - ), + SvgPicture.asset( + 'assets/icons/arrow_right.svg', + height: 16, + width: 16, + colorFilter: const ColorFilter.mode( + ColorPalette.greyscale200, + BlendMode.srcIn, + ), + ), + ], HeadingItem( - step: 3, + step: + ref.watch(signUpDataNotifierProvider).googleAccessToken.isNotEmpty + ? 2 + : 3, title: 'Wallet', color: ref.watch(signUpPageProvider) > 2 ? Colors.white diff --git a/lib/features/authentication/presentation/screens/sign_up/step_1.dart b/lib/features/authentication/presentation/screens/sign_up/username_step.dart similarity index 58% rename from lib/features/authentication/presentation/screens/sign_up/step_1.dart rename to lib/features/authentication/presentation/screens/sign_up/username_step.dart index 4d49823e..96a1fb1c 100644 --- a/lib/features/authentication/presentation/screens/sign_up/step_1.dart +++ b/lib/features/authentication/presentation/screens/sign_up/username_step.dart @@ -1,32 +1,35 @@ import 'package:d_reader_flutter/config/config.dart'; import 'package:d_reader_flutter/constants/constants.dart'; -import 'package:d_reader_flutter/features/authentication/presentation/providers/auth_providers.dart'; import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_data_notifier.dart'; import 'package:d_reader_flutter/features/authentication/presentation/providers/sign_up/sign_up_notifier.dart'; import 'package:d_reader_flutter/shared/presentations/providers/global/global_notifier.dart'; import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/shared/utils/show_snackbar.dart'; import 'package:d_reader_flutter/shared/utils/url_utils.dart'; +import 'package:d_reader_flutter/shared/utils/validation.dart'; import 'package:d_reader_flutter/shared/widgets/buttons/custom_text_button.dart'; -import 'package:d_reader_flutter/shared/widgets/checkbox/custom_labeled_checkbox.dart'; import 'package:d_reader_flutter/shared/widgets/textfields/text_field.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; -class SignUpStep1 extends ConsumerStatefulWidget { +class SignUpUsernameStep extends ConsumerStatefulWidget { final Function() onSuccess; - const SignUpStep1({ + final void Function()? overrideNext; + const SignUpUsernameStep({ super.key, required this.onSuccess, + this.overrideNext, }); @override - ConsumerState createState() => _SignUpStep1State(); + ConsumerState createState() => + _SignUpUsernameStepState(); } -class _SignUpStep1State extends ConsumerState { +class _SignUpUsernameStepState extends ConsumerState { final GlobalKey _usernameFormKey = GlobalKey(); final TextEditingController _usernameController = TextEditingController(); @@ -44,6 +47,12 @@ class _SignUpStep1State extends ConsumerState { void _handleNext() { if (_usernameFormKey.currentState!.validate()) { + if (widget.overrideNext != null) { + ref.read(signUpDataNotifierProvider.notifier).updateUsername( + _usernameController.text.trim(), + ); + return widget.overrideNext!(); + } ref.read(signUpNotifierProvider.notifier).handleSignUpStep1( username: _usernameController.text.trim(), onSuccess: widget.onSuccess, @@ -96,19 +105,10 @@ class _SignUpStep1State extends ConsumerState { labelText: 'Username', hintText: 'e.g Bun-Bun', controller: _usernameController, - onValidate: (value) { - if (value == null || value.isEmpty) { - return 'Field cannot be empty.'; - } else if (value.length < 2 || value.length > 20) { - return 'Username must be 3 to 20 characters long.'; - } else if (!usernameRegex.hasMatch(value)) { - return 'Letters, numbers, hyphens and dashes are allowed.'; - } - return null; - }, + onValidate: usernameValidation, ), const Text( - 'Must be 2 to 20 characters long. Letters, numbers and dashes are allowed.', + usernameCriteriaText, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, @@ -118,30 +118,62 @@ class _SignUpStep1State extends ConsumerState { const SizedBox( height: 24, ), - CustomLabeledCheckbox( - isChecked: ref.watch(isTOSSelected), - onChange: () { - ref.read(isTOSSelected.notifier).update((state) => !state); - }, - label: RichText( - text: TextSpan( - text: 'I have read and agree to the ', - style: Theme.of(context).textTheme.bodySmall, - children: [ - TextSpan( - text: 'Terms of Service', - recognizer: TapGestureRecognizer() - ..onTap = () { - openUrl(Config.privacyPolicyUrl); - }, + Row( + children: [ + SvgPicture.asset( + 'assets/icons/double_tick.svg', + ), + const SizedBox( + width: 8, + ), + Expanded( + child: RichText( + text: TextSpan( + text: + 'By creating an account I confirm I read and agree to the ', style: Theme.of(context) .textTheme .bodySmall - ?.copyWith(color: ColorPalette.dReaderYellow100), + ?.copyWith(color: ColorPalette.greyscale200), + children: [ + TextSpan( + text: 'Terms of Service ', + recognizer: TapGestureRecognizer() + ..onTap = () { + openUrl(Config.privacyPolicyUrl, + LaunchMode.inAppWebView); + }, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorPalette.dReaderYellow100), + ), + TextSpan( + text: '& ', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorPalette.greyscale200), + ), + TextSpan( + text: 'Privacy Policy', + recognizer: TapGestureRecognizer() + ..onTap = () { + openUrl(Config.privacyPolicyUrl, + LaunchMode.inAppWebView); + }, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorPalette.dReaderYellow100), + ), + ], ), - ], + ), ), - ), + ], ), const SizedBox( height: 48, @@ -149,7 +181,6 @@ class _SignUpStep1State extends ConsumerState { CustomTextButton( isLoading: ref.watch(globalNotifierProvider).isLoading, padding: const EdgeInsets.all(0), - isDisabled: !ref.watch(isTOSSelected), size: const Size( double.infinity, 50, diff --git a/lib/features/authentication/presentation/screens/sign_up/step_2_verification.dart b/lib/features/authentication/presentation/screens/sign_up/verification_step.dart similarity index 97% rename from lib/features/authentication/presentation/screens/sign_up/step_2_verification.dart rename to lib/features/authentication/presentation/screens/sign_up/verification_step.dart index c07d649d..7252cdc4 100644 --- a/lib/features/authentication/presentation/screens/sign_up/step_2_verification.dart +++ b/lib/features/authentication/presentation/screens/sign_up/verification_step.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -class SignUpStep2Verification extends StatelessWidget { +class SignUpVerificationStep extends StatelessWidget { final Function() handleNext; - const SignUpStep2Verification({ + const SignUpVerificationStep({ super.key, required this.handleNext, }); diff --git a/lib/features/comic/presentation/providers/comic_providers.dart b/lib/features/comic/presentation/providers/comic_providers.dart index 9085a8f2..82c6b128 100644 --- a/lib/features/comic/presentation/providers/comic_providers.dart +++ b/lib/features/comic/presentation/providers/comic_providers.dart @@ -46,10 +46,8 @@ final paginatedComicsProvider = StateNotifierProvider.family< final comicSlugProvider = FutureProvider.autoDispose.family((ref, slug) async { - return ref - .read(comicRepositoryProvider) - .getComic(slug) - .then((result) => result.fold((exception) => null, (data) => data)); + return ref.read(comicRepositoryProvider).getComic(slug).then( + (result) => result.fold((exception) => throw exception, (data) => data)); }); final updateComicFavouriteProvider = diff --git a/lib/features/home/presentation/screens/initial.dart b/lib/features/home/presentation/screens/initial.dart index 7a8a4204..bafa2ecd 100644 --- a/lib/features/home/presentation/screens/initial.dart +++ b/lib/features/home/presentation/screens/initial.dart @@ -127,10 +127,12 @@ class InitialIntroScreen extends StatelessWidget { ref .read(signInControllerProvider.notifier) .googleSignIn( - onSuccess: () { + onSuccess: (bool isRegistered) { nextScreenCloseOthers( context: context, - path: RoutePath.home, + path: isRegistered + ? RoutePath.home + : RoutePath.signUp, ); }, onFail: (String message) { diff --git a/lib/features/home/presentation/screens/splash.dart b/lib/features/home/presentation/screens/splash.dart index 95085da7..e6c0128b 100644 --- a/lib/features/home/presentation/screens/splash.dart +++ b/lib/features/home/presentation/screens/splash.dart @@ -56,32 +56,9 @@ class _SplashViewState extends State with TickerProviderStateMixin { child: Center( child: FadeTransition( opacity: _fadeInFadeOut, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(), - SvgPicture.asset( - Config.whiteLogoPath, - height: 50, - ), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - border: Border.all( - color: ColorPalette.dReaderYellow100, - ), - borderRadius: BorderRadius.circular( - 8, - ), - ), - child: Text( - 'beta version', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: ColorPalette.dReaderYellow100, - ), - ), - ), - ], + child: SvgPicture.asset( + Config.whiteLogoPath, + height: 50, ), ), ), diff --git a/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart b/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart index 06d9edfc..6b16a007 100644 --- a/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart +++ b/lib/features/library/presentation/widgets/tabs/favorites/favorites.dart @@ -36,7 +36,7 @@ class FavoritesTab extends ConsumerWidget { height: 8, ), const Text( - 'Favorite comic first', + 'Favoritize comic first', style: TextStyle( fontSize: 14, letterSpacing: 0.2, diff --git a/lib/features/nft/presentation/screens/animations/mint_animation_screen.dart b/lib/features/nft/presentation/screens/animations/mint_animation_screen.dart index 63f8b4da..60ec1d12 100644 --- a/lib/features/nft/presentation/screens/animations/mint_animation_screen.dart +++ b/lib/features/nft/presentation/screens/animations/mint_animation_screen.dart @@ -358,7 +358,7 @@ class _DoneMintingAnimationState extends State final bool isLoading = ref.watch(globalNotifierProvider).isLoading; return CustomTextButton( - backgroundColor: ColorPalette.dReaderGreen, + backgroundColor: ColorPalette.dReaderYellow100, padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 8, diff --git a/lib/features/nft/presentation/screens/animations/open_nft_animation_screen.dart b/lib/features/nft/presentation/screens/animations/open_nft_animation_screen.dart index 4fe3cc0a..a7db73c1 100644 --- a/lib/features/nft/presentation/screens/animations/open_nft_animation_screen.dart +++ b/lib/features/nft/presentation/screens/animations/open_nft_animation_screen.dart @@ -47,7 +47,8 @@ class _OpenNftAnimationState extends ConsumerState onSuccess: (String nftAddress) { nextScreenReplace( context: context, - path: '${RoutePath.nftDetails}/$nftAddress', + path: + '${RoutePath.nftDetails}/$nftAddress', // TODO open eReader homeSubRoute: true, ); }, @@ -56,7 +57,7 @@ class _OpenNftAnimationState extends ConsumerState context.pop(); showSnackBar( context: context, - text: 'Failed to open.', + text: 'Failed to unwrap.', backgroundColor: ColorPalette.dReaderRed, ); return; diff --git a/lib/features/nft/presentation/screens/nft_details.dart b/lib/features/nft/presentation/screens/nft_details.dart index a75ecfe8..7f3d0be7 100644 --- a/lib/features/nft/presentation/screens/nft_details.dart +++ b/lib/features/nft/presentation/screens/nft_details.dart @@ -53,12 +53,6 @@ class NftDetails extends ConsumerWidget { text: openResponse, ); } - showSnackBar( - context: context, - text: 'NFT Unwrapped successfully', - backgroundColor: ColorPalette.dReaderGreen, - ); - ref.invalidate(lastProcessedNftProvider); ref.invalidate(nftsProvider); ref.invalidate(nftProvider); @@ -66,6 +60,11 @@ class NftDetails extends ConsumerWidget { ref.invalidate(ownedIssuesAsyncProvider); ref.invalidate(comicIssuePagesProvider); ref.invalidate(comicIssueDetailsProvider); + showSnackBar( + context: context, + text: 'Comic unwrapped successfully', + backgroundColor: ColorPalette.dReaderGreen, + ); } @override @@ -178,10 +177,10 @@ class NftDetails extends ConsumerWidget { ), Expanded( child: Button( - borderColor: ColorPalette.dReaderGreen, + borderColor: ColorPalette.dReaderYellow100, isLoading: ref.watch(globalNotifierProvider).isLoading, - loadingColor: ColorPalette.dReaderGreen, + loadingColor: ColorPalette.dReaderYellow100, onPressed: () async { if (nft.isUsed) { return nextScreenPush( @@ -222,9 +221,9 @@ class NftDetails extends ConsumerWidget { ); }, child: Text( - nft.isUsed ? 'Read' : 'Open', + nft.isUsed ? 'Read' : 'Unwrap', style: textTheme.titleMedium?.copyWith( - color: ColorPalette.dReaderGreen, + color: ColorPalette.dReaderYellow100, ), ), ), diff --git a/lib/features/settings/presentation/providers/change_network.g.dart b/lib/features/settings/presentation/providers/change_network.g.dart index a9c0a827..a3776e79 100644 --- a/lib/features/settings/presentation/providers/change_network.g.dart +++ b/lib/features/settings/presentation/providers/change_network.g.dart @@ -7,7 +7,7 @@ part of 'change_network.dart'; // ************************************************************************** String _$changeNetworkControllerHash() => - r'6ad2b48337d9ea11a658b001f027337a89635604'; + r'ff62d7ea95d5b8268c597a092c9c9d2aeb036e6f'; /// See also [ChangeNetworkController]. @ProviderFor(ChangeNetworkController) diff --git a/lib/features/settings/presentation/providers/security_and_privacy.g.dart b/lib/features/settings/presentation/providers/security_and_privacy.g.dart index 9acd1184..c46727ab 100644 --- a/lib/features/settings/presentation/providers/security_and_privacy.g.dart +++ b/lib/features/settings/presentation/providers/security_and_privacy.g.dart @@ -7,7 +7,7 @@ part of 'security_and_privacy.dart'; // ************************************************************************** String _$securityAndPrivacyControllerHash() => - r'15833a16a90c400bbdf64d839353589ee1a3f2af'; + r'b0d6c0f111ba747d649d5e04c8598eac56f58c22'; /// See also [SecurityAndPrivacyController]. @ProviderFor(SecurityAndPrivacyController) diff --git a/lib/features/settings/presentation/screens/profile/profile.dart b/lib/features/settings/presentation/screens/profile/profile.dart index fbf64431..9b8f4b31 100644 --- a/lib/features/settings/presentation/screens/profile/profile.dart +++ b/lib/features/settings/presentation/screens/profile/profile.dart @@ -13,6 +13,7 @@ import 'package:d_reader_flutter/shared/presentations/providers/global/global_pr import 'package:d_reader_flutter/shared/theme/app_colors.dart'; import 'package:d_reader_flutter/shared/utils/screen_navigation.dart'; import 'package:d_reader_flutter/shared/utils/show_snackbar.dart'; +import 'package:d_reader_flutter/shared/utils/validation.dart'; import 'package:d_reader_flutter/shared/widgets/buttons/custom_text_button.dart'; import 'package:d_reader_flutter/shared/widgets/textfields/text_field.dart'; import 'package:d_reader_flutter/features/settings/presentation/widgets/list_tile.dart'; @@ -25,19 +26,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; class ProfileView extends HookConsumerWidget { const ProfileView({super.key}); - String? _validateUsername({required String? value, required WidgetRef ref}) { - if (value == null || value.isEmpty) { - return "Please enter username."; - } else if (value.length > 20) { - return "Must be less than 20 characters."; - } else if (value.length < 2) { - return "Must be greater than 2 characters."; - } else if (!usernameRegex.hasMatch(value)) { - return 'Letters, numbers, hyphens and dashes are allowed.'; - } - return null; - } - @override Widget build(BuildContext context, WidgetRef ref) { final provider = ref.watch(myUserProvider); @@ -160,16 +148,14 @@ class ProfileView extends HookConsumerWidget { labelText: 'Username', defaultValue: user.name.isNotEmpty ? user.name : null, - onValidate: (value) { - return _validateUsername(value: value, ref: ref); - }, + onValidate: usernameValidation, onChange: (String value) { ref.read(usernameTextProvider.notifier).state = value; }, ), const Text( - 'Must be 2 to 20 characters long. Letters, numbers and dashes are allowed.', + usernameCriteriaText, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, diff --git a/lib/features/settings/presentation/screens/root.dart b/lib/features/settings/presentation/screens/root.dart index 05c32665..eb1879bc 100644 --- a/lib/features/settings/presentation/screens/root.dart +++ b/lib/features/settings/presentation/screens/root.dart @@ -7,6 +7,7 @@ import 'package:d_reader_flutter/shared/utils/screen_navigation.dart'; import 'package:d_reader_flutter/features/settings/presentation/widgets/list_tile.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; class SettingsRootView extends StatelessWidget { const SettingsRootView({ @@ -115,7 +116,7 @@ class SettingsRootView extends StatelessWidget { title: 'FAQ', leadingPath: '${Config.settingsAssetsPath}/light/info_square.svg', onTap: () { - openUrl('https://dreader.app/faq'); + openUrl('https://dreader.app/faq', LaunchMode.inAppWebView); }, ), ], diff --git a/lib/features/wallet/presentation/providers/wallet_notifier.g.dart b/lib/features/wallet/presentation/providers/wallet_notifier.g.dart index e99126c9..1a0b8b1b 100644 --- a/lib/features/wallet/presentation/providers/wallet_notifier.g.dart +++ b/lib/features/wallet/presentation/providers/wallet_notifier.g.dart @@ -6,7 +6,7 @@ part of 'wallet_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$walletControllerHash() => r'db80c8d40f427bcd29bc3d4ba02c2871216148c5'; +String _$walletControllerHash() => r'56e743ffbc0bc1da3c9f6a67760a43e0baf329f5'; /// See also [WalletController]. @ProviderFor(WalletController) diff --git a/lib/shared/data/remote/dio_network_service.dart b/lib/shared/data/remote/dio_network_service.dart index 74be6f42..5e86de51 100644 --- a/lib/shared/data/remote/dio_network_service.dart +++ b/lib/shared/data/remote/dio_network_service.dart @@ -42,14 +42,18 @@ class DioNetworkService extends NetworkService with ExceptionHandlerMixin { } @override - Future> post(String endpoint, - {Map? data}) { + Future> post( + String endpoint, { + Map? data, + Map? headers, + }) { return handleExceptionWrapper( endpoint: endpoint, handler: () { return dio.post( endpoint, data: data, + options: Options(headers: headers), ); }, ); diff --git a/lib/shared/data/remote/network_service.dart b/lib/shared/data/remote/network_service.dart index a4f643d9..9a9d2967 100644 --- a/lib/shared/data/remote/network_service.dart +++ b/lib/shared/data/remote/network_service.dart @@ -18,6 +18,7 @@ abstract class NetworkService { Future> post( String endpoint, { Map? data, + Map? headers, }); Future> patch( diff --git a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart index 9a9d9e3c..d82fabe0 100644 --- a/lib/shared/domain/providers/solana/solana_transaction_notifier.dart +++ b/lib/shared/domain/providers/solana/solana_transaction_notifier.dart @@ -524,7 +524,7 @@ class SolanaTransactionNotifier extends _$SolanaTransactionNotifier { AppException( identifier: 'SolanaTransactionNotifier.list', statusCode: 500, - message: 'Failed to open nft', + message: 'Failed to unwrap nft', ), ); } diff --git a/lib/shared/domain/providers/solana/solana_transaction_notifier.g.dart b/lib/shared/domain/providers/solana/solana_transaction_notifier.g.dart index 26a830ec..3c6becff 100644 --- a/lib/shared/domain/providers/solana/solana_transaction_notifier.g.dart +++ b/lib/shared/domain/providers/solana/solana_transaction_notifier.g.dart @@ -7,7 +7,7 @@ part of 'solana_transaction_notifier.dart'; // ************************************************************************** String _$solanaTransactionNotifierHash() => - r'6a91d51e015b57d2efabb80d806f09fe19f8cef9'; + r'3a9235eb7a03fc64c870318f09d5c3d6d42076ff'; /// See also [SolanaTransactionNotifier]. @ProviderFor(SolanaTransactionNotifier) diff --git a/lib/shared/utils/validation.dart b/lib/shared/utils/validation.dart new file mode 100644 index 00000000..ce65cc29 --- /dev/null +++ b/lib/shared/utils/validation.dart @@ -0,0 +1,12 @@ +import 'package:d_reader_flutter/constants/constants.dart'; + +String? usernameValidation(String? username) { + if (username == null || username.isEmpty) { + return 'Field cannot be empty.'; + } else if (username.length < 3 || username.length > 20) { + return 'Username must be 3 to 20 characters long.'; + } else if (!usernameRegex.hasMatch(username)) { + return 'Letters, numbers, dashes and underscores are allowed.'; + } + return null; +} diff --git a/lib/shared/widgets/layout/bottom_navigation_item_icon.dart b/lib/shared/widgets/layout/bottom_navigation_item_icon.dart index c540031b..eaaa43a2 100644 --- a/lib/shared/widgets/layout/bottom_navigation_item_icon.dart +++ b/lib/shared/widgets/layout/bottom_navigation_item_icon.dart @@ -13,17 +13,14 @@ class BottomNavigationItemIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - imagePath, - colorFilter: isActive - ? const ColorFilter.mode( - ColorPalette.dReaderYellow100, - BlendMode.srcIn, - ) - : null, - ), + return SvgPicture.asset( + imagePath, + colorFilter: isActive + ? const ColorFilter.mode( + ColorPalette.dReaderYellow100, + BlendMode.srcIn, + ) + : null, ); } }