diff --git a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart index 573fcef8..1907cb09 100644 --- a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart +++ b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource.dart @@ -2,4 +2,8 @@ abstract class AuthRemoteDatasource { Future requestPhoneCode({required String phone}); Future verifyPhoneCode({required String phone, required String code}); + + Future login({required String email, required String password}); + + Future twoFALogin({required String token, required String code}); } diff --git a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart index 1053f587..0510575a 100644 --- a/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart +++ b/modules/auth/lib/src/core/data/datasource/auth_remote_datasource_impl.dart @@ -53,4 +53,33 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource { return Exception(message); } + + @override + Future login({ + required String email, + required String password, + }) async { + try { + final response = await _repository.post>( + '/auth/login', + body: {'email': email, 'password': password}, + ); + final token = response.data!['token']; + return token; + } on DioException catch (error) { + throw _mapDioError(error, defaultMessage: 'Error in login'); + } + } + + @override + Future twoFALogin({required String token, required String code}) async { + try { + await _repository.post>( + '/auth/login', + body: {'token': token, 'password': code}, + ); + } on DioException catch (error) { + throw _mapDioError(error, defaultMessage: 'Error in login'); + } + } } diff --git a/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart b/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart index 127c6070..1bfc3593 100644 --- a/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart +++ b/modules/auth/lib/src/core/data/repositories/auth_repository_impl.dart @@ -15,4 +15,14 @@ class AuthRepositoryImpl implements AuthRepository { Future verifyPhoneCode({required String phone, required String code}) { return _remote.verifyPhoneCode(phone: phone, code: code); } + + @override + Future login({required String email, required String password}) { + return _remote.login(email: email, password: password); + } + + @override + Future twoFALogin({required String token, required String code}) { + return _remote.twoFALogin(token: token, code: code); + } } diff --git a/modules/auth/lib/src/core/domain/repositories/auth_repository.dart b/modules/auth/lib/src/core/domain/repositories/auth_repository.dart index 4584b9db..43c8fdb4 100644 --- a/modules/auth/lib/src/core/domain/repositories/auth_repository.dart +++ b/modules/auth/lib/src/core/domain/repositories/auth_repository.dart @@ -2,4 +2,8 @@ abstract class AuthRepository { Future requestPhoneCode({required String phone}); Future verifyPhoneCode({required String phone, required String code}); + + Future login({required String email, required String password}); + + Future twoFALogin({required String token, required String code}); } diff --git a/modules/auth/lib/src/features/device_sign_up/link_watch/create_profile_screen.dart b/modules/auth/lib/src/features/device_sign_up/link_watch/create_profile_screen.dart index 01b3e577..7c397094 100644 --- a/modules/auth/lib/src/features/device_sign_up/link_watch/create_profile_screen.dart +++ b/modules/auth/lib/src/features/device_sign_up/link_watch/create_profile_screen.dart @@ -17,16 +17,14 @@ class CreateProfileScreen extends ConsumerWidget { Text( "Comienza con un peque; luego podrás agregar más", textAlign: TextAlign.center, - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0), - ), - CustomTextField( - label: "Nombre", - hint: "Nombre", - ), - CustomTextField( - label: "Apellidos", - hint: "Apellidos", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), ), + CustomTextField(label: "Nombre", hint: "Nombre"), + CustomTextField(label: "Apellidos", hint: "Apellidos"), Column( spacing: 8, children: [ @@ -42,21 +40,21 @@ class CreateProfileScreen extends ConsumerWidget { children: [ Expanded( child: CustomTextField( - numeric: true, + keyboardType: TextInputType.number, hint: "DD", length: 2, ), ), Expanded( child: CustomTextField( - numeric: true, + keyboardType: TextInputType.number, hint: "MM", length: 2, ), ), Expanded( child: CustomTextField( - numeric: true, + keyboardType: TextInputType.number, hint: "AAAA", length: 4, ), @@ -77,7 +75,7 @@ class CreateProfileScreen extends ConsumerWidget { size: 18, weight: FontWeight.w500, ), - ) + ), ], ); } diff --git a/modules/auth/lib/src/features/link_phone/presentation/request_phone/request_link_phone_screen.dart b/modules/auth/lib/src/features/link_phone/presentation/request_phone/request_link_phone_screen.dart index 093aeda2..0385fdd5 100644 --- a/modules/auth/lib/src/features/link_phone/presentation/request_phone/request_link_phone_screen.dart +++ b/modules/auth/lib/src/features/link_phone/presentation/request_phone/request_link_phone_screen.dart @@ -67,7 +67,7 @@ class RequestLinkPhoneScreen extends ConsumerWidget { child: CustomTextField( controller: viewModel.phoneNumberController, hint: context.translate(I18n.phoneNumber), - numeric: true, + keyboardType: TextInputType.number, ), ), ], diff --git a/modules/auth/lib/src/features/login/presentation/loading_screen.dart b/modules/auth/lib/src/features/link_phone/presentation/widgets/loading_screen.dart similarity index 100% rename from modules/auth/lib/src/features/login/presentation/loading_screen.dart rename to modules/auth/lib/src/features/link_phone/presentation/widgets/loading_screen.dart diff --git a/modules/auth/lib/src/features/login/domain/login_use_case.dart b/modules/auth/lib/src/features/login/domain/login_use_case.dart new file mode 100644 index 00000000..f6ab5b70 --- /dev/null +++ b/modules/auth/lib/src/features/login/domain/login_use_case.dart @@ -0,0 +1,3 @@ +abstract class LoginUseCase { + Future login({required String email, required String password}); +} diff --git a/modules/auth/lib/src/features/login/domain/login_use_case_impl.dart b/modules/auth/lib/src/features/login/domain/login_use_case_impl.dart new file mode 100644 index 00000000..6688eee8 --- /dev/null +++ b/modules/auth/lib/src/features/login/domain/login_use_case_impl.dart @@ -0,0 +1,13 @@ +import 'package:auth/src/core/domain/repositories/auth_repository.dart'; +import 'package:auth/src/features/login/domain/login_use_case.dart'; + +class LoginUseCaseImpl implements LoginUseCase { + LoginUseCaseImpl(this._repository); + + final AuthRepository _repository; + + @override + Future login({required String email, required String password}) { + return _repository.login(email: email, password: password); + } +} diff --git a/modules/auth/lib/src/features/login/presentation/login_screen.dart b/modules/auth/lib/src/features/login/presentation/login_screen.dart index 9758e619..331c59e9 100644 --- a/modules/auth/lib/src/features/login/presentation/login_screen.dart +++ b/modules/auth/lib/src/features/login/presentation/login_screen.dart @@ -1,172 +1,165 @@ import 'package:auth/src/features/login/presentation/loading_google_screen.dart'; -import 'package:auth/src/features/sign_up/signup_screen.dart'; +import 'package:auth/src/features/login/presentation/state/login_view_model.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navigation/navigation.dart'; import 'package:sf_localizations/sf_localizations.dart'; -class LoginScreen extends ConsumerStatefulWidget { +class LoginScreen extends ConsumerWidget { final NavigationContract navigationContract; const LoginScreen({super.key, required this.navigationContract}); @override - ConsumerState createState() => _LoginScreenState(); -} - -class _LoginScreenState extends ConsumerState { - bool passwordVisible = false; - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themePortProvider); + final vm = ref.read(loginViewModelProvider.notifier); + final state = ref.watch(loginViewModelProvider); - bool passwordVisible = true; - - final content = [ - Column( - spacing: 8, - children: [ - Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), - size: 50, - ), - Text( - // context.translate(I18n.example) - "¡Te damos la bienvenida!", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), - ), - ], - ), - Column( - spacing: 32, - children: [ - Column( - spacing: 24, - children: [ - CustomTextField( - hint: "Nombre de usuario", - label: "Nombre de usuario", - ), - Column( - spacing: 12, - children: [ - CustomTextField( - showPassword: passwordVisible, - label: "Contraseña", - hint: "********", - ), - Align( - alignment: Alignment.topLeft, - child: CustomTextButton( - text: "¿Has olvidado la contraseña?", - onPressed: () => widget.navigationContract.pushTo( - AppRoutes.recoverPassword, - ), - size: 16, - ), - ), - ], - ), - ], - ), - PrimaryButton( - onPressed: () => - widget.navigationContract.goTo(AppRoutes.dashboardHome), - text: "Iniciar sesión", - color: theme.getColorFor(ThemeCode.buttonPrimary), - ), - Container( - padding: EdgeInsets.only(top: 24), - child: Column( - spacing: 24, - children: [ - Stack( - children: [ - Divider(endIndent: 74, indent: 74), - Align( - alignment: Alignment.center, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14), - color: theme.getColorFor(ThemeCode.backgroundPrimary), - child: Text("o continúa con"), - ), - ), - ], - ), - Row( - spacing: 20, - children: [ - Spacer(), - SecondaryButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => LoadingGoogleScreen(), - ), - ), - radius: 16, - padding: 44, - text: "Google", - label: "Google", - ), - SecondaryButton( - onPressed: () => {}, - radius: 16, - padding: 44, - icon: Icons.apple, - label: "Apple", - ), - Spacer(), - ], - ), - ], - ), - ), - Column( - spacing: 8, - children: [ - Text( - "¿No tienes cuenta?", - style: TextStyle(fontSize: 18, letterSpacing: 0), - ), - TextButton( - onPressed: () => - widget.navigationContract.goTo(AppRoutes.signup), - child: Text( - "Crear una ahora", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - letterSpacing: 0, - ), - ), - ), - ], - ), - ], - ), - ]; + Future onSignIn() async { + FocusScope.of(context).unfocus(); + final login = await vm.login(); + if (login) navigationContract.goTo(AppRoutes.dashboardHome); + } return Scaffold( backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), - body: Expanded( - child: Center( - child: Container( - margin: EdgeInsets.all(30), - child: ListView.separated( - itemBuilder: (BuildContext context, int index) { - return content[index]; - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(color: Colors.transparent, height: 48); - }, - itemCount: content.length, + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.check, + color: theme.getColorFor(ThemeCode.buttonPrimary), + size: 54, ), - ), + Text( + context.translate(I18n.welcome), + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 48), + + CustomTextField( + hint: context.translate(I18n.username), + label: context.translate(I18n.username), + controller: vm.emailController, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + ), + + const SizedBox(height: 24), + + CustomTextField( + showPassword: state.passwordVisible, + label: context.translate(I18n.password), + hint: "********", + controller: vm.passwordController, + + textInputAction: TextInputAction.done, + onSubmitted: (_) => onSignIn(), + ), + + const SizedBox(height: 16), + + Align( + alignment: Alignment.topLeft, + child: CustomTextButton( + text: context.translate(I18n.forgotPassword), + onPressed: state.isLoading + ? () {} + : () => + navigationContract.pushTo(AppRoutes.recoverPassword), + size: 16, + ), + ), + + const SizedBox(height: 30), + + PrimaryButton( + onPressed: state.isLoading ? () {} : onSignIn, + text: context.translate(I18n.signIn), + color: theme.getColorFor(ThemeCode.buttonPrimary), + leading: state.isLoading + ? const SizedBox( + height: 18, + width: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : null, + ), + + const SizedBox(height: 30), + + Stack( + children: [ + const Divider(endIndent: 74, indent: 74), + Align( + alignment: Alignment.center, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + child: Text(context.translate(I18n.orContinueWith)), + ), + ), + ], + ), + + const SizedBox(height: 24), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + onPressed: state.isLoading + ? () {} + : () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const LoadingGoogleScreen(), + ), + ), + radius: 16, + padding: 44, + text: context.translate(I18n.google), + label: 'Google', + ), + const SizedBox(width: 16), + SecondaryButton( + onPressed: state.isLoading ? () {} : () {}, + radius: 16, + padding: 44, + icon: Icons.apple, + label: 'Apple', + ), + ], + ), + + const SizedBox(height: 30), + + Text( + context.translate(I18n.dontHaveAccount), + style: const TextStyle(fontSize: 18, letterSpacing: 0), + ), + TextButton( + onPressed: state.isLoading + ? null + : () => navigationContract.goTo(AppRoutes.signup), + child: Text( + context.translate(I18n.createOneNow), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + ], ), ), ); diff --git a/modules/auth/lib/src/features/login/presentation/providers/login_provider.dart b/modules/auth/lib/src/features/login/presentation/providers/login_provider.dart new file mode 100644 index 00000000..55b2b0b7 --- /dev/null +++ b/modules/auth/lib/src/features/login/presentation/providers/login_provider.dart @@ -0,0 +1,9 @@ +import 'package:auth/src/core/providers/auth_repository_provider.dart'; +import 'package:auth/src/features/login/domain/login_use_case.dart'; +import 'package:auth/src/features/login/domain/login_use_case_impl.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final loginUseCaseProvider = Provider.autoDispose((ref) { + final authRepository = ref.read(authRepositoryProvider); + return LoginUseCaseImpl(authRepository); +}); diff --git a/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart b/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart new file mode 100644 index 00000000..28a3f5fa --- /dev/null +++ b/modules/auth/lib/src/features/login/presentation/state/login_view_model.dart @@ -0,0 +1,79 @@ +import 'package:auth/src/features/login/domain/login_use_case.dart'; +import 'package:auth/src/features/login/presentation/providers/login_provider.dart'; +import 'package:auth/src/features/login/presentation/state/login_view_state.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final loginViewModelProvider = + NotifierProvider.autoDispose( + LoginViewModel.new, + ); + +class LoginViewModel extends Notifier { + late final LoginUseCase _loginUseCase; + late final TextEditingController emailController; + late final TextEditingController passwordController; + + @override + LoginViewState build() { + _loginUseCase = ref.read(loginUseCaseProvider); + + emailController = TextEditingController(); + passwordController = TextEditingController(); + + emailController.addListener(_onEmailChanged); + passwordController.addListener(_onPasswordChanged); + + ref.onDispose(disposeControllers); + + return const LoginViewState(); + } + + void _onEmailChanged() { + if (emailController.text != state.email) { + state = state.copyWith(email: emailController.text, errorMessage: ''); + } + } + + void _onPasswordChanged() { + if (passwordController.text != state.password) { + state = state.copyWith( + password: passwordController.text, + errorMessage: '', + ); + } + } + + void togglePasswordVisible() { + state = state.copyWith(passwordVisible: !state.passwordVisible); + } + + Future login() async { + final email = state.email.trim(); + final password = state.password.trim(); + + if (email.isEmpty) { + state = state.copyWith(errorMessage: 'errorMessageIsEmpty'); + return false; + } + + state = state.copyWith(isLoading: true, errorMessage: ''); + try { + await _loginUseCase.login(email: email, password: password); + if (!ref.mounted) return false; + state = state.copyWith(isLoading: false); + return true; + } catch (e) { + if (!ref.mounted) return false; + state = state.copyWith(isLoading: false, errorMessage: e.toString()); + return false; + } + } + + void disposeControllers() { + emailController.removeListener(_onEmailChanged); + passwordController.removeListener(_onPasswordChanged); + emailController.dispose(); + passwordController.dispose(); + } +} diff --git a/modules/auth/lib/src/features/login/presentation/state/login_view_state.dart b/modules/auth/lib/src/features/login/presentation/state/login_view_state.dart new file mode 100644 index 00000000..5dfb3fbd --- /dev/null +++ b/modules/auth/lib/src/features/login/presentation/state/login_view_state.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_view_state.freezed.dart'; + +@freezed +abstract class LoginViewState with _$LoginViewState { + const factory LoginViewState({ + @Default('') String email, + @Default('') String password, + @Default(false) bool passwordVisible, + @Default('') String errorMessage, + @Default(false) bool isLoading, + }) = _LoginViewState; +} diff --git a/modules/auth/lib/src/features/login/presentation/state/login_view_state.freezed.dart b/modules/auth/lib/src/features/login/presentation/state/login_view_state.freezed.dart new file mode 100644 index 00000000..5d528dd7 --- /dev/null +++ b/modules/auth/lib/src/features/login/presentation/state/login_view_state.freezed.dart @@ -0,0 +1,283 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_view_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$LoginViewState { + + String get email; String get password; bool get passwordVisible; String get errorMessage; bool get isLoading; +/// Create a copy of LoginViewState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LoginViewStateCopyWith get copyWith => _$LoginViewStateCopyWithImpl(this as LoginViewState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,email,password,passwordVisible,errorMessage,isLoading); + +@override +String toString() { + return 'LoginViewState(email: $email, password: $password, passwordVisible: $passwordVisible, errorMessage: $errorMessage, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class $LoginViewStateCopyWith<$Res> { + factory $LoginViewStateCopyWith(LoginViewState value, $Res Function(LoginViewState) _then) = _$LoginViewStateCopyWithImpl; +@useResult +$Res call({ + String email, String password, bool passwordVisible, String errorMessage, bool isLoading +}); + + + + +} +/// @nodoc +class _$LoginViewStateCopyWithImpl<$Res> + implements $LoginViewStateCopyWith<$Res> { + _$LoginViewStateCopyWithImpl(this._self, this._then); + + final LoginViewState _self; + final $Res Function(LoginViewState) _then; + +/// Create a copy of LoginViewState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? email = null,Object? password = null,Object? passwordVisible = null,Object? errorMessage = null,Object? isLoading = null,}) { + return _then(_self.copyWith( +email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String,passwordVisible: null == passwordVisible ? _self.passwordVisible : passwordVisible // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [LoginViewState]. +extension LoginViewStatePatterns on LoginViewState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _LoginViewState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _LoginViewState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _LoginViewState value) $default,){ +final _that = this; +switch (_that) { +case _LoginViewState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LoginViewState value)? $default,){ +final _that = this; +switch (_that) { +case _LoginViewState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String email, String password, bool passwordVisible, String errorMessage, bool isLoading)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _LoginViewState() when $default != null: +return $default(_that.email,_that.password,_that.passwordVisible,_that.errorMessage,_that.isLoading);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String email, String password, bool passwordVisible, String errorMessage, bool isLoading) $default,) {final _that = this; +switch (_that) { +case _LoginViewState(): +return $default(_that.email,_that.password,_that.passwordVisible,_that.errorMessage,_that.isLoading);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String email, String password, bool passwordVisible, String errorMessage, bool isLoading)? $default,) {final _that = this; +switch (_that) { +case _LoginViewState() when $default != null: +return $default(_that.email,_that.password,_that.passwordVisible,_that.errorMessage,_that.isLoading);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _LoginViewState implements LoginViewState { + const _LoginViewState({this.email = '', this.password = '', this.passwordVisible = false, this.errorMessage = '', this.isLoading = false}); + + +@override@JsonKey() final String email; +@override@JsonKey() final String password; +@override@JsonKey() final bool passwordVisible; +@override@JsonKey() final String errorMessage; +@override@JsonKey() final bool isLoading; + +/// Create a copy of LoginViewState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LoginViewStateCopyWith<_LoginViewState> get copyWith => __$LoginViewStateCopyWithImpl<_LoginViewState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,email,password,passwordVisible,errorMessage,isLoading); + +@override +String toString() { + return 'LoginViewState(email: $email, password: $password, passwordVisible: $passwordVisible, errorMessage: $errorMessage, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class _$LoginViewStateCopyWith<$Res> implements $LoginViewStateCopyWith<$Res> { + factory _$LoginViewStateCopyWith(_LoginViewState value, $Res Function(_LoginViewState) _then) = __$LoginViewStateCopyWithImpl; +@override @useResult +$Res call({ + String email, String password, bool passwordVisible, String errorMessage, bool isLoading +}); + + + + +} +/// @nodoc +class __$LoginViewStateCopyWithImpl<$Res> + implements _$LoginViewStateCopyWith<$Res> { + __$LoginViewStateCopyWithImpl(this._self, this._then); + + final _LoginViewState _self; + final $Res Function(_LoginViewState) _then; + +/// Create a copy of LoginViewState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? email = null,Object? password = null,Object? passwordVisible = null,Object? errorMessage = null,Object? isLoading = null,}) { + return _then(_LoginViewState( +email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String,passwordVisible: null == passwordVisible ? _self.passwordVisible : passwordVisible // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/modules/auth/lib/src/features/recover_password/presentation/new_password_screen.dart b/modules/auth/lib/src/features/recover_password/presentation/new_password_screen.dart index 866fe532..38a5b814 100644 --- a/modules/auth/lib/src/features/recover_password/presentation/new_password_screen.dart +++ b/modules/auth/lib/src/features/recover_password/presentation/new_password_screen.dart @@ -57,7 +57,11 @@ class NewPasswordScreenState extends ConsumerState { const Text( "Recuperar contraseña", textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 30, + letterSpacing: 0, + ), ), Column( spacing: 16, @@ -91,11 +95,16 @@ class NewPasswordScreenState extends ConsumerState { children: [ Icon( Icons.check, - color: theme.getColorFor(securityChecks["min"]! - ? ThemeCode.buttonPrimary - : ThemeCode.buttonSecondary), + color: theme.getColorFor( + securityChecks["min"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary, + ), + ), + const Text( + "Al menos 8 caracteres", + style: TextStyle(fontSize: 14), ), - const Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)), ], ), Row( @@ -103,11 +112,16 @@ class NewPasswordScreenState extends ConsumerState { children: [ Icon( Icons.check, - color: theme.getColorFor(securityChecks["capital"]! - ? ThemeCode.buttonPrimary - : ThemeCode.buttonSecondary), + color: theme.getColorFor( + securityChecks["capital"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary, + ), + ), + const Text( + "Una mayúscula", + style: TextStyle(fontSize: 14), ), - const Text("Una mayúscula", style: TextStyle(fontSize: 14)), ], ), Row( @@ -115,11 +129,16 @@ class NewPasswordScreenState extends ConsumerState { children: [ Icon( Icons.check, - color: theme.getColorFor(securityChecks["number"]! - ? ThemeCode.buttonPrimary - : ThemeCode.buttonSecondary), + color: theme.getColorFor( + securityChecks["number"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary, + ), + ), + const Text( + "Un número", + style: TextStyle(fontSize: 14), ), - const Text("Un número", style: TextStyle(fontSize: 14)), ], ), Row( @@ -127,15 +146,20 @@ class NewPasswordScreenState extends ConsumerState { children: [ Icon( Icons.check, - color: theme.getColorFor(securityChecks["special"]! - ? ThemeCode.buttonPrimary - : ThemeCode.buttonSecondary), + color: theme.getColorFor( + securityChecks["special"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary, + ), + ), + const Text( + "Un carácter especial", + style: TextStyle(fontSize: 14), ), - const Text("Un carácter especial", style: TextStyle(fontSize: 14)), ], ), ], - ) + ), ], ), Column( @@ -146,32 +170,39 @@ class NewPasswordScreenState extends ConsumerState { child: const Text( "Teléfono móvil", style: TextStyle(fontSize: 14, letterSpacing: 0), - ) + ), ), Row( spacing: 8, children: [ CustomDropdown( value: 0, - items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)], - onChanged: (value)=> {}, + items: [ + Icon(Icons.outlined_flag), + Icon(Icons.outlined_flag), + Icon(Icons.outlined_flag), + ], + onChanged: (value) => {}, width: 80, ), - Expanded(child: CustomTextField( - hint: "Teléfono", - numeric: true - )) - ] + Expanded( + child: CustomTextField( + hint: "Teléfono", + keyboardType: TextInputType.number, + ), + ), + ], ), ], - ) - + ), ], ), PrimaryButton( - onPressed: ()=>{navigationContract.goTo(AppRoutes.dashboardHome)}, + onPressed: () => { + navigationContract.goTo(AppRoutes.dashboardHome), + }, text: "Aceptar", - color: theme.getColorFor(ThemeCode.buttonPrimary) + color: theme.getColorFor(ThemeCode.buttonPrimary), ), const Spacer(), ], diff --git a/modules/auth/lib/src/features/recover_password/presentation/restore_password_screen.dart b/modules/auth/lib/src/features/recover_password/presentation/restore_password_screen.dart index 7068801f..00100938 100644 --- a/modules/auth/lib/src/features/recover_password/presentation/restore_password_screen.dart +++ b/modules/auth/lib/src/features/recover_password/presentation/restore_password_screen.dart @@ -69,7 +69,7 @@ class RestorePasswordScreen extends ConsumerWidget { Expanded( child: CustomTextField( hint: "Teléfono", - numeric: true, + keyboardType: TextInputType.number, ), ), ], diff --git a/modules/auth/lib/src/features/sign_up/signup_address_screen.dart b/modules/auth/lib/src/features/sign_up/signup_address_screen.dart index dc967a94..d1db0593 100644 --- a/modules/auth/lib/src/features/sign_up/signup_address_screen.dart +++ b/modules/auth/lib/src/features/sign_up/signup_address_screen.dart @@ -1,4 +1,4 @@ - import 'package:design_system/design_system.dart'; +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -6,12 +6,11 @@ class SignupAddressScreen extends ConsumerStatefulWidget { const SignupAddressScreen({super.key}); @override - ConsumerState createState() => SignupAddressScreenState(); - + ConsumerState createState() => + SignupAddressScreenState(); } -class SignupAddressScreenState extends ConsumerState{ - +class SignupAddressScreenState extends ConsumerState { late String country; late int relation; @@ -29,30 +28,39 @@ class SignupAddressScreenState extends ConsumerState{ Column( spacing: 8, children: [ - Align(alignment: Alignment.bottomLeft, child: Text( - "Fecha de nacimiento", - style: TextStyle(fontSize: 14, letterSpacing: 0), - )), + Align( + alignment: Alignment.bottomLeft, + child: Text( + "Fecha de nacimiento", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), Row( - spacing: 8, - children: [ - Expanded(child: CustomTextField( + spacing: 8, + children: [ + Expanded( + child: CustomTextField( //label: "Fecha de nacimiento", hint: "DD", length: 2, - numeric: true, - )), - Expanded(child: CustomTextField( + keyboardType: TextInputType.number, + ), + ), + Expanded( + child: CustomTextField( hint: "MM", length: 2, - numeric: true, - )), - Expanded(child: CustomTextField( + keyboardType: TextInputType.number, + ), + ), + Expanded( + child: CustomTextField( hint: "AAAA", length: 4, - numeric: true, - )), - ] + keyboardType: TextInputType.number, + ), + ), + ], ), ], ), @@ -69,13 +77,16 @@ class SignupAddressScreenState extends ConsumerState{ CustomDropdown( items: [Text("Padre"), Text("Madre"), Text("Tutor")], hint: "¿Qué familiar eres?", - onChanged: (value)=>setState(() { + onChanged: (value) => setState(() { relation = value; - }) + }), ), ], ), - CustomTextField(label: "Dirección completa", hint: "Calle Gran Vía 30 6º, 28013"), + CustomTextField( + label: "Dirección completa", + hint: "Calle Gran Vía 30 6º, 28013", + ), CustomTextField(label: "Ciudad", hint: "Ciudad"), Column( spacing: 8, @@ -90,9 +101,9 @@ class SignupAddressScreenState extends ConsumerState{ CustomDropdown( items: [Text("España"), Text("Francia"), Text("Portugal")], hint: "País", - onChanged: (value)=>setState(() { + onChanged: (value) => setState(() { country = value; - }) + }), ), ], ), diff --git a/modules/auth/lib/src/features/sign_up/signup_personal_screen.dart b/modules/auth/lib/src/features/sign_up/signup_personal_screen.dart index 1afc1186..e39e504e 100644 --- a/modules/auth/lib/src/features/sign_up/signup_personal_screen.dart +++ b/modules/auth/lib/src/features/sign_up/signup_personal_screen.dart @@ -1,7 +1,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -class SignupPersonalScreen extends StatelessWidget{ +class SignupPersonalScreen extends StatelessWidget { const SignupPersonalScreen({super.key}); @override @@ -21,16 +21,20 @@ class SignupPersonalScreen extends StatelessWidget{ onChanged: (value)=> {}, width: 80, ),*/ - Expanded(child: CustomTextField( - label: "Teléfono móvil", - hint: "123456789", - numeric: true - )) - ] + Expanded( + child: CustomTextField( + label: "Teléfono móvil", + hint: "123456789", + keyboardType: TextInputType.number, + ), + ), + ], + ), + CustomTextField( + label: "Correo electrónico", + hint: "Correo electrónico", ), - CustomTextField(label: "Correo electrónico", hint: "Correo electrónico"), ], ); } - -} \ No newline at end of file +} diff --git a/modules/home/lib/src/presentation/deposit_screen.dart b/modules/home/lib/src/presentation/deposit_screen.dart index 73dc9d60..e401153d 100644 --- a/modules/home/lib/src/presentation/deposit_screen.dart +++ b/modules/home/lib/src/presentation/deposit_screen.dart @@ -32,9 +32,9 @@ class _DepositScreenState extends ConsumerState { child: Column( children: [ PrimaryButton( - onPressed: ()=>{}, + onPressed: () => {}, text: "Añadir dinero", - color: theme.getColorFor(ThemeCode.buttonPrimary) + color: theme.getColorFor(ThemeCode.buttonPrimary), ), TextButton( onPressed: () => Navigator.pop(context), @@ -58,7 +58,7 @@ class _DepositScreenState extends ConsumerState { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), CustomTextField( - numeric: true, + keyboardType: TextInputType.number, label: "Cantidad", hint: "0€", ), @@ -135,7 +135,8 @@ class _DepositScreenState extends ConsumerState { CustomTextField( lines: 3, length: 150, - label: "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", + label: + "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", hint: "Escribe tu mensaje", ), const Align( @@ -191,4 +192,4 @@ class _DepositScreenState extends ConsumerState { ], ); } -} \ No newline at end of file +} diff --git a/modules/home/lib/src/presentation/extract_screen.dart b/modules/home/lib/src/presentation/extract_screen.dart index 6ce326ca..82f4aada 100644 --- a/modules/home/lib/src/presentation/extract_screen.dart +++ b/modules/home/lib/src/presentation/extract_screen.dart @@ -5,14 +5,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:home/src/presentation/wallet_management_layout.dart'; import 'package:sf_shared/sf_shared.dart'; -class ExtractScreen extends ConsumerWidget{ +class ExtractScreen extends ConsumerWidget { final Kid kid; @override - ExtractScreen({ - super.key, - required this.kid, - }); + ExtractScreen({super.key, required this.kid}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -20,92 +17,125 @@ class ExtractScreen extends ConsumerWidget{ return WalletManagementLayout( kid: kid, - children: [Container( - padding: EdgeInsets.all(24), - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(24)), - color: theme.getColorFor(ThemeCode.backgroundPrimary) + children: [ + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(24)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + child: Column( + spacing: 24, + children: [ + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Retirar dinero de la cuenta", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + Align( + alignment: Alignment.topLeft, + child: Text( + "Este dato aparecerá en el reloj del peque", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + ], + ), + CustomTextField( + label: "Selecciona la cantidad de dinero", + hint: "2€", + keyboardType: TextInputType.number, + ), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Este es el mensaje fijado por defecto:", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ), + Align( + alignment: Alignment.topLeft, + child: Text( + "\"Hemos quitado el dinero del reloj, ya no puedes pagar con él\"", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + ], + ), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Escribir mensaje a ${kid.name} del motivo de la retirada de su dinero", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + CustomTextField( + hint: "Escribe tu mensaje", + lines: 4, + length: 150, + ), + Row( + spacing: 4, + children: [ + Icon(Icons.info_outline, size: 16), + Text( + "Máximo 150 caracteres", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ], + ), + ], + ), + ], + ), ), - child: Column( - spacing: 24, - children: [ - Column( - spacing: 8, - children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Retirar dinero de la cuenta", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0) - )), - Align(alignment: Alignment.topLeft, child: Text( - "Este dato aparecerá en el reloj del peque", - style: TextStyle(fontSize: 14, letterSpacing: 0) - )) - ], - ), - CustomTextField( - label: "Selecciona la cantidad de dinero", - hint: "2€", - numeric: true, - ), - Column( - spacing: 8, - children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Este es el mensaje fijado por defecto:", - style: TextStyle(fontSize: 16, letterSpacing: 0) - )), - Align(alignment: Alignment.topLeft, child: Text( - "\"Hemos quitado el dinero del reloj, ya no puedes pagar con él\"", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0) - )) - ], - ), - Column( - spacing: 8, - children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Escribir mensaje a ${kid.name} del motivo de la retirada de su dinero", - style: TextStyle(fontSize: 14, letterSpacing: 0) - )), - CustomTextField( - hint: "Escribe tu mensaje", - lines: 4, - length: 150, - ), - Row( - spacing: 4, - children: [ - Icon(Icons.info_outline, size: 16), - Text("Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0)) - ], - ) - ], - ) - ], - ), - )], + ], footer: Container( padding: EdgeInsets.all(24), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.only(topRight: Radius.circular(24), topLeft: Radius.circular(24)) + borderRadius: BorderRadius.only( + topRight: Radius.circular(24), + topLeft: Radius.circular(24), + ), ), child: Column( spacing: 16, children: [ PrimaryButton( - onPressed: ()=>{Navigator.pop(context)}, + onPressed: () => {Navigator.pop(context)}, text: "Enviar mensaje y bloquear", color: theme.getColorFor(ThemeCode.buttonPrimary), ), TextButton( - style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), - onPressed: ()=>Navigator.pop(context), - child: Text("Cancelar", style: TextStyle(fontSize: 18)) - ) + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.all(0)), + ), + onPressed: () => Navigator.pop(context), + child: Text("Cancelar", style: TextStyle(fontSize: 18)), + ), ], ), - ) + ), ); } -} \ No newline at end of file +} diff --git a/modules/home/lib/src/presentation/goals_screen.dart b/modules/home/lib/src/presentation/goals_screen.dart index c025904b..26ecfadf 100644 --- a/modules/home/lib/src/presentation/goals_screen.dart +++ b/modules/home/lib/src/presentation/goals_screen.dart @@ -5,14 +5,20 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:home/src/presentation/wallet_management_layout.dart'; import 'package:sf_shared/sf_shared.dart'; -class GoalsScreen extends ConsumerStatefulWidget{ +class GoalsScreen extends ConsumerStatefulWidget { final Kid kid; - List tasks = [Task(rewardAmount: 10, subtasks: [ - Subtask(name: "Hacer la cama", completed: false), - Subtask(name: "Terminar los deberes", completed: true), - Subtask(name: "Recoger la mesa", completed: true), - Subtask(name: "Lavarse los dientes", completed: false) - ]), Task(rewardAmount: 10)]; + List tasks = [ + Task( + rewardAmount: 10, + subtasks: [ + Subtask(name: "Hacer la cama", completed: false), + Subtask(name: "Terminar los deberes", completed: true), + Subtask(name: "Recoger la mesa", completed: true), + Subtask(name: "Lavarse los dientes", completed: false), + ], + ), + Task(rewardAmount: 10), + ]; final List savingsGoals; @override @@ -21,15 +27,15 @@ class GoalsScreen extends ConsumerStatefulWidget{ required this.kid, this.savingsGoals = const [ SavingsGoal(name: "Cumpleaños de Emma", goal: 24, saved: 12.09), - SavingsGoal(name: "Protecciones nuevas de patines", goal: 10, saved: 0) - ] + SavingsGoal(name: "Protecciones nuevas de patines", goal: 10, saved: 0), + ], }); @override ConsumerState createState() => GoalsScreenState(); } -class GoalsScreenState extends ConsumerState{ +class GoalsScreenState extends ConsumerState { late bool fullPlan; @override @@ -49,7 +55,7 @@ class GoalsScreenState extends ConsumerState{ padding: EdgeInsets.all(24), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(24)) + borderRadius: BorderRadius.all(Radius.circular(24)), ), child: Column( spacing: 24, @@ -57,10 +63,20 @@ class GoalsScreenState extends ConsumerState{ Row( spacing: 8, children: [ - Text("Metas", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)), + Text( + "Metas", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), Icon(Icons.workspace_premium), Spacer(), - Text("Sólo con Plan Completo", style: TextStyle(fontSize: 14, letterSpacing: 0)) + Text( + "Sólo con Plan Completo", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), ], ), Text( @@ -73,7 +89,7 @@ class GoalsScreenState extends ConsumerState{ SavingsBlock(fullPlan: fullPlan, savings: widget.savingsGoals), TasksBlock(fullPlan: fullPlan, tasks: widget.tasks), ], - footer: Container() + footer: Container(), ); } } @@ -86,14 +102,14 @@ class SavingsBlock extends ConsumerStatefulWidget { const SavingsBlock({ super.key, required this.fullPlan, - required this.savings + required this.savings, }); @override ConsumerState createState() => SavingsBlockState(); } -class SavingsBlockState extends ConsumerState{ +class SavingsBlockState extends ConsumerState { late List showEdit; late List showAdd; late bool showNew; @@ -101,8 +117,8 @@ class SavingsBlockState extends ConsumerState{ @override void initState() { super.initState(); - showEdit = widget.savings.map((_)=>false).toList(); - showAdd = widget.savings.map((_)=>false).toList(); + showEdit = widget.savings.map((_) => false).toList(); + showAdd = widget.savings.map((_) => false).toList(); showNew = false; } @@ -115,82 +131,105 @@ class SavingsBlockState extends ConsumerState{ decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(24)), - border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary)) + border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary)), ), child: Stack( children: [ - Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/ahorros.svg")), + Align( + alignment: Alignment.topRight, + child: SvgPicture.asset("assets/images/ui/ahorros.svg"), + ), Column( spacing: 24, children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Ahorros", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), - )), - Align(alignment: Alignment.topLeft, child: SizedBox( - width: 200, + Align( + alignment: Alignment.topLeft, child: Text( - "Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero", - style: TextStyle(fontSize: 14, letterSpacing: 0), - ), - )), - if (fullPlan) Column( - spacing: 24, - children: [ - Align( - alignment: Alignment.topLeft, - child: SecondaryButton( - onPressed: ()=>setState(() { - showNew = true; - }), - text: "Crear objetivo de ahorro", - radius: 8, - height: 44, - size: 14, - width: 230, - ), + "Ahorros", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, ), - Align( - alignment: Alignment.topLeft, - child: TextButton( - onPressed: ()=>{}, - style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)), - child: Text( - "Ver estado de ahorros", - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0) - ) - ) - ) - ], + ), ), - if (!fullPlan) TextButton( - onPressed: ()=>{}, - child: Row( - spacing: 4, + Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + child: Text( + "Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + ), + if (fullPlan) + Column( + spacing: 24, children: [ - Icon(Icons.workspace_premium, size: 16), - Text("Suscribirme al Plan Completo") + Align( + alignment: Alignment.topLeft, + child: SecondaryButton( + onPressed: () => setState(() { + showNew = true; + }), + text: "Crear objetivo de ahorro", + radius: 8, + height: 44, + size: 14, + width: 230, + ), + ), + Align( + alignment: Alignment.topLeft, + child: TextButton( + onPressed: () => {}, + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.zero), + ), + child: Text( + "Ver estado de ahorros", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + ), ], - ) - ) + ), + if (!fullPlan) + TextButton( + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon(Icons.workspace_premium, size: 16), + Text("Suscribirme al Plan Completo"), + ], + ), + ), ], - ) + ), ], - ) + ), ); var editBlock = ({create, index}) => Column( spacing: 24, children: [ CustomTextField( - hint: create? "Ponle un título para reconocerlo" : widget.savings[index].name, + hint: create + ? "Ponle un título para reconocerlo" + : widget.savings[index].name, label: "Motivo del ahorro", - lines: create? 2 : 1, + lines: create ? 2 : 1, ), CustomTextField( hint: "30€", label: "Seleciona la cantidad a ahorrar", - numeric: true, + keyboardType: TextInputType.number, ), CheckboxListTile( value: false, @@ -201,13 +240,17 @@ class SavingsBlockState extends ConsumerState{ contentPadding: EdgeInsets.zero, title: Text( "Ahorro automático desde su paga", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), ), ), CustomTextField( hint: "2€", label: "Selecciona la cantidad de dinero", - numeric: true, + keyboardType: TextInputType.number, ), CheckboxListTile( value: false, @@ -230,8 +273,12 @@ class SavingsBlockState extends ConsumerState{ ), Text( "\"¡Genial, has conseguido ahorrar lo que querías!\"", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0), - ) + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), ], ), Column( @@ -246,13 +293,13 @@ class SavingsBlockState extends ConsumerState{ Row( spacing: 4, children: [ - Icon(Icons.info_outline, size: 16,), + Icon(Icons.info_outline, size: 16), Text( "Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0), - ) + ), ], - ) + ), ], ), Column( @@ -261,12 +308,11 @@ class SavingsBlockState extends ConsumerState{ PrimaryButton( onPressed: () => {}, text: "Guardar cambios", - color: theme.getColorFor( - ThemeCode.buttonPrimary) + color: theme.getColorFor(ThemeCode.buttonPrimary), ), TextButton( onPressed: () { - if (create){ + if (create) { setState(() { showNew = false; }); @@ -278,32 +324,41 @@ class SavingsBlockState extends ConsumerState{ }, child: Text( "Cancelar", - style: TextStyle(fontSize: 16, + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500, - letterSpacing: 0), - ) - ) + letterSpacing: 0, + ), + ), + ), ], - ) + ), ], ); if (widget.fullPlan) { - if (showNew){ + if (showNew) { return Container( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundSecondary), border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)), - borderRadius: BorderRadius.all(Radius.circular(24)) + borderRadius: BorderRadius.all(Radius.circular(24)), ), child: Column( spacing: 24, children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Ahorros", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), - )), + Align( + alignment: Alignment.topLeft, + child: Text( + "Ahorros", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), Text( "Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero", style: TextStyle(fontSize: 14, letterSpacing: 0), @@ -312,10 +367,10 @@ class SavingsBlockState extends ConsumerState{ padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(24)), - color: theme.getColorFor(ThemeCode.backgroundPrimary) + color: theme.getColorFor(ThemeCode.backgroundPrimary), ), - child: editBlock(create: true, index: null) - ) + child: editBlock(create: true, index: null), + ), ], ), ); @@ -331,19 +386,24 @@ class SavingsBlockState extends ConsumerState{ child: Column( spacing: 24, children: [ - Align(alignment: Alignment.topLeft, + Align( + alignment: Alignment.topLeft, child: Text( "Ahorros", - style: TextStyle(fontSize: 20, + style: TextStyle( + fontSize: 20, fontWeight: FontWeight.w500, - letterSpacing: 0), - ) + letterSpacing: 0, + ), + ), ), - ...List.generate(widget.savings.length, (int index) => - Container( + ...List.generate( + widget.savings.length, + (int index) => Container( decoration: BoxDecoration( border: BoxBorder.all( - color: theme.getColorFor(ThemeCode.textPrimary)), + color: theme.getColorFor(ThemeCode.textPrimary), + ), color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(24)), ), @@ -354,149 +414,181 @@ class SavingsBlockState extends ConsumerState{ Row( spacing: 16, children: [ - Expanded(child: Column( - spacing: 8, - children: [ - Align( - alignment: Alignment.topLeft, - child: Text( - "Ahorro para", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, letterSpacing: 0), + Expanded( + child: Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Ahorro para", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + letterSpacing: 0, + ), + ), ), - ), - Text( - widget.savings[index].name, - style: TextStyle(fontSize: 16, - fontWeight: FontWeight.w500, - letterSpacing: 0), - ) - ], - )), + Text( + widget.savings[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ], + ), + ), Container( decoration: BoxDecoration( color: theme.getColorFor( - ThemeCode.backgroundTertiary), + ThemeCode.backgroundTertiary, + ), borderRadius: BorderRadius.all( - Radius.circular(16)), + Radius.circular(16), + ), ), padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 4), + horizontal: 8, + vertical: 4, + ), child: Row( spacing: 8, children: [ - Icon(Icons.account_balance, size: 24, + Icon( + Icons.account_balance, + size: 24, color: theme.getColorFor( - ThemeCode.buttonPrimary)), + ThemeCode.buttonPrimary, + ), + ), MoneyText( - text: "${widget.savings[index] - .goal}€", + text: "${widget.savings[index].goal}€", size: 30, secondarySize: 14, color: theme.getColorFor( - ThemeCode.buttonPrimary) - ) - ], - ), - ) - ] - ), - if (index == 0)Column( - spacing: 8, - children: [ - ProgressBar( - max: widget.savings[index].goal+0.0, - value: widget.savings[index].saved, - height: 64, - textSize: 40, - textSecondarySize: 24, - backgroundColor: theme.getColorFor( - ThemeCode.backgroundTertiary), - foregroundColor: theme.getColorFor( - ThemeCode.buttonPrimary), - textColor: theme.getColorFor( - ThemeCode.textSecondary) - ), - Center(child: Text("Ahorrado")), - if (!showAdd[index]) TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll( - EdgeInsets.zero)), - onPressed: () => - setState(() { - showAdd[index] = true; - }), - child: Text( - "+ Añadir dinero extra a este ahorro", - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 16, letterSpacing: 0), - ) - ), - if (showAdd[index]) CustomTextField( - hint: "5€", - numeric: true, - ) - ], - ), - if (!showEdit[index]) Row( - children: [ - TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll( - EdgeInsets.zero)), - onPressed: () => - setState(() { - showEdit[index] = true; - }), - child: Row( - spacing: 4, - children: [ - Icon(Icons.edit_outlined, size: 24), - Text( - "Editar", - style: TextStyle(fontSize: 16, - fontWeight: FontWeight.w500, - letterSpacing: 0), + ThemeCode.buttonPrimary, + ), ), ], - ) - ), - Spacer(), - TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll( - EdgeInsets.zero)), - onPressed: () => {}, - child: Row( - spacing: 4, - children: [ - Icon(Icons.delete_outline_outlined, - size: 24), - Text( - "Eliminar", - style: TextStyle(fontSize: 16, - fontWeight: FontWeight.w500, - letterSpacing: 0), - ) - ], - ) + ), ), ], ), - if(showEdit[index]) editBlock( - create: false, index: index) + if (index == 0) + Column( + spacing: 8, + children: [ + ProgressBar( + max: widget.savings[index].goal + 0.0, + value: widget.savings[index].saved, + height: 64, + textSize: 40, + textSecondarySize: 24, + backgroundColor: theme.getColorFor( + ThemeCode.backgroundTertiary, + ), + foregroundColor: theme.getColorFor( + ThemeCode.buttonPrimary, + ), + textColor: theme.getColorFor( + ThemeCode.textSecondary, + ), + ), + Center(child: Text("Ahorrado")), + if (!showAdd[index]) + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.zero, + ), + ), + onPressed: () => setState(() { + showAdd[index] = true; + }), + child: Text( + "+ Añadir dinero extra a este ahorro", + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 16, + letterSpacing: 0, + ), + ), + ), + if (showAdd[index]) + CustomTextField( + hint: "5€", + keyboardType: TextInputType.number, + ), + ], + ), + if (!showEdit[index]) + Row( + children: [ + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.zero, + ), + ), + onPressed: () => setState(() { + showEdit[index] = true; + }), + child: Row( + spacing: 4, + children: [ + Icon(Icons.edit_outlined, size: 24), + Text( + "Editar", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ], + ), + ), + Spacer(), + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.zero, + ), + ), + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon( + Icons.delete_outline_outlined, + size: 24, + ), + Text( + "Eliminar", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ], + ), + ), + ], + ), + if (showEdit[index]) + editBlock(create: false, index: index), ], ), ), ), TextButton( - onPressed: () => - setState(() { - showNew = true; - }), + onPressed: () => setState(() { + showNew = true; + }), child: Row( spacing: 4, children: [ @@ -504,13 +596,15 @@ class SavingsBlockState extends ConsumerState{ Icon(Icons.account_balance, size: 24), Text( "Crear otro ahorro", - style: TextStyle(fontSize: 18, + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.w500, - letterSpacing: 0), + letterSpacing: 0, + ), ), - Spacer() + Spacer(), ], - ) + ), ), ], ), @@ -526,255 +620,290 @@ class SavingsBlockState extends ConsumerState{ } class TasksBlock extends ConsumerStatefulWidget { - final bool fullPlan; final List tasks; @override - const TasksBlock({ - super.key, - required this.fullPlan, - required this.tasks - }); + const TasksBlock({super.key, required this.fullPlan, required this.tasks}); @override ConsumerState createState() => TasksBlockState(); } -class TasksBlockState extends ConsumerState{ - +class TasksBlockState extends ConsumerState { @override Widget build(BuildContext context) { final theme = ref.watch(themePortProvider); - final emptyBlock = ({fullPlan})=>Container( + final emptyBlock = ({fullPlan}) => Container( padding: EdgeInsets.all(24), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(24)), - border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary)) + border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary)), ), child: Stack( children: [ - Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/tareas.svg")), + Align( + alignment: Alignment.topRight, + child: SvgPicture.asset("assets/images/ui/tareas.svg"), + ), Column( spacing: 24, children: [ - Align(alignment: Alignment.topLeft, child: Text( - "Tareas", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), - )), - Align(alignment: Alignment.topLeft, child: SizedBox( - width: 200, - child: Text( - "Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir", - style: TextStyle(fontSize: 14, letterSpacing: 0), - ), - )), - if (fullPlan) Align( + Align( alignment: Alignment.topLeft, - child: SecondaryButton( - onPressed: ()=>{}, - text: "Crear lista de tareas", - size: 14, - width: 190, - height: 44, - ) + child: Text( + "Tareas", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), ), - if (!fullPlan) TextButton( - onPressed: ()=>{}, - child: Row( - spacing: 4, - children: [ - Icon(Icons.workspace_premium, size: 16), - Text("Suscribirme al Plan Completo") - ], - ) - ) + Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + child: Text( + "Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + ), + if (fullPlan) + Align( + alignment: Alignment.topLeft, + child: SecondaryButton( + onPressed: () => {}, + text: "Crear lista de tareas", + size: 14, + width: 190, + height: 44, + ), + ), + if (!fullPlan) + TextButton( + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon(Icons.workspace_premium, size: 16), + Text("Suscribirme al Plan Completo"), + ], + ), + ), ], - ) + ), ], - ) + ), ); if (widget.fullPlan) { if (widget.tasks.isNotEmpty) { return Container( - padding: EdgeInsets.all(24), - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(24)), - border: BoxBorder.all( - color: theme.getColorFor(ThemeCode.textPrimary)), - color: theme.getColorFor(ThemeCode.backgroundSecondary) + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(24)), + border: BoxBorder.all( + color: theme.getColorFor(ThemeCode.textPrimary), ), - child: Column( - spacing: 24, - children: [ - Column( - spacing: 8, - children: [ - Align(alignment: Alignment.topLeft, child: Text( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + ), + child: Column( + spacing: 24, + children: [ + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( "Tareas", - style: TextStyle(fontSize: 20, - fontWeight: FontWeight.w500, - letterSpacing: 0), - )), - Text( - "Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir", - style: TextStyle(fontSize: 14, letterSpacing: 0), - ) - ], - ), - ...List.generate(widget.tasks.length, (int i) => - Container( - decoration: BoxDecoration( - border: BoxBorder.fromLTRB(top: BorderSide( - color: theme.getColorFor(ThemeCode.buttonPrimary), - width: 8)), - borderRadius: BorderRadius.all(Radius.circular(24)), - color: theme.getColorFor(ThemeCode.backgroundPrimary) + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, ), - padding: EdgeInsets.all(24), - child: Column( - spacing: 16, + ), + ), + Text( + "Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ], + ), + ...List.generate( + widget.tasks.length, + (int i) => Container( + decoration: BoxDecoration( + border: BoxBorder.fromLTRB( + top: BorderSide( + color: theme.getColorFor(ThemeCode.buttonPrimary), + width: 8, + ), + ), + borderRadius: BorderRadius.all(Radius.circular(24)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + padding: EdgeInsets.all(24), + child: Column( + spacing: 16, + children: [ + Row( children: [ - Row(children: [ - Text( - "Lista de tareas", - style: TextStyle(fontSize: 20, - fontWeight: FontWeight.w500, - letterSpacing: 0), - ), - Spacer(), - Text( - "10 oct - 17 oct", - style: TextStyle(fontSize: 14, letterSpacing: 0), - ) - ]), - ...List.generate( - widget.tasks[i].subtasks.length, (int j) => - CheckboxListTile( - contentPadding: EdgeInsets.zero, - checkboxScaleFactor: 2, - value: widget.tasks[i].subtasks[j].completed, - title: Text(widget.tasks[i].subtasks[j].name), - controlAffinity: ListTileControlAffinity - .leading, - activeColor: theme.getColorFor( - ThemeCode.buttonPrimary), - onChanged: (_) => - setState(() { - widget.tasks[i].subtasks[j] = Subtask( - name: widget.tasks[i].subtasks[j] - .name, - completed: !widget.tasks[i] - .subtasks[j].completed); - }), - ) - ), - TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll(EdgeInsets - .zero)), - onPressed: () => {}, - child: Align( - alignment: Alignment.topLeft, - child: Text( - "+ Añadir tarea", - style: TextStyle(fontSize: 18, - fontWeight: FontWeight.w500, - letterSpacing: 0), - ) - ) - ), - Container( - decoration: BoxDecoration( - color: theme.getColorFor(ThemeCode - .backgroundTertiary), - borderRadius: BorderRadius.all(Radius.circular( - 24)) - ), - padding: EdgeInsets.symmetric( - horizontal: 20, vertical: 16), - child: Row( - spacing: 16, - children: [ - Expanded(child: Text( - "Recompensa por cumplimiento", - style: TextStyle( - fontSize: 16, letterSpacing: 0), - )), - Row( - spacing: 8, - children: [ - Icon(Icons.emoji_events_outlined, size: 16, - color: theme.getColorFor( - ThemeCode.buttonPrimary)), - MoneyText( - text: "${widget.tasks[i] - .rewardAmount}€", - size: 40, - secondarySize: 24, - color: theme.getColorFor( - ThemeCode.buttonPrimary) - ) - ], - ) - ], + Text( + "Lista de tareas", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, ), ), - Column( - spacing: 8, - children: [ - Align( - alignment: Alignment.topLeft, - child: Text( - "Este es el mensaje que se enviará al cumplir con todas las tareas:", - style: TextStyle( - fontSize: 14, letterSpacing: 0), + Spacer(), + Text( + "10 oct - 17 oct", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ], + ), + ...List.generate( + widget.tasks[i].subtasks.length, + (int j) => CheckboxListTile( + contentPadding: EdgeInsets.zero, + checkboxScaleFactor: 2, + value: widget.tasks[i].subtasks[j].completed, + title: Text(widget.tasks[i].subtasks[j].name), + controlAffinity: ListTileControlAffinity.leading, + activeColor: theme.getColorFor( + ThemeCode.buttonPrimary, + ), + onChanged: (_) => setState(() { + widget.tasks[i].subtasks[j] = Subtask( + name: widget.tasks[i].subtasks[j].name, + completed: !widget.tasks[i].subtasks[j].completed, + ); + }), + ), + ), + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.zero), + ), + onPressed: () => {}, + child: Align( + alignment: Alignment.topLeft, + child: Text( + "+ Añadir tarea", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + ), + Container( + decoration: BoxDecoration( + color: theme.getColorFor( + ThemeCode.backgroundTertiary, + ), + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + child: Row( + spacing: 16, + children: [ + Expanded( + child: Text( + "Recompensa por cumplimiento", + style: TextStyle( + fontSize: 16, + letterSpacing: 0, ), ), - Align( - alignment: Alignment.topLeft, - child: Text( - "¡Enhorabuena, has cumplido todas las tareas de esta semana!", - style: TextStyle(fontSize: 14, - fontWeight: FontWeight.w500, - letterSpacing: 0), + ), + Row( + spacing: 8, + children: [ + Icon( + Icons.emoji_events_outlined, + size: 16, + color: theme.getColorFor( + ThemeCode.buttonPrimary, + ), + ), + MoneyText( + text: "${widget.tasks[i].rewardAmount}€", + size: 40, + secondarySize: 24, + color: theme.getColorFor( + ThemeCode.buttonPrimary, + ), + ), + ], + ), + ], + ), + ), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Este es el mensaje que se enviará al cumplir con todas las tareas:", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + Align( + alignment: Alignment.topLeft, + child: Text( + "¡Enhorabuena, has cumplido todas las tareas de esta semana!", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), + ), + ), + ], + ), + Align( + alignment: Alignment.topLeft, + child: TextButton( + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon(Icons.delete_outline_outlined, size: 24), + Text( + "Eliminar lista de tareas", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0, ), ), ], ), - Align( - alignment: Alignment.topLeft, - child: TextButton( - onPressed: () => {}, - child: Row( - spacing: 4, - children: [ - Icon(Icons.delete_outline_outlined, - size: 24), - Text( - "Eliminar lista de tareas", - style: TextStyle(fontSize: 16, - fontWeight: FontWeight.w500, - letterSpacing: 0), - ) - ], - ) - ), - ) - ], + ), ), - ) + ], + ), ), - SecondaryButton( - onPressed: () => {}, - text: "Crear una lista de tareas nueva", - size: 14, - ), - ], - ) + ), + SecondaryButton( + onPressed: () => {}, + text: "Crear una lista de tareas nueva", + size: 14, + ), + ], + ), ); } else { return emptyBlock(fullPlan: true); @@ -783,4 +912,4 @@ class TasksBlockState extends ConsumerState{ return emptyBlock(fullPlan: false); } } -} \ No newline at end of file +} diff --git a/modules/home/lib/src/presentation/limits_screen.dart b/modules/home/lib/src/presentation/limits_screen.dart index a3374424..62b83711 100644 --- a/modules/home/lib/src/presentation/limits_screen.dart +++ b/modules/home/lib/src/presentation/limits_screen.dart @@ -22,7 +22,8 @@ class LimitsScreenState extends ConsumerState { @override void initState() { super.initState(); - dailyLimits = [ //dey, week, month, year + dailyLimits = [ + //dey, week, month, year {"title": "Diario L-V", "limit": "5", "edit": false}, {"title": "Fines de semana", "limit": "8", "edit": false}, {"title": "Semanal", "limit": "30", "edit": false}, @@ -41,12 +42,7 @@ class LimitsScreenState extends ConsumerState { "end": "21:00", "edit": false, }, - { - "title": "Vacaciones", - "start": "09:00", - "end": "22:00", - "edit": false - }, + {"title": "Vacaciones", "start": "09:00", "end": "22:00", "edit": false}, ]; conditions = [ {"title": "Alimentación", "limit": "10", "active": true, "edit": false}, @@ -73,7 +69,10 @@ class LimitsScreenState extends ConsumerState { footer: Container( decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)) + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), ), padding: EdgeInsets.all(24), child: Column( @@ -81,19 +80,19 @@ class LimitsScreenState extends ConsumerState { PrimaryButton( onPressed: () => {}, text: "Guardar límites", - color: theme.getColorFor(ThemeCode.buttonPrimary) + color: theme.getColorFor(ThemeCode.buttonPrimary), ), TextButton( - onPressed: ()=>Navigator.pop(context), + onPressed: () => Navigator.pop(context), child: Text( "Cancelar", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: theme.getColorFor(ThemeCode.textPrimary) - ) - ) - ) + color: theme.getColorFor(ThemeCode.textPrimary), + ), + ), + ), ], ), ), @@ -112,7 +111,7 @@ class LimitsScreenState extends ConsumerState { child: Text( "Pon límite de gastos", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - ) + ), ), Text("Libertad para ellos, tranquilidad para ti"), ...List.generate(dailyLimits.length, (int index) { @@ -135,9 +134,7 @@ class LimitsScreenState extends ConsumerState { ), ], ), - if (dailyLimits[index]["edit"]) CustomTextField( - hint: "5€", - ), + if (dailyLimits[index]["edit"]) CustomTextField(hint: "5€"), ], ); }), @@ -170,16 +167,15 @@ class LimitsScreenState extends ConsumerState { TextButton( onPressed: () => { setState(() { - timeLimits[index]["edit"] = !timeLimits[index]["edit"]; + timeLimits[index]["edit"] = + !timeLimits[index]["edit"]; }), }, child: Text("Editar"), ), ], ), - if (timeLimits[index]["edit"]) CustomTextField( - hint: "5€", - ), + if (timeLimits[index]["edit"]) CustomTextField(hint: "5€"), ], ); }), @@ -197,67 +193,89 @@ class LimitsScreenState extends ConsumerState { children: [ Text( "Condiciones", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), ), Column( spacing: 8, - children: List.generate(conditions.length, (int index)=> - Column( + children: List.generate( + conditions.length, + (int index) => Column( spacing: 8, children: [ - Row(children: [ - Expanded(child: CheckboxListTile( - value: conditions[index]["active"], - onChanged: (_)=>setState(() { - conditions[index]["active"] = !conditions[index]["active"]; - }), - title: Text( - "${conditions[index]["title"]}: ${conditions[index]["limit"]}€/sem", - style: TextStyle(fontSize: 16, letterSpacing: 0), + Row( + children: [ + Expanded( + child: CheckboxListTile( + value: conditions[index]["active"], + onChanged: (_) => setState(() { + conditions[index]["active"] = + !conditions[index]["active"]; + }), + title: Text( + "${conditions[index]["title"]}: ${conditions[index]["limit"]}€/sem", + style: TextStyle( + fontSize: 16, + letterSpacing: 0, + ), + ), + checkboxScaleFactor: 2, + controlAffinity: ListTileControlAffinity.leading, + activeColor: theme.getColorFor( + ThemeCode.buttonPrimary, + ), + contentPadding: EdgeInsets.zero, + ), ), - checkboxScaleFactor: 2, - controlAffinity: ListTileControlAffinity.leading, - activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - contentPadding: EdgeInsets.zero, - )), - TextButton( - onPressed: ()=>setState(() { - conditions[index]["edit"] = ! conditions[index]["edit"]; - }), - child: Text( - "Editar", - style: TextStyle(fontSize: 16, letterSpacing: 0), - ) - ) - ]), - if (conditions[index]["edit"]) CustomTextField( - hint: "5€", - numeric: true, - ) - ] - ) + TextButton( + onPressed: () => setState(() { + conditions[index]["edit"] = + !conditions[index]["edit"]; + }), + child: Text( + "Editar", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ), + ], + ), + if (conditions[index]["edit"]) + CustomTextField( + hint: "5€", + keyboardType: TextInputType.number, + ), + ], + ), ), - ) + ), ], - ) + ), ), Container( padding: EdgeInsets.all(24), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(24)) + borderRadius: BorderRadius.all(Radius.circular(24)), ), child: Column( spacing: 24, children: [ Text( "Comercios bloqueados", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0, + ), ), Column( spacing: 8, - children: List.generate(blocks.length, (int index) => - CheckboxListTile( + children: List.generate( + blocks.length, + (int index) => CheckboxListTile( value: blocks[index]["active"], onChanged: (_) => setState(() { blocks[index]["active"] = !blocks[index]["active"]; @@ -270,13 +288,13 @@ class LimitsScreenState extends ConsumerState { controlAffinity: ListTileControlAffinity.leading, activeColor: theme.getColorFor(ThemeCode.buttonPrimary), contentPadding: EdgeInsets.zero, - ) - ) - ) + ), + ), + ), ], ), - ) + ), ], ); } -} \ No newline at end of file +} diff --git a/modules/home/lib/src/presentation/wage_screen.dart b/modules/home/lib/src/presentation/wage_screen.dart index f4e0c69a..9918ca7f 100644 --- a/modules/home/lib/src/presentation/wage_screen.dart +++ b/modules/home/lib/src/presentation/wage_screen.dart @@ -43,7 +43,7 @@ class _WageScreenState extends ConsumerState { PrimaryButton( onPressed: () => {}, text: "Activar paga automática", - color: theme.getColorFor(ThemeCode.buttonPrimary) + color: theme.getColorFor(ThemeCode.buttonPrimary), ), TextButton(onPressed: () {}, child: const Text('Cancelar')), ], @@ -59,14 +59,15 @@ class _WageScreenState extends ConsumerState { child: Column( spacing: 10, children: [ - Align(alignment: Alignment.topLeft, + Align( + alignment: Alignment.topLeft, child: const Text( "Paga automática", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), ), ), CustomTextField( - numeric: true, + keyboardType: TextInputType.number, label: "Cantidad", hint: "0€", ), @@ -84,13 +85,15 @@ class _WageScreenState extends ConsumerState { child: Column( spacing: 10, children: [ - Align(alignment: Alignment.topLeft, + Align( + alignment: Alignment.topLeft, child: const Text( "Frecuencia", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - ) + ), ), - Align(alignment: Alignment.topLeft, + Align( + alignment: Alignment.topLeft, child: const Text("Cuándo se envía el dinero"), ), CheckboxListTile( @@ -139,22 +142,30 @@ class _WageScreenState extends ConsumerState { Text("Sábado"), Text("Domingo"), ], - values: ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"], - onChanged: (value)=> {}, + values: [ + "Lunes", + "Martes", + "Miércoles", + "Jueves", + "Viernes", + "Sábado", + "Domingo", + ], + onChanged: (value) => {}, hint: "Día de la semana", ), CustomDropdown( hint: "Hora del día", - items: List.generate(24,(int index){ + items: List.generate(24, (int index) { return Text("$index:00"); }), - onChanged: (value)=> {}, + onChanged: (value) => {}, ), CustomTextField( lines: 3, length: 150, label: - "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", + "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", hint: "Escribe tu mensaje", ), const Align( @@ -174,13 +185,15 @@ class _WageScreenState extends ConsumerState { child: Column( spacing: 10, children: [ - const Align(alignment: Alignment.topLeft, + const Align( + alignment: Alignment.topLeft, child: Text( "Condiciones", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), ), - const Align(alignment: Alignment.topLeft, + const Align( + alignment: Alignment.topLeft, child: Text("Este dato aparecerá en el reloj del peque"), ), CheckboxListTile( diff --git a/packages/design_system/lib/src/buttons/primary_button.dart b/packages/design_system/lib/src/buttons/primary_button.dart index 38149989..e9ef3da9 100644 --- a/packages/design_system/lib/src/buttons/primary_button.dart +++ b/packages/design_system/lib/src/buttons/primary_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class PrimaryButton extends StatelessWidget { - final VoidCallback onPressed; + final VoidCallback? onPressed; final String text; final Color color; final double height; @@ -9,24 +9,35 @@ class PrimaryButton extends StatelessWidget { final double size; final double radius; final double padding; + final Widget? leading; + final double leadingGap; const PrimaryButton({ super.key, - required this.onPressed, required this.text, required this.color, + this.onPressed, this.height = 60, this.width, this.size = 18, this.radius = 18, this.padding = 0, + this.leading, + this.leadingGap = 10, }); @override Widget build(BuildContext context) { + final bgResolver = WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return color.withValues(alpha: 0.5); + } + return color; + }); + return FilledButton( style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll(color), + backgroundColor: bgResolver, padding: WidgetStatePropertyAll( EdgeInsets.symmetric(horizontal: padding), ), @@ -40,17 +51,29 @@ class PrimaryButton extends StatelessWidget { child: SizedBox( width: width, height: height, - child: Center( - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: size, - fontWeight: FontWeight.w500, - letterSpacing: 0, - color: Colors.white, // theme.getColorFor(ThemeCode.textSecondary) + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + // mainAxisSize: MainAxisSize.min, + children: [ + if (leading != null) ...[ + IconTheme.merge( + data: const IconThemeData(color: Colors.white, size: 20), + child: leading!, + ), + SizedBox(width: leadingGap), + ], + Text( + text, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: size, + fontWeight: FontWeight.w500, + letterSpacing: 0, + color: Colors.white, + ), ), - ), + ], ), ), ); diff --git a/packages/design_system/lib/src/inputs/textfields.dart b/packages/design_system/lib/src/inputs/textfields.dart index a665ff8d..86c35c15 100644 --- a/packages/design_system/lib/src/inputs/textfields.dart +++ b/packages/design_system/lib/src/inputs/textfields.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class CustomTextField extends StatefulWidget { final bool? showPassword; - final bool numeric; + final TextInputType keyboardType; + final TextInputAction? textInputAction; + final ValueChanged? onSubmitted; final String hint; final String label; final int? lines; @@ -14,7 +15,9 @@ class CustomTextField extends StatefulWidget { const CustomTextField({ super.key, this.showPassword, - this.numeric = false, + this.keyboardType = TextInputType.text, + this.textInputAction, + this.onSubmitted, this.hint = '', this.label = '', this.lines, @@ -49,17 +52,14 @@ class CustomTextFieldState extends State { ), ), TextFormField( + onFieldSubmitted: widget.onSubmitted, + textInputAction: widget.textInputAction, controller: widget.controller, - keyboardType: widget.numeric - ? TextInputType.number - : TextInputType.text, + keyboardType: widget.keyboardType, obscureText: !_showPassword, enableSuggestions: _showPassword, autocorrect: !_showPassword, style: const TextStyle(color: Color(0xFF4B4B4B)), - inputFormatters: widget.numeric - ? [FilteringTextInputFormatter.digitsOnly] - : const [], decoration: InputDecoration( counterText: "", hintText: widget.hint, diff --git a/packages/design_system/test/widget_test.dart b/packages/design_system/test/widget_test.dart index 9e518317..9dc6d1cb 100644 --- a/packages/design_system/test/widget_test.dart +++ b/packages/design_system/test/widget_test.dart @@ -93,7 +93,11 @@ void main() { ) ..addScenario( 'numeric', - SizedBox(height: 70, width: 250, child: CustomTextField(numeric: true)), + SizedBox( + height: 70, + width: 250, + child: CustomTextField(keyboardType: TextInputType.number), + ), ) ..addScenario( 'password', diff --git a/packages/sf_localizations/assets/l10n/de.json b/packages/sf_localizations/assets/l10n/de.json index 447bc8b7..95f198e1 100644 --- a/packages/sf_localizations/assets/l10n/de.json +++ b/packages/sf_localizations/assets/l10n/de.json @@ -20,5 +20,15 @@ "enterCodeHere": "Gib den Code hier ein", "enter": "Weiter", "didNotReceiveIt": "Hast du es nicht erhalten?", - "tryAgain": "Erneut versuchen" + "tryAgain": "Erneut versuchen", + "welcome": "Wir heißen dich willkommen!", + "username": "Benutzername", + "password": "Passwort", + "forgotPassword": "Passwort vergessen?", + "signIn": "Anmelden", + "orContinueWith": "oder weiter mit", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "Du hast noch kein Konto?", + "createOneNow": "Jetzt eines erstellen" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index 15d42b73..c106c693 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -20,5 +20,15 @@ "enterCodeHere": "Enter the code here", "enter": "Enter", "didNotReceiveIt": "Didn't receive it?", - "tryAgain": "Try again" + "tryAgain": "Try again", + "welcome": "Welcome!", + "username": "Username", + "password": "Password", + "forgotPassword": "Forgot your password?", + "signIn": "Sign in", + "orContinueWith": "or continue with", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "Don't have an account?", + "createOneNow": "Create one now" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index f9acdf5a..2f05efaf 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -20,5 +20,15 @@ "enterCodeHere": "Introduce el código aquí", "enter": "enter", "didNotReceiveIt": "¿No lo has recibido?", - "tryAgain": "Volver a intentarlo" + "tryAgain": "Volver a intentarlo", + "welcome": "¡Te damos la bienvenida!", + "username": "Nombre de usuario", + "password": "Contraseña", + "forgotPassword": "¿Has olvidado la contraseña?", + "signIn": "Iniciar sesión", + "orContinueWith": "o continúa con", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "¿No tienes cuenta?", + "createOneNow": "Crear una ahora" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/fr.json b/packages/sf_localizations/assets/l10n/fr.json index 557f92e8..806b14d9 100644 --- a/packages/sf_localizations/assets/l10n/fr.json +++ b/packages/sf_localizations/assets/l10n/fr.json @@ -20,5 +20,15 @@ "enterCodeHere": "Saisissez le code ici", "enter": "Entrer", "didNotReceiveIt": "Tu ne l'as pas reçu ?", - "tryAgain": "Réessayer" + "tryAgain": "Réessayer", + "welcome": "Nous te souhaitons la bienvenue !", + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "forgotPassword": "Mot de passe oublié ?", + "signIn": "Se connecter", + "orContinueWith": "ou continuer avec", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "Tu n'as pas de compte ?", + "createOneNow": "Crée-en un maintenant" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/it.json b/packages/sf_localizations/assets/l10n/it.json index b55a0679..a5a3055e 100644 --- a/packages/sf_localizations/assets/l10n/it.json +++ b/packages/sf_localizations/assets/l10n/it.json @@ -20,5 +20,15 @@ "enterCodeHere": "Inserisci il codice qui", "enter": "Entra", "didNotReceiveIt": "Non lo hai ricevuto?", - "tryAgain": "Riprova" + "tryAgain": "Riprova", + "welcome": "Ti diamo il benvenuto!", + "username": "Nome utente", + "password": "Password", + "forgotPassword": "Hai dimenticato la password?", + "signIn": "Accedi", + "orContinueWith": "o continua con", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "Non hai un account?", + "createOneNow": "Creane uno adesso" } \ No newline at end of file diff --git a/packages/sf_localizations/assets/l10n/pt.json b/packages/sf_localizations/assets/l10n/pt.json index 2fff2b01..0bad019b 100644 --- a/packages/sf_localizations/assets/l10n/pt.json +++ b/packages/sf_localizations/assets/l10n/pt.json @@ -20,5 +20,15 @@ "enterCodeHere": "Insira o código aqui", "enter": "Entrar", "didNotReceiveIt": "Você não recebeu?", - "tryAgain": "Tentar novamente" + "tryAgain": "Tentar novamente", + "welcome": "Bem-vindo!", + "username": "Nome de usuário", + "password": "Senha", + "forgotPassword": "Esqueceu a senha?", + "signIn": "Iniciar sessão", + "orContinueWith": "ou continuar com", + "google": "Google", + "apple": "Apple", + "dontHaveAccount": "Não tem conta?", + "createOneNow": "Criar uma agora" } \ No newline at end of file diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index 84ec6a23..0170a2df 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -25,4 +25,14 @@ class I18n { static const String enter = "enter"; static const String didNotReceiveIt = "didNotReceiveIt"; static const String tryAgain = "tryAgain"; + static const String welcome = "welcome"; + static const String username = "username"; + static const String password = "password"; + static const String forgotPassword = "forgotPassword"; + static const String signIn = "signIn"; + static const String orContinueWith = "orContinueWith"; + static const String google = "google"; + static const String apple = "apple"; + static const String dontHaveAccount = "dontHaveAccount"; + static const String createOneNow = "createOneNow"; } diff --git a/packages/sf_shared/lib/src/widgets/deposit_block.dart b/packages/sf_shared/lib/src/widgets/deposit_block.dart index 710145e1..9e4d12fe 100644 --- a/packages/sf_shared/lib/src/widgets/deposit_block.dart +++ b/packages/sf_shared/lib/src/widgets/deposit_block.dart @@ -38,7 +38,7 @@ class DepositBlock extends ConsumerWidget { child: CustomTextField( label: "Cantidad", hint: "0€", - numeric: true, + keyboardType: TextInputType.number, ), ), Align( @@ -49,7 +49,7 @@ class DepositBlock extends ConsumerWidget { color: theme.getColorFor(ThemeCode.buttonPrimary), padding: 24, ), - ) + ), ], ), Align( @@ -60,10 +60,10 @@ class DepositBlock extends ConsumerWidget { Icon(Icons.info_outline, size: 16), Text("Máximo que puedes añadir: $max€"), ], - ) + ), ), ], - ) + ), ], ), );