added login and 2fa login endpoints, providers and states
This commit is contained in:
@@ -2,4 +2,8 @@ abstract class AuthRemoteDatasource {
|
||||
Future<void> requestPhoneCode({required String phone});
|
||||
|
||||
Future<void> verifyPhoneCode({required String phone, required String code});
|
||||
|
||||
Future<String> login({required String email, required String password});
|
||||
|
||||
Future<void> twoFALogin({required String token, required String code});
|
||||
}
|
||||
|
||||
@@ -53,4 +53,33 @@ class AuthRemoteDatasourceImpl implements AuthRemoteDatasource {
|
||||
|
||||
return Exception(message);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _repository.post<Map<String, dynamic>>(
|
||||
'/auth/login',
|
||||
body: <String, dynamic>{'email': email, 'password': password},
|
||||
);
|
||||
final token = response.data!['token'];
|
||||
return token;
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in login');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> twoFALogin({required String token, required String code}) async {
|
||||
try {
|
||||
await _repository.post<Map<String, dynamic>>(
|
||||
'/auth/login',
|
||||
body: <String, dynamic>{'token': token, 'password': code},
|
||||
);
|
||||
} on DioException catch (error) {
|
||||
throw _mapDioError(error, defaultMessage: 'Error in login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,14 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
Future<void> verifyPhoneCode({required String phone, required String code}) {
|
||||
return _remote.verifyPhoneCode(phone: phone, code: code);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> login({required String email, required String password}) {
|
||||
return _remote.login(email: email, password: password);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> twoFALogin({required String token, required String code}) {
|
||||
return _remote.twoFALogin(token: token, code: code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,8 @@ abstract class AuthRepository {
|
||||
Future<void> requestPhoneCode({required String phone});
|
||||
|
||||
Future<void> verifyPhoneCode({required String phone, required String code});
|
||||
|
||||
Future<String> login({required String email, required String password});
|
||||
|
||||
Future<void> twoFALogin({required String token, required String code});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class RequestLinkPhoneScreen extends ConsumerWidget {
|
||||
child: CustomTextField(
|
||||
controller: viewModel.phoneNumberController,
|
||||
hint: context.translate(I18n.phoneNumber),
|
||||
numeric: true,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
abstract class LoginUseCase {
|
||||
Future<void> login({required String email, required String password});
|
||||
}
|
||||
@@ -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<void> login({required String email, required String password}) {
|
||||
return _repository.login(email: email, password: password);
|
||||
}
|
||||
}
|
||||
@@ -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<ConsumerStatefulWidget> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
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<void> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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<LoginUseCase>((ref) {
|
||||
final authRepository = ref.read(authRepositoryProvider);
|
||||
return LoginUseCaseImpl(authRepository);
|
||||
});
|
||||
@@ -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, LoginViewState>(
|
||||
LoginViewModel.new,
|
||||
);
|
||||
|
||||
class LoginViewModel extends Notifier<LoginViewState> {
|
||||
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<bool> 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>(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<LoginViewState> get copyWith => _$LoginViewStateCopyWithImpl<LoginViewState>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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 extends Object?>(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
|
||||
@@ -57,7 +57,11 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
|
||||
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<NewPasswordScreen> {
|
||||
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<NewPasswordScreen> {
|
||||
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<NewPasswordScreen> {
|
||||
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<NewPasswordScreen> {
|
||||
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<NewPasswordScreen> {
|
||||
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(),
|
||||
],
|
||||
|
||||
@@ -69,7 +69,7 @@ class RestorePasswordScreen extends ConsumerWidget {
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
hint: "Teléfono",
|
||||
numeric: true,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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<SignupAddressScreen> createState() => SignupAddressScreenState();
|
||||
|
||||
ConsumerState<SignupAddressScreen> createState() =>
|
||||
SignupAddressScreenState();
|
||||
}
|
||||
|
||||
class SignupAddressScreenState extends ConsumerState<SignupAddressScreen>{
|
||||
|
||||
class SignupAddressScreenState extends ConsumerState<SignupAddressScreen> {
|
||||
late String country;
|
||||
late int relation;
|
||||
|
||||
@@ -29,30 +28,39 @@ class SignupAddressScreenState extends ConsumerState<SignupAddressScreen>{
|
||||
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<SignupAddressScreen>{
|
||||
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<SignupAddressScreen>{
|
||||
CustomDropdown(
|
||||
items: [Text("España"), Text("Francia"), Text("Portugal")],
|
||||
hint: "País",
|
||||
onChanged: (value)=>setState(() {
|
||||
onChanged: (value) => setState(() {
|
||||
country = value;
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
|
||||
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<DepositScreen> {
|
||||
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<DepositScreen> {
|
||||
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<DepositScreen> {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,8 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
||||
@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<LimitsScreen> {
|
||||
"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<LimitsScreen> {
|
||||
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<LimitsScreen> {
|
||||
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<LimitsScreen> {
|
||||
child: Text(
|
||||
"Pon límite de gastos",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
)
|
||||
),
|
||||
),
|
||||
Text("Libertad para ellos, tranquilidad para ti"),
|
||||
...List<Widget>.generate(dailyLimits.length, (int index) {
|
||||
@@ -135,9 +134,7 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (dailyLimits[index]["edit"]) CustomTextField(
|
||||
hint: "5€",
|
||||
),
|
||||
if (dailyLimits[index]["edit"]) CustomTextField(hint: "5€"),
|
||||
],
|
||||
);
|
||||
}),
|
||||
@@ -170,16 +167,15 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
|
||||
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<LimitsScreen> {
|
||||
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<Widget>.generate(conditions.length, (int index)=>
|
||||
Column(
|
||||
children: List<Widget>.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<Widget>.generate(blocks.length, (int index) =>
|
||||
CheckboxListTile(
|
||||
children: List<Widget>.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<LimitsScreen> {
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
|
||||
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<WageScreen> {
|
||||
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<WageScreen> {
|
||||
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<WageScreen> {
|
||||
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<Widget>.generate(24,(int index){
|
||||
items: List<Widget>.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<WageScreen> {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user