From 62ffc9ef7cfa9f59971b91ed0823f99f21400129 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Wed, 3 Dec 2025 15:28:10 +0100 Subject: [PATCH] - created custom text block, dropdown, - created extract, goals and block card screen. - generated tests for design system components. - updated restore password and home screens to 17/11 design. --- .../mobile_app/lib/navigation/app_router.dart | 5 + apps/mobile_app/test/widget_test.dart | 28 - modules/auth/lib/auth.dart | 3 +- .../src/device_sign_up/add_kid_screen.dart | 63 +- .../src/device_sign_up/contact_screen.dart | 8 +- .../device_sign_up/device_signup_screen.dart | 8 +- .../link_watch/create_profile_screen.dart | 70 +- .../link_watch_previous_screen.dart | 4 +- .../link_watch/link_watch_screen.dart | 104 ++- .../login/presentation/link_phone_screen.dart | 105 +-- .../src/login/presentation/login_screen.dart | 158 +++- .../login/presentation/phone_code_screen.dart | 117 ++- .../presentation/welcome_screen.dart | 83 +- .../presentation/email_sent_screen.dart | 84 -- .../presentation/new_password_screen.dart | 211 +++-- .../presentation/restore_password_screen.dart | 107 ++- .../presentation/sent_screen.dart | 98 +++ .../src/sign_up/account_created_screen.dart | 5 +- .../src/sign_up/signup_address_screen.dart | 110 ++- .../auth/lib/src/sign_up/signup_builder.dart | 18 + .../src/sign_up/signup_personal_screen.dart | 29 +- .../auth/lib/src/sign_up/signup_screen.dart | 66 +- .../src/widgets/layouts/form_step_layout.dart | 19 +- .../lib/src/presentation/deposit_screen.dart | 17 +- .../lib/src/presentation/extract_screen.dart | 111 +++ .../lib/src/presentation/goals_screen.dart | 786 ++++++++++++++++++ .../lib/src/presentation/home_screen.dart | 2 +- .../src/presentation/kid_wallet_screen.dart | 50 +- .../lib/src/presentation/limits_screen.dart | 171 +++- .../src/presentation/lock_card_screen.dart | 105 +++ .../lib/src/presentation/wage_screen.dart | 99 +-- .../lib/src/presentation/wallet_item.dart | 32 +- .../lib/src/presentation/activity_screen.dart | 4 +- .../lib/src/presentation/alert_screen.dart | 20 +- .../lib/src/presentation/profile_screen.dart | 13 +- modules/profile/lib/src/settings_screen.dart | 327 +++++--- packages/design_system/lib/design_system.dart | 6 +- .../lib/src/buttons/custom_text_button.dart | 39 + .../lib/src/buttons/primary_button.dart | 54 ++ .../lib/src/buttons/secondary_button.dart | 67 ++ .../lib/src/dropdowns/dropdown.dart | 67 ++ .../lib/src/inputs/textfields.dart | 85 +- .../lib/src/progress_bars/progress_bar.dart | 18 +- .../lib/src/snackbars/snackbar.dart | 15 +- .../lib/src/texts/money_text.dart | 9 +- packages/design_system/pubspec.yaml | 2 + packages/design_system/test/widget_test.dart | 201 +++++ packages/sf_shared/lib/sf_shared.dart | 4 +- .../lib/src/models/savings_goal.dart | 12 + packages/sf_shared/lib/src/models/task.dart | 21 + .../lib/src/widgets/deposit_block.dart | 23 +- .../sf_shared/lib/src/widgets/line_graph.dart | 67 +- .../lib/src/widgets/wallet_balance_block.dart | 22 +- 53 files changed, 3070 insertions(+), 882 deletions(-) delete mode 100644 apps/mobile_app/test/widget_test.dart delete mode 100644 modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart create mode 100644 modules/auth/lib/src/recover_password/presentation/sent_screen.dart create mode 100644 modules/auth/lib/src/sign_up/signup_builder.dart create mode 100644 modules/home/lib/src/presentation/extract_screen.dart create mode 100644 modules/home/lib/src/presentation/goals_screen.dart create mode 100644 modules/home/lib/src/presentation/lock_card_screen.dart create mode 100644 packages/design_system/lib/src/buttons/custom_text_button.dart create mode 100644 packages/design_system/lib/src/buttons/primary_button.dart create mode 100644 packages/design_system/lib/src/buttons/secondary_button.dart create mode 100644 packages/design_system/lib/src/dropdowns/dropdown.dart create mode 100644 packages/design_system/test/widget_test.dart create mode 100644 packages/sf_shared/lib/src/models/savings_goal.dart create mode 100644 packages/sf_shared/lib/src/models/task.dart diff --git a/apps/mobile_app/lib/navigation/app_router.dart b/apps/mobile_app/lib/navigation/app_router.dart index 6d0f5127..0e1e2493 100644 --- a/apps/mobile_app/lib/navigation/app_router.dart +++ b/apps/mobile_app/lib/navigation/app_router.dart @@ -24,6 +24,11 @@ void configureAppRouter() { name: 'login', pageBuilder: LoginBuilder().buildPage, ), + GoRoute( + path: '/signup', + name: 'signup', + pageBuilder: SignupBuilder().buildPage, + ), GoRoute( path: '/onboarding', name: 'onboarding', diff --git a/apps/mobile_app/test/widget_test.dart b/apps/mobile_app/test/widget_test.dart deleted file mode 100644 index 3c91e2c9..00000000 --- a/apps/mobile_app/test/widget_test.dart +++ /dev/null @@ -1,28 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - //await tester.pumpWidget(const PaymentsApp(di: di)); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/modules/auth/lib/auth.dart b/modules/auth/lib/auth.dart index f7e85138..8f61929c 100644 --- a/modules/auth/lib/auth.dart +++ b/modules/auth/lib/auth.dart @@ -4,4 +4,5 @@ export 'src/login/link_phone_builder.dart'; export 'src/login/phone_code_builder.dart'; export 'src/login/login_builder.dart'; export 'src/recover_password/recover_password_builder.dart'; -export 'src/device_sign_up/device_signup_builder.dart'; \ No newline at end of file +export 'src/device_sign_up/device_signup_builder.dart'; +export 'src/sign_up/signup_builder.dart'; \ No newline at end of file diff --git a/modules/auth/lib/src/device_sign_up/add_kid_screen.dart b/modules/auth/lib/src/device_sign_up/add_kid_screen.dart index ea7d13f9..5171a960 100644 --- a/modules/auth/lib/src/device_sign_up/add_kid_screen.dart +++ b/modules/auth/lib/src/device_sign_up/add_kid_screen.dart @@ -1,13 +1,18 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class AddKidScreen extends StatelessWidget { +class AddKidScreen extends ConsumerWidget { final nextStep; const AddKidScreen({super.key, required this.nextStep}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( margin: EdgeInsets.all(30), child: Column( @@ -17,13 +22,39 @@ class AddKidScreen extends StatelessWidget { Text("Añade a tu peque", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), Text( "Controla su gasto a la vez que aprende hábitos financieros responsables", + textAlign: TextAlign.center, ), Container( margin: EdgeInsets.symmetric(vertical: 30, horizontal: 50), child: Row( + spacing: 10, children: [ - Column(children: [Text("1"), Text("2"), Text("3")]), + Stack( + children: [ + Column( + spacing: 16, + children: List.generate(3, (int index) => + Container( + decoration: ShapeDecoration( + shape: CircleBorder(), + color: theme.getColorFor(ThemeCode.buttonPrimary) + ), + width: 32, + height: 32, + child: Center(child: Text( + (index + 1).toString(), + style: TextStyle( + color: theme.getColorFor(ThemeCode.backgroundPrimary) + ) + )) + ) + ) + ), + Divider(color: Colors.red, thickness: 4,), + ], + ), Column( + spacing: 16, children: [ Text("Crea su perfil"), Text("Vincula su correa y su reloj"), @@ -33,18 +64,28 @@ class AddKidScreen extends StatelessWidget { ], ), ), - Text("¡Y todo listo para que tenga su dinero!", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - Text("Recuerda que necesitas tener un Plan SaveFamily"), + Text( + "¡Y todo listo para que tenga su dinero!", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 0 + ) + ), + Text( + "Recuerda que necesitas tener un Plan SaveFamily", + textAlign: TextAlign.center + ), Text( "Si aún no lo tienes, puedes conseguirlo a través de nuestra web", + textAlign: TextAlign.center, ), Spacer(flex: 8), - Container( - width: double.infinity, - child: FilledButton( - onPressed: nextStep, - child: Text("¡Empezar!"), - ), + PrimaryButton( + onPressed: nextStep, + text: "¡Empezar!", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), ], ), diff --git a/modules/auth/lib/src/device_sign_up/contact_screen.dart b/modules/auth/lib/src/device_sign_up/contact_screen.dart index 6a7befe3..b10fa641 100644 --- a/modules/auth/lib/src/device_sign_up/contact_screen.dart +++ b/modules/auth/lib/src/device_sign_up/contact_screen.dart @@ -1,12 +1,16 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class ContactScreen extends StatelessWidget { +class ContactScreen extends ConsumerWidget { const ContactScreen({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( margin: EdgeInsets.all(30), child: Center( diff --git a/modules/auth/lib/src/device_sign_up/device_signup_screen.dart b/modules/auth/lib/src/device_sign_up/device_signup_screen.dart index 1be84427..65a8fdd8 100644 --- a/modules/auth/lib/src/device_sign_up/device_signup_screen.dart +++ b/modules/auth/lib/src/device_sign_up/device_signup_screen.dart @@ -38,15 +38,13 @@ class DeviceSignupScreenState extends ConsumerState{ final theme = ref.watch(themePortProvider); final continueBtn = Container( - padding: EdgeInsets.all(24), color: theme.getColorFor(ThemeCode.backgroundPrimary), - child: FilledButton( + child: PrimaryButton( onPressed: ()=>{setState(() { currentStep++; })}, - child: Container( - width: double.infinity, - child: Expanded(child: Center(child: Text("Continuar")))) + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonPrimary) ) ); diff --git a/modules/auth/lib/src/device_sign_up/link_watch/create_profile_screen.dart b/modules/auth/lib/src/device_sign_up/link_watch/create_profile_screen.dart index a26e8ed2..01b3e577 100644 --- a/modules/auth/lib/src/device_sign_up/link_watch/create_profile_screen.dart +++ b/modules/auth/lib/src/device_sign_up/link_watch/create_profile_screen.dart @@ -16,7 +16,8 @@ class CreateProfileScreen extends ConsumerWidget { children: [ Text( "Comienza con un peque; luego podrás agregar más", - style: TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0), ), CustomTextField( label: "Nombre", @@ -26,30 +27,41 @@ class CreateProfileScreen extends ConsumerWidget { label: "Apellidos", hint: "Apellidos", ), - Row( - spacing: 10, + Column( + spacing: 8, children: [ - Expanded( - child: CustomTextField( - numeric: true, - label: "Fecha de nacimiento", - hint: "DD", - length: 2, + Align( + alignment: Alignment.bottomLeft, + child: Text( + "Fecha de nacimiento", + style: TextStyle(fontSize: 14, letterSpacing: 0), ), ), - Expanded( - child: CustomTextField( - numeric: true, - hint: "MM", - length: 2, - ), - ), - Expanded( - child: CustomTextField( - numeric: true, - hint: "AAAA", - length: 4, - ), + Row( + spacing: 10, + children: [ + Expanded( + child: CustomTextField( + numeric: true, + hint: "DD", + length: 2, + ), + ), + Expanded( + child: CustomTextField( + numeric: true, + hint: "MM", + length: 2, + ), + ), + Expanded( + child: CustomTextField( + numeric: true, + hint: "AAAA", + length: 4, + ), + ), + ], ), ], ), @@ -57,13 +69,15 @@ class CreateProfileScreen extends ConsumerWidget { label: "Dirección completa", hint: "Nombre de la calle", ), - TextButton( - onPressed: () => {}, - child: Text( - "Cambiar dirección", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: theme.getColorFor(ThemeCode.textPrimary)), + Align( + alignment: Alignment.topLeft, + child: CustomTextButton( + onPressed: () => {}, + text: "Cambiar dirección", + size: 18, + weight: FontWeight.w500, ), - ), + ) ], ); } diff --git a/modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart index 77cc1f12..815d12f6 100644 --- a/modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart +++ b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart @@ -37,8 +37,8 @@ class LinkWatchPreviousScreen extends ConsumerWidget{ children: [ Container( decoration: ShapeDecoration( - shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))), - color: theme.getColorFor(ThemeCode.backgroundSecondary) + shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))), + color: theme.getColorFor(ThemeCode.backgroundSecondary) ), width: 48, height: 48, diff --git a/modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart index c6a8a9d6..85b9c60b 100644 --- a/modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart +++ b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart @@ -21,13 +21,69 @@ class LinkWatchScreenState extends ConsumerState{ return Column( spacing: 32, children: [ - Row( + Stack( children: [ - Text("Escanea la correa"), - Spacer(), - Text("Escanea el reloj") + Divider( + color: theme.getColorFor(ThemeCode.buttonPrimary), + thickness: 4, + indent: 93, + endIndent: 93, + height: 48, + ), + if (widget.step==1)Divider( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + thickness: 4, + indent: 186, + endIndent: 93, + height: 48, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 69), + child: Row( + children: [ + Container( + decoration: ShapeDecoration( + shape: CircleBorder(), + color: theme.getColorFor(ThemeCode.buttonPrimary) + ), + width: 48, + height: 48, + child: Center(child: Text( + "1", + style: TextStyle( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + fontSize: 24 + ) + )) + ), + Spacer(), + Container( + decoration: ShapeDecoration( + shape: CircleBorder(), + color: theme.getColorFor(widget.step==1 ? ThemeCode.backgroundSecondary : ThemeCode.buttonPrimary) + ), + width: 48, + height: 48, + child: Center(child: Text( + "2", + style: TextStyle( + color: theme.getColorFor(widget.step==1 ? ThemeCode.textPrimary : ThemeCode.backgroundSecondary), + fontSize: 24 + ) + )) + ), + ], + ), + ) ], ), + Row(children: [ + Spacer(), + Text("Escanea la correa"), + Spacer(), + Text("Escanea el reloj"), + Spacer(), + ]), Container( padding: EdgeInsets.all(40), decoration: BoxDecoration( @@ -36,24 +92,36 @@ class LinkWatchScreenState extends ConsumerState{ ), child: SvgPicture.asset("assets/images/ui/qr.svg") ), - if (widget.step == 2)Text("O inserta el código del reloj"), - if (widget.step == 2)Row( + if (widget.step == 2)Column( spacing: 16, children: [ - Expanded(child: CustomTextField( - hint: "XXXXXXXXXX", - )), - Expanded(child: FilledButton( - style: ButtonStyle(backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary))), - onPressed: ()=>{}, - child: Expanded(child: Center(child: Text( - "Continuar con código", - style: TextStyle(fontSize: 16, letterSpacing: 0)))) - )) + Align( + alignment: Alignment.bottomLeft, + child: Text("O inserta el código del reloj"), + ), + Row( + spacing: 16, + children: [ + Expanded(child: CustomTextField( + hint: "XXXXXXXXXX", + )), + Expanded(child: PrimaryButton( + onPressed: ()=>{}, + text: "Continuar con código", + size: 16, + color: theme.getColorFor(ThemeCode.buttonSecondary) + )) + ], + ), ], ), - Text("Si no consigues vincular su correa o reloj"), - TextButton(onPressed: ()=>{}, child: Text("Contáctanos")) + Column( + spacing: 8, + children: [ + Text("Si no consigues vincular su correa o reloj"), + CustomTextButton(onPressed: ()=>{}, text: "Contáctanos", weight: FontWeight.w500, size: 18) + ], + ) ], ); } diff --git a/modules/auth/lib/src/login/presentation/link_phone_screen.dart b/modules/auth/lib/src/login/presentation/link_phone_screen.dart index 9c28b293..2a834e9d 100644 --- a/modules/auth/lib/src/login/presentation/link_phone_screen.dart +++ b/modules/auth/lib/src/login/presentation/link_phone_screen.dart @@ -10,62 +10,69 @@ class LinkPhoneScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + // TextEditingController phoneController = TextEditingController(); // String? phone; - return Scaffold(body: SafeArea( - child: Container( - margin: EdgeInsets.symmetric(horizontal: 24), - child: Expanded( - child: Center( - child: Column( - spacing: 48, - children: [ - Spacer(flex: 8), - Text( - "¡Nos alegra mucho tenerte por aquí!", - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), - ), - Text( - "Para poder entrar de forma segura, te vamos a enviar un código al teléfono", - ), - Row( - spacing: 10, - children: [ - DropdownMenu( - initialSelection: "es", - dropdownMenuEntries: List.generate(3, ( - int index, - ) { - return DropdownMenuEntry( - labelWidget: Icon(Icons.outlined_flag), - label: "es", - value: "es", - ); - }), - ), - Expanded( - child: CustomTextField( - label: "Teléfono móvil", - hint: "Teléfono", - numeric: true - ) - ), - ], - ), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () => navigationContract.pushTo('/phone_code'), - child: Text("Siguiente"), + return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), + body: SafeArea( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 24), + child: Expanded( + child: Center( + child: Column( + spacing: 48, + children: [ + Spacer(flex: 8), + Text( + "¡Nos alegra mucho tenerte por aquí!", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0), ), - ), - Spacer(flex: 10) - ], + Text( + "Para poder entrar de forma segura, te vamos a enviar un código al teléfono", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + Column( + spacing: 8, + children: [ + Align(alignment: Alignment.bottomLeft, child: Text( + "Teléfono móvil", + style: TextStyle(fontSize: 14, letterSpacing: 0), + )), + Row( + spacing: 10, + children: [ + CustomDropdown( + value: 0, + 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 + ) + ), + ], + ), + ], + ), + PrimaryButton( + onPressed: () => navigationContract.pushTo('/phone_code'), + text: "Siguiente", + color: theme.getColorFor(ThemeCode.buttonPrimary), + ), + Spacer(flex: 10) + ], + ), ), ), ), - ), )); } } diff --git a/modules/auth/lib/src/login/presentation/login_screen.dart b/modules/auth/lib/src/login/presentation/login_screen.dart index e374bd39..ce2ea7b2 100644 --- a/modules/auth/lib/src/login/presentation/login_screen.dart +++ b/modules/auth/lib/src/login/presentation/login_screen.dart @@ -12,70 +12,140 @@ class LoginScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + bool passwordVisible = true; - return Scaffold( - body: Expanded( - child: Center( - child: Container( - margin: EdgeInsets.all(30), + + final content = [ + Column( + spacing: 8, + children: [ + Icon(Icons.check, color: theme.getColorFor(ThemeCode.buttonPrimary), size: 50), + Text( + "¡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: () => + navigationContract.pushTo('/recover_password'), + size: 16, + )), + ], + ) + ], + ), + PrimaryButton( + onPressed: () => navigationContract.goTo('/main/home'), + text: "Iniciar sesión", + color: theme.getColorFor(ThemeCode.buttonPrimary) + ), + Container( + padding: EdgeInsets.only(top: 24), child: Column( - spacing: 10, + spacing: 24, children: [ - Icon(Icons.check, color: Color(0xFF329e95), size: 50), - Text( - "¡Te damos la bienvenida!", - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), - ), - CustomTextField( - hint: "Nombre de usuario", - label: "Nombre de usuario", - ), - CustomTextField( - showPassword: passwordVisible, - label: "Contraseña", - hint: "********" - ), - TextButton( - onPressed: () => - navigationContract.pushTo('/recover_password'), - child: Text("¿Has olvidado la contraseña?"), - ), - FilledButton( - onPressed: () => navigationContract.pushTo('/main/home'), - child: Text("Iniciar sesión"), - ), - Stack(children: [Divider(), Text("o continúa con")]), + Stack(children: [ + Divider(endIndent: 74, indent: 74), + Align( + alignment: Alignment.center, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 14), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + child: Text("o continúa con"), + ) + ) + ]), Row( spacing: 20, children: [ - OutlinedButton( + Spacer(), + SecondaryButton( onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (_) => LoadingGoogleScreen(), ), ), - child: Text("Google", semanticsLabel: "Google"), + radius: 16, + padding: 44, + text: "Google", + label: "Google", ), - OutlinedButton( - onPressed: () => {}, - child: Icon(Icons.apple, semanticLabel: "Apple"), + SecondaryButton( + onPressed: ()=>{}, + radius: 16, + padding: 44, + icon: Icons.apple, + label: "Apple", ), + Spacer(), ], ), - Text("¿No tienes cuenta?"), - TextButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => SignupScreen()), - ), - child: Text("Crear una ahora"), - ), ], ), ), - ), + Column( + spacing: 8, + children: [ + Text( + "¿No tienes cuenta?", + style: TextStyle(fontSize: 18, letterSpacing: 0) + ), + TextButton( + onPressed: () => navigationContract.goTo('/signup'), + child: Text( + "Crear una ahora", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0) + ) + ), + ], + ) + ], ), + ]; + + 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, + ), + ), + ) + ) ); } } diff --git a/modules/auth/lib/src/login/presentation/phone_code_screen.dart b/modules/auth/lib/src/login/presentation/phone_code_screen.dart index 772703aa..c80878ca 100644 --- a/modules/auth/lib/src/login/presentation/phone_code_screen.dart +++ b/modules/auth/lib/src/login/presentation/phone_code_screen.dart @@ -1,4 +1,6 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:navigation/navigation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -15,64 +17,87 @@ class PhoneCodeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( margin: EdgeInsets.all(30), child: Expanded( child: Center( child: Column( - spacing: 15, + spacing: 48, children: [ Spacer(flex: 8), - Text( - "Conéctate", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - Text.rich( - TextSpan( - text: "Hemos enviado el código al ", - children: [ + Column( + spacing: 24, + children: [ + Text( + "Conéctate", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + ), + Text.rich( TextSpan( - // text: widget.phone, - style: TextStyle(fontWeight: FontWeight.bold), + text: "Hemos enviado el código al ", + children: [ + TextSpan( + // text: widget.phone, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], ), - ], - ), + ), + Text("Introduce el código aquí"), + Row( + spacing: 8, + children: List.generate(6, (int i) { + return Expanded( + child: TextField( + focusNode: focusNodes[i], + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: "0", + counterText: "", + border: OutlineInputBorder(), + ), + maxLength: 1, + onChanged: (String value) => { + value != "" + ? focusNodes[i + 1].requestFocus() + : focusNodes[i - 1].requestFocus(), + }, + ), + ); + }), + ), + ], ), - Text("Introduce el código aquí"), - Row( - spacing: 20, - children: List.generate(6, (int i) { - return Expanded( - child: TextField( - focusNode: focusNodes[i], - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: "0", - counterText: "", - border: OutlineInputBorder(), + Column( + spacing: 24, + children: [ + PrimaryButton( + onPressed: () => {navigationContract.pushTo('/login')}, + text: "Entrar", + color: theme.getColorFor(ThemeCode.buttonPrimary), + ), + Column( + spacing: 8, + children: [ + Text( + "¿No lo has recibido?", + style: TextStyle(fontSize: 18, letterSpacing: 0, height: 1.5), ), - maxLength: 1, - onChanged: (String value) => { - value != "" - ? focusNodes[i + 1].requestFocus() - : focusNodes[i - 1].requestFocus(), - }, - ), - ); - }), - ), - FilledButton( - onPressed: () => {navigationContract.pushTo('/login')}, - child: Text("Entrar"), - ), - Text("¿No lo has recibido?"), - TextButton( - onPressed: () => {}, - child: Text( - "Volver a intentarlo", - style: TextStyle(fontWeight: FontWeight.bold), - ), + CustomTextButton( + onPressed: () => {}, + text: "Volver a intentarlo", + size: 18, + weight: FontWeight.w500, + ), + ], + ) + ], ), Spacer(flex: 10), ], diff --git a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart index 0ae92371..e7858573 100644 --- a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart +++ b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart @@ -31,8 +31,9 @@ class WelcomeScreenState extends ConsumerState{ final theme = ref.watch(themePortProvider); return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( - padding: EdgeInsets.symmetric(horizontal: 24), + padding: EdgeInsets.only(top: 24), child: Center( child: Column( spacing: 48, @@ -42,7 +43,11 @@ class WelcomeScreenState extends ConsumerState{ Column( spacing: 24, children: [ - StepIndicator(max: 3, current: currentStep+1, color: theme.getColorFor(ThemeCode.buttonSecondary)), + StepIndicator( + max: 3, + current: currentStep+1, + color: theme.getColorFor(ThemeCode.buttonSecondary) + ), generateButtons(theme, 3, currentStep+1) ] ), @@ -56,29 +61,29 @@ class WelcomeScreenState extends ConsumerState{ Widget generateButtons(ThemePort theme, int max, int step){ if (step==max) { - return FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonPrimary)) - ), + return PrimaryButton( onPressed: () => navigationContract.goTo('/link_phone'), - child: Expanded(child: Center(child: Text('Continuar'))) - ); + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonPrimary), + width: 324, + ); } else { return Column( spacing: 16, children: [ - FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary)) - ), + PrimaryButton( onPressed: ()=>setState(() { currentStep++; }), - child: Expanded(child: Center( child: Text("Siguiente"))) + text: "Siguiente", + color: theme.getColorFor(ThemeCode.buttonSecondary), + width: 324, ), - TextButton( + CustomTextButton( onPressed: ()=>navigationContract.goTo('/link_phone'), - child: Text("Omitir") + text: "Omitir", + size: 18, + weight: FontWeight.w500, ) ], ); @@ -97,37 +102,57 @@ class WelcomeScreenState extends ConsumerState{ Text( "Aprende a gestionar su dinero", textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30) + style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) ), Text( "Tu peque crea hábitos y se divierte mientras lo hace", textAlign: TextAlign.center, - style: TextStyle(fontSize: 18) + style: TextStyle(fontSize: 18, letterSpacing: 0) ) ] ) ] ), Column( + spacing: 48, children: [ SvgPicture.asset("assets/images/ui/bienvenida_paso2.svg"), - Text("Tranquilidad en cada pago que hacen", - textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30)), - Text("Supervisa gastos, fija límites y acompáñalos en cada paso", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18)), + Column( + spacing: 16, + children: [ + Text( + "Tranquilidad en cada pago que hace", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) + ), + Text( + "Supervisa sus gastos, fija límites y acompáñale en cada paso", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, letterSpacing: 0) + ), + ], + ) ], ), Column( + spacing: 48, children: [ SvgPicture.asset("assets/images/ui/bienvenida_paso3.svg"), - Text("Pagos fáciles y seguros en sus manos", - textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30)), - Text("Podrá pagar desde su reloj.\n Sin móvil ni efectivo", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18)), + Column( + spacing: 16, + children: [ + Text( + "Pagos fáciles y seguros, en sus manos", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) + ), + Text( + "Podrá pagar desde su reloj.\n Sin móvil ni efectivo", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, letterSpacing: 0) + ), + ], + ) ], ), ]; diff --git a/modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart b/modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart deleted file mode 100644 index 3e21e2c1..00000000 --- a/modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:auth/src/recover_password/presentation/new_password_screen.dart'; -import 'package:design_system/design_system.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class EmailSentScreen extends ConsumerWidget { - final String email; - - const EmailSentScreen({super.key, required this.email}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = ref.watch(themePortProvider); - - return Scaffold( - body: Container( - margin: EdgeInsets.all(30), - child: Center( - child: Column( - spacing: 20, - children: [ - Spacer(flex: 8), - Text( - "Recuperar contraseña", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - Spacer(flex: 1), - Row( - spacing: 10, - children: [ - Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), - ), - Text( - "Correo enviado correctamente", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - Spacer(flex: 1), - Text( - "Revisa tu email y haz clic en el enlace para crear una nueva contraseña", - ), - Text( - "Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"", - ), - Row( - spacing: 10, - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => {}, - child: Text("Reenviar correo"), - ), - ), - Expanded( - child: FilledButton( - onPressed: () => { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (_) => NewPasswordScreen(), - // ), - // ), - }, - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonSecondary), - ), - ), - child: Text("Continuar"), - ), - ), - ], - ), - Spacer(flex: 10), - ], - ), - ), - ), - ); - } -} diff --git a/modules/auth/lib/src/recover_password/presentation/new_password_screen.dart b/modules/auth/lib/src/recover_password/presentation/new_password_screen.dart index ed2cb62d..9014d7e3 100644 --- a/modules/auth/lib/src/recover_password/presentation/new_password_screen.dart +++ b/modules/auth/lib/src/recover_password/presentation/new_password_screen.dart @@ -42,104 +42,137 @@ class NewPasswordScreenState extends ConsumerState { final theme = ref.watch(themePortProvider); return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( - margin: EdgeInsets.all(30), + padding: EdgeInsets.all(24), child: Center( child: Column( - spacing: 10, + spacing: 48, children: [ - Spacer(flex: 4), - Text( - "Recuperar contraseña", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - CustomTextField( - showPassword: passwordVisible, - label: "Nueva contraseña", - hint: "********", - onChanged: (value) => { - setState(() { - password = value; - securityChecks = checkSecurity(value); - }), - }, - ), - CustomTextField( - showPassword: passwordVisible, - label: "Repetir contraseña", - hint: "********", - onChanged: (value) => { - setState(() { - equalPasswords = password == value; - }), - }, - ), - Row( + Spacer(), + Column( + spacing: 32, children: [ - securityChecks["min"]! - ? Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), + Text( + "Recuperar contraseña", + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0), + ), + Column( + spacing: 16, + children: [ + CustomTextField( + showPassword: passwordVisible, + label: "Nueva contraseña", + hint: "********", + onChanged: (value) => { + setState(() { + password = value; + securityChecks = checkSecurity(value); + }), + }, + ), + CustomTextField( + showPassword: passwordVisible, + label: "Repetir contraseña", + hint: "********", + onChanged: (value) => { + setState(() { + equalPasswords = password == value; + }), + }, + ), + Column( + spacing: 4, + children: [ + Row( + spacing: 8, + children: [ + Icon( + Icons.check, + color: theme.getColorFor(securityChecks["min"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary), + ), + Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)), + ], + ), + Row( + spacing: 8, + children: [ + Icon( + Icons.check, + color: theme.getColorFor(securityChecks["capital"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary), + ), + Text("Una mayúscula", style: TextStyle(fontSize: 14)), + ], + ), + Row( + spacing: 8, + children: [ + Icon( + Icons.check, + color: theme.getColorFor(securityChecks["number"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary), + ), + Text("Un número", style: TextStyle(fontSize: 14)), + ], + ), + Row( + spacing: 8, + children: [ + Icon( + Icons.check, + color: theme.getColorFor(securityChecks["special"]! + ? ThemeCode.buttonPrimary + : ThemeCode.buttonSecondary), + ), + Text("Un carácter especial", style: TextStyle(fontSize: 14)), + ], + ), + ], + ) + ], + ), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Text( + "Teléfono móvil", + style: TextStyle(fontSize: 14, letterSpacing: 0), ) - : Icon( - Icons.cancel_outlined, - color: theme.getColorFor(ThemeCode.buttonSecondary), - ), - Text("Al menos 8 caracteres"), + ), + Row( + spacing: 8, + children: [ + CustomDropdown( + value: 0, + 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 + )) + ] + ), + ], + ) + ], ), - Row( - children: [ - securityChecks["capital"]! - ? Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), - ) - : Icon( - Icons.cancel_outlined, - color: theme.getColorFor(ThemeCode.buttonSecondary), - ), - Text("Una mayúscula"), - ], + PrimaryButton( + onPressed: ()=>{navigationContract.goTo('/main/home')}, + text: "Aceptar", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), - Row( - children: [ - securityChecks["number"]! - ? Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), - ) - : Icon( - Icons.cancel_outlined, - color: theme.getColorFor(ThemeCode.buttonSecondary), - ), - Text("Un número"), - ], - ), - Row( - children: [ - securityChecks["special"]! - ? Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.buttonPrimary), - ) - : Icon( - Icons.cancel_outlined, - color: theme.getColorFor(ThemeCode.buttonSecondary), - ), - Text("Un carácter especial"), - ], - ), - Spacer(flex: 1), - FilledButton( - onPressed: ()=>{navigationContract.pushTo('/main/home')}, - child: Container( - width: double.infinity, - padding: EdgeInsets.all(20), - child: Text("Aceptar"), - ), - ), - Spacer(flex: 4), + Spacer(), ], ), ), diff --git a/modules/auth/lib/src/recover_password/presentation/restore_password_screen.dart b/modules/auth/lib/src/recover_password/presentation/restore_password_screen.dart index 56972a94..c3f1bfcd 100644 --- a/modules/auth/lib/src/recover_password/presentation/restore_password_screen.dart +++ b/modules/auth/lib/src/recover_password/presentation/restore_password_screen.dart @@ -1,4 +1,4 @@ -import 'package:auth/src/recover_password/presentation/email_sent_screen.dart'; +import 'package:auth/src/recover_password/presentation/sent_screen.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:navigation/navigation.dart'; @@ -14,50 +14,85 @@ class RestorePasswordScreen extends ConsumerWidget { final theme = ref.watch(themePortProvider); return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: Container( margin: EdgeInsets.all(30), child: Center( child: Column( - spacing: 30, + spacing: 48, children: [ Spacer(flex: 8), - Text( - "Recuperar contaseña", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - Text( - "Introduce tu email para enviarte un enlace de recuperación", - ), - CustomTextField( - label: "Correo electrónico", - hint: "Correo electrónico", - ), - Row( - spacing: 20, + Column( + spacing: 32, children: [ - Expanded( - child: OutlinedButton( - onPressed: () => {Navigator.pop(context)}, - child: Text("Volver"), - ), + Text( + "Recuperar contaseña", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), ), - Expanded( - child: FilledButton( - onPressed: () => { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => EmailSentScreen(email: ""), - ), - ), - }, - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonSecondary), - ), + Text( + "Introduce tu email para enviarte un enlace de recuperación", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, letterSpacing: 0), + ), + ], + ), + Column( + spacing: 40, + children: [ + CustomTextField( + label: "Correo electrónico", + hint: "Correo electrónico", + ), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Text( + "Teléfono móvil", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ) ), - child: Text("Enviar"), - ), + Row( + spacing: 10, + children: [ + CustomDropdown( + value: 0, + 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 + ) + ), + ], + ), + ], + ), + Row( + spacing: 20, + children: [ + Expanded( child: SecondaryButton( + onPressed: () => {Navigator.pop(context)}, + text: "Volver" + )), + Expanded( child: PrimaryButton( + onPressed: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => SentScreen(format: "email"), + ), + ), + }, + text: "Enviar", + size: 16, + color: theme.getColorFor(ThemeCode.buttonSecondary) + )), + ], ), ], ), diff --git a/modules/auth/lib/src/recover_password/presentation/sent_screen.dart b/modules/auth/lib/src/recover_password/presentation/sent_screen.dart new file mode 100644 index 00000000..2dc4e3f6 --- /dev/null +++ b/modules/auth/lib/src/recover_password/presentation/sent_screen.dart @@ -0,0 +1,98 @@ +import 'package:auth/src/recover_password/presentation/new_password_screen.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class SentScreen extends ConsumerWidget { + final String format; + + const SentScreen({ + super.key, + required this.format + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), + body: Container( + margin: EdgeInsets.all(24), + child: Center( + child: Column( + spacing: 48, + children: [ + Spacer(flex: 8), + Text( + "Recuperar contraseña", + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0), + ), + Row( + spacing: 10, + children: [ + Spacer(), + Icon( + Icons.check, + color: theme.getColorFor(ThemeCode.buttonPrimary), + ), + Text( + format=="email" + ?"Correo enviado correctamente" + :"SMS enviado correctamente", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Spacer(), + ], + ), + Column( + spacing: 16, + children: [ + Text( + format=="email" + ?"Revisa tu email y haz clic en el enlace para crear una nueva contraseña." + :"Revisa tu móvil y sigue las instrucciones para crear una nueva contraseña.", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, letterSpacing: 0), + ), + Text( + format=="email" + ?"Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"." + :"Si no recibes el SMS en unos minutos, asegúrate de tener cobertura o pulsa \"Reenviar SMS \".", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ], + ), + Row( + spacing: 10, + children: [ + Expanded( child: SecondaryButton( + onPressed: () => {}, + text: format=="email" + ?"Reenviar correo" + :"Reenviar SMS" + )), + Expanded( + child: PrimaryButton( + onPressed: ()=>Navigator.push( + context, + MaterialPageRoute( + builder: (_) => NewPasswordScreen(), + ), + ), + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonSecondary) + ), + ), + ], + ), + Spacer(flex: 10), + ], + ), + ), + ), + ); + } +} diff --git a/modules/auth/lib/src/sign_up/account_created_screen.dart b/modules/auth/lib/src/sign_up/account_created_screen.dart index b9b8f8fd..52f47761 100644 --- a/modules/auth/lib/src/sign_up/account_created_screen.dart +++ b/modules/auth/lib/src/sign_up/account_created_screen.dart @@ -69,7 +69,7 @@ class AccountCreatedScreen extends ConsumerWidget { "Crea la cuenta de tu peque e ingresa su \nprimera paga para utilizarla con su reloj", textAlign: TextAlign.center, ), - FilledButton( + PrimaryButton( onPressed: () => { if (kidAccount){ navigationContract.goTo('/main/home') @@ -77,7 +77,8 @@ class AccountCreatedScreen extends ConsumerWidget { navigationContract.pushTo('/device_signup') } }, - child: Text("Continuar"), + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), Spacer(flex: 8), ], diff --git a/modules/auth/lib/src/sign_up/signup_address_screen.dart b/modules/auth/lib/src/sign_up/signup_address_screen.dart index e5e3ac68..dc967a94 100644 --- a/modules/auth/lib/src/sign_up/signup_address_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_address_screen.dart @@ -1,52 +1,100 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class SignupAddressScreen extends StatelessWidget { +class SignupAddressScreen extends ConsumerStatefulWidget { const SignupAddressScreen({super.key}); + @override + ConsumerState createState() => SignupAddressScreenState(); + +} + +class SignupAddressScreenState extends ConsumerState{ + + late String country; + late int relation; + + @override + void initState() { + relation = 0; + super.initState(); + } + @override Widget build(BuildContext context) { return Column( spacing: 24, children: [ - Row( + Column( spacing: 8, children: [ - Expanded(child: CustomTextField( - label: "Fecha de nacimiento", - hint: "DD", - length: 2, - numeric: true, + Align(alignment: Alignment.bottomLeft, child: Text( + "Fecha de nacimiento", + style: TextStyle(fontSize: 14, letterSpacing: 0), )), - Expanded(child: CustomTextField( - hint: "MM", - length: 2, - numeric: true, - )), - Expanded(child: CustomTextField( - hint: "AAAA", - length: 4, - numeric: true, - )), - ] + Row( + spacing: 8, + children: [ + Expanded(child: CustomTextField( + //label: "Fecha de nacimiento", + hint: "DD", + length: 2, + numeric: true, + )), + Expanded(child: CustomTextField( + hint: "MM", + length: 2, + numeric: true, + )), + Expanded(child: CustomTextField( + hint: "AAAA", + length: 4, + numeric: true, + )), + ] + ), + ], ), - DropdownMenu( - width: double.infinity, - label: Text("¿Qué familiar eres?"), - dropdownMenuEntries: [ - DropdownMenuEntry(label: "Padre", value: "Padre"), - DropdownMenuEntry(label: "Madre", value: "Madre"), - DropdownMenuEntry(label: "Tutor", value: "Tutor"), + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Text( + "¿Qué familiar eres?", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + CustomDropdown( + items: [Text("Padre"), Text("Madre"), Text("Tutor")], + hint: "¿Qué familiar eres?", + onChanged: (value)=>setState(() { + relation = value; + }) + ), ], ), CustomTextField(label: "Dirección completa", hint: "Calle Gran Vía 30 6º, 28013"), CustomTextField(label: "Ciudad", hint: "Ciudad"), - DropdownMenu( - dropdownMenuEntries: List.generate(3, (int index) { - return DropdownMenuEntry(value: "España", label: "España"); - }), - hintText: "País", - width: double.infinity, + Column( + spacing: 8, + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Text( + "País", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + CustomDropdown( + items: [Text("España"), Text("Francia"), Text("Portugal")], + hint: "País", + onChanged: (value)=>setState(() { + country = value; + }) + ), + ], ), CustomTextField(label: "Nacionalidad", hint: "España"), ], diff --git a/modules/auth/lib/src/sign_up/signup_builder.dart b/modules/auth/lib/src/sign_up/signup_builder.dart new file mode 100644 index 00000000..4356b0b7 --- /dev/null +++ b/modules/auth/lib/src/sign_up/signup_builder.dart @@ -0,0 +1,18 @@ +import 'package:auth/src/sign_up/signup_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:get_it/get_it.dart'; +import 'package:navigation/navigation.dart'; + +class SignupBuilder { + const SignupBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: SignupScreen(navigationContract: navigationContract), + ); + } +} diff --git a/modules/auth/lib/src/sign_up/signup_personal_screen.dart b/modules/auth/lib/src/sign_up/signup_personal_screen.dart index a8046eb4..1afc1186 100644 --- a/modules/auth/lib/src/sign_up/signup_personal_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_personal_screen.dart @@ -12,19 +12,22 @@ class SignupPersonalScreen extends StatelessWidget{ CustomTextField(label: "Nombre", hint: "Nombre"), CustomTextField(label: "Apellidos", hint: "Apellidos"), CustomTextField(label: "DNI", hint: "DNI"), - Row(children: [ - DropdownMenu( - initialSelection: "es", - dropdownMenuEntries: List.generate(3, (int index){ - return DropdownMenuEntry(labelWidget: Icon(Icons.outlined_flag), label: "es", value: "es"); - }) - ), - Expanded(child: CustomTextField( - label: "Teléfono móvil", - hint: "123456789", - numeric: true - )) - ]), + Row( + spacing: 8, + children: [ + /*CustomDropdown( + value: 0, + items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)], + onChanged: (value)=> {}, + width: 80, + ),*/ + Expanded(child: CustomTextField( + label: "Teléfono móvil", + hint: "123456789", + numeric: true + )) + ] + ), CustomTextField(label: "Correo electrónico", hint: "Correo electrónico"), ], ); diff --git a/modules/auth/lib/src/sign_up/signup_screen.dart b/modules/auth/lib/src/sign_up/signup_screen.dart index 361389c2..21ea82fe 100644 --- a/modules/auth/lib/src/sign_up/signup_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_screen.dart @@ -1,4 +1,5 @@ import 'package:auth/src/widgets/layouts/form_step_layout.dart'; +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:auth/src/sign_up/signup_address_screen.dart'; import 'package:auth/src/sign_up/signup_personal_screen.dart'; @@ -7,19 +8,31 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get_it/get_it.dart'; import 'package:navigation/navigation.dart'; -class SignupScreen extends ConsumerStatefulWidget { - SignupScreen({super.key}); +import 'account_created_screen.dart'; - ConsumerState createState() => SignupScreenState(); +class SignupScreen extends ConsumerStatefulWidget { + NavigationContract navigationContract; + + SignupScreen({ + super.key, + required this.navigationContract + }); + + ConsumerState createState() => SignupScreenState(navigationContract); } class SignupScreenState extends ConsumerState { late int currentStep; + late bool acceptTerms; + final NavigationContract navigationContract; + + SignupScreenState(this.navigationContract); @override void initState() { super.initState(); currentStep = 0; + acceptTerms = false; } @override @@ -28,14 +41,50 @@ class SignupScreenState extends ConsumerState { } List getSteps() { + final theme = ref.watch(themePortProvider); + return [ FormStepLayout( title: "Crea tu usuario", - subtitle: "Nos aseguraremos de que la cuenta esté a nombre del adulto responsable", - supertitle: "Datos personales", + subtitle: "Con tu email y tu número podremos mantenerte siempre informado", + supertitle: "Usuario y contacto", currentStep: 1, numSteps: 3, body: [SignupPersonalScreen()], + footer: [ + Row( + spacing: 16, + children: [ + Expanded(child: SecondaryButton( + onPressed: ()=>{}, + text: "Atrás", + size: 16, + )), + Expanded(child: PrimaryButton( + onPressed: ()=>{setState(() { + currentStep++; + })}, + text: "Siguiente", + size: 16, + color: theme.getColorFor(ThemeCode.buttonSecondary) + )) + ] + ), + CheckboxListTile( + value: acceptTerms, + onChanged: (_) => setState(() { + acceptTerms = !acceptTerms; + }), + title: Text( + "Acepto los términos y condiciones", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + checkboxScaleFactor: 1.5, + contentPadding: EdgeInsets.zero, + activeColor: theme.getColorFor(ThemeCode.buttonPrimary), + controlAffinity: ListTileControlAffinity.leading, + ) + ], nextStep: ()=>{setState(() { currentStep++; })}, @@ -57,16 +106,19 @@ class SignupScreenState extends ConsumerState { ), FormStepLayout( title: "Identifícate", - subtitle: "Con tu email y tu número podremos mantenerte siempre informado", + subtitle: "Contraseña mínima de 8 caracteres, con una mayúscula, un número y un carácter especial", supertitle: "Usuario y contacto", currentStep: 3, numSteps: 3, body: [SignupUserScreen()], - nextStep: ()=>{GetIt.I().pushTo('/device_signup')}, + nextStep: ()=>{setState(() { + currentStep++; + })}, previousStep: ()=>{setState(() { currentStep--; })}, ), + AccountCreatedScreen(navigationContract: navigationContract, kidAccount: false) ]; } } diff --git a/modules/auth/lib/src/widgets/layouts/form_step_layout.dart b/modules/auth/lib/src/widgets/layouts/form_step_layout.dart index 90ac4668..65f03b54 100644 --- a/modules/auth/lib/src/widgets/layouts/form_step_layout.dart +++ b/modules/auth/lib/src/widgets/layouts/form_step_layout.dart @@ -31,6 +31,7 @@ class FormStepLayout extends ConsumerWidget{ final theme = ref.watch(themePortProvider); return Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), body: SafeArea(child: SingleChildScrollView(child: Container( color: theme.getColorFor(ThemeCode.backgroundPrimary), padding: EdgeInsets.only(top: 40, left: 24, right: 24), @@ -66,23 +67,25 @@ class FormStepLayout extends ConsumerWidget{ Widget navigationButtons(int currentStep, int numSteps, VoidCallback nextStep, VoidCallback previousStep, ThemePort theme) { if (currentStep == numSteps){ - return FilledButton( + return PrimaryButton( onPressed: nextStep, - style: ButtonStyle(backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonPrimary))), - child: Expanded(child: Center(child: Text("Continuar"))) + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonPrimary) ); } else { return Row( spacing: 16, children: [ - Expanded(child: OutlinedButton( + Expanded(child: SecondaryButton( onPressed: previousStep, - child: Expanded(child: Center(child: Text("Atrás"))) + text: "Atrás", + size: 16, )), - Expanded(child: FilledButton( + Expanded(child: PrimaryButton( onPressed: nextStep, - style: ButtonStyle(backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary))), - child: Expanded(child: Center(child: Text("Siguiente"))) + text: "Siguiente", + size: 16, + color: theme.getColorFor(ThemeCode.buttonSecondary) )) ] ); diff --git a/modules/home/lib/src/presentation/deposit_screen.dart b/modules/home/lib/src/presentation/deposit_screen.dart index f5ea4be9..4c7551fc 100644 --- a/modules/home/lib/src/presentation/deposit_screen.dart +++ b/modules/home/lib/src/presentation/deposit_screen.dart @@ -26,13 +26,10 @@ class DepositScreen extends ConsumerWidget { padding: EdgeInsets.all(10), child: Column( children: [ - FilledButton( - onPressed: () => {}, - child: Container( - width: double.infinity, - padding: EdgeInsets.all(20), - child: Center(child: Text("Añadir dinero")), - ), + PrimaryButton( + onPressed: ()=>{}, + text: "Añadir dinero", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), TextButton( onPressed: () => Navigator.pop(context), @@ -82,6 +79,7 @@ class DepositScreen extends ConsumerWidget { ), Text("Este dato aparecerá en el reloj del peque"), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Paga semanal'), controlAffinity: ListTileControlAffinity.leading, value: reason == "weekly", @@ -93,6 +91,7 @@ class DepositScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Objetivo semanal cumplido'), controlAffinity: ListTileControlAffinity.leading, value: reason == "goal", @@ -104,6 +103,7 @@ class DepositScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Gastos extraordinarios'), controlAffinity: ListTileControlAffinity.leading, value: reason == "extraordinary", @@ -115,6 +115,7 @@ class DepositScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Otro'), controlAffinity: ListTileControlAffinity.leading, value: reason == "other", @@ -153,6 +154,7 @@ class DepositScreen extends ConsumerWidget { ), Text("Este dato aparecerá en el reloj del peque"), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Ahora'), controlAffinity: ListTileControlAffinity.leading, value: program == false, @@ -164,6 +166,7 @@ class DepositScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Programar'), controlAffinity: ListTileControlAffinity.leading, value: program == true, diff --git a/modules/home/lib/src/presentation/extract_screen.dart b/modules/home/lib/src/presentation/extract_screen.dart new file mode 100644 index 00000000..6ce326ca --- /dev/null +++ b/modules/home/lib/src/presentation/extract_screen.dart @@ -0,0 +1,111 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +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{ + final Kid kid; + + @override + ExtractScreen({ + super.key, + required this.kid, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return WalletManagementLayout( + kid: kid, + 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€", + 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)) + ), + child: Column( + spacing: 16, + children: [ + PrimaryButton( + 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)) + ) + ], + ), + ) + ); + } +} \ 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 new file mode 100644 index 00000000..640c5e45 --- /dev/null +++ b/modules/home/lib/src/presentation/goals_screen.dart @@ -0,0 +1,786 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +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{ + 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.05)]; + final List savingsGoals; + + @override + GoalsScreen({ + super.key, + 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) + ] + }); + + @override + ConsumerState createState() => GoalsScreenState(); +} + +class GoalsScreenState extends ConsumerState{ + late bool fullPlan; + + @override + void initState() { + super.initState(); + fullPlan = true; + } + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + + return WalletManagementLayout( + kid: widget.kid, + children: [ + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(24)) + ), + child: Column( + spacing: 24, + children: [ + Row( + spacing: 8, + children: [ + 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( + "Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero", + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ], + ), + ), + SavingsBlock(fullPlan: fullPlan, savings: widget.savingsGoals), + TasksBlock(fullPlan: fullPlan, tasks: widget.tasks), + ], + footer: Container() + ); + } +} + +class SavingsBlock extends ConsumerStatefulWidget { + final bool fullPlan; + final List savings; + + @override + const SavingsBlock({ + super.key, + required this.fullPlan, + required this.savings + }); + + @override + ConsumerState createState() => SavingsBlockState(); +} + +class SavingsBlockState extends ConsumerState{ + late List showEdit; + late List showAdd; + late bool showNew; + + @override + void initState() { + super.initState(); + showEdit = widget.savings.map((_)=>false).toList(); + showAdd = widget.savings.map((_)=>false).toList(); + showNew = false; + } + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + + var 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)) + ), + child: Stack( + children: [ + 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, + 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, + ), + ), + 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, + label: "Motivo del ahorro", + lines: create? 2 : 1, + ), + CustomTextField( + hint: "30€", + label: "Seleciona la cantidad a ahorrar", + numeric: true, + ), + CheckboxListTile( + value: false, + onChanged: (_) => {}, + checkboxScaleFactor: 2, + controlAffinity: ListTileControlAffinity.leading, + activeColor: theme.getColorFor(ThemeCode.buttonPrimary), + contentPadding: EdgeInsets.zero, + title: Text( + "Ahorro automático desde su paga", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0), + ), + ), + CustomTextField( + hint: "2€", + label: "Selecciona la cantidad de dinero", + numeric: true, + ), + CheckboxListTile( + value: false, + onChanged: (_) => {}, + checkboxScaleFactor: 2, + controlAffinity: ListTileControlAffinity.leading, + activeColor: theme.getColorFor(ThemeCode.buttonPrimary), + contentPadding: EdgeInsets.zero, + title: Text( + "Mandar automáticamente el dinero al reloj del peque cuando consiga su objetivo", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ), + Column( + spacing: 10, + children: [ + Text( + "Este es el mensaje fijado por defecto que le llegará a su reloj:", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + Text( + "\"¡Genial, has conseguido ahorrar lo que querías!\"", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0), + ) + ], + ), + Column( + spacing: 8, + children: [ + CustomTextField( + hint: "Escribe tu mensaje", + label: "Escribir otro mensaje diferente:", + 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), + ) + ], + ) + ], + ), + Column( + spacing: 24, + children: [ + PrimaryButton( + onPressed: () => {}, + text: "Guardar cambios", + color: theme.getColorFor( + ThemeCode.buttonPrimary) + ), + TextButton( + onPressed: () { + if (create){ + setState(() { + showNew = false; + }); + } else { + setState(() { + showEdit[index] = false; + }); + } + }, + child: Text( + "Cancelar", + style: TextStyle(fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0), + ) + ) + ], + ) + ], + ); + + if (widget.fullPlan) { + 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)) + ), + child: Column( + spacing: 24, + children: [ + 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), + ), + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(24)), + color: theme.getColorFor(ThemeCode.backgroundPrimary) + ), + child: editBlock(create: true, index: null) + ) + ], + ), + ); + } else { + if (widget.savings.isNotEmpty) { + return Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(24)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + //border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary)) + ), + child: Column( + spacing: 24, + children: [ + Align(alignment: Alignment.topLeft, + child: Text( + "Ahorros", + style: TextStyle(fontSize: 20, + fontWeight: FontWeight.w500, + letterSpacing: 0), + ) + ), + ...List.generate(widget.savings.length, (int index) => + Container( + decoration: BoxDecoration( + border: BoxBorder.all( + color: theme.getColorFor(ThemeCode.textPrimary)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + padding: EdgeInsets.all(16), + child: Column( + spacing: 24, + children: [ + 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), + ), + ), + + Text( + widget.savings[index].name, + style: TextStyle(fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0), + ) + ], + )), + Container( + decoration: BoxDecoration( + color: theme.getColorFor( + ThemeCode.backgroundTertiary), + borderRadius: BorderRadius.all( + Radius.circular(16)), + ), + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + child: Row( + spacing: 8, + children: [ + Icon(Icons.account_balance, size: 24, + color: theme.getColorFor( + ThemeCode.buttonPrimary)), + MoneyText( + 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, + 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), + ), + ], + ) + ), + 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; + }), + child: Row( + spacing: 4, + children: [ + Spacer(), + Icon(Icons.account_balance, size: 24), + Text( + "Crear otro ahorro", + style: TextStyle(fontSize: 18, + fontWeight: FontWeight.w500, + letterSpacing: 0), + ), + Spacer() + ], + ) + ), + ], + ), + ); + } else { + return emptyBlock(fullPlan: true); + } + } + } else { + return emptyBlock(fullPlan: false); + } + } +} + +class TasksBlock extends ConsumerStatefulWidget { + + final bool fullPlan; + final List tasks; + + @override + const TasksBlock({ + super.key, + required this.fullPlan, + required this.tasks + }); + + @override + ConsumerState createState() => TasksBlockState(); +} + +class TasksBlockState extends ConsumerState{ + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + + 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)) + ), + child: Stack( + children: [ + 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( + 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) + ), + 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) + ), + padding: EdgeInsets.all(24), + child: Column( + spacing: 16, + 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) + ) + ], + ) + ], + ), + ), + 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), + ) + ], + ) + ), + ) + ], + ), + ) + ), + SecondaryButton( + onPressed: () => {}, + text: "Crear una lista de tareas nueva", + size: 14, + ), + ], + ) + ); + } else { + return emptyBlock(fullPlan: true); + } + } else { + return emptyBlock(fullPlan: false); + } + } +} \ No newline at end of file diff --git a/modules/home/lib/src/presentation/home_screen.dart b/modules/home/lib/src/presentation/home_screen.dart index ae75259c..b7f4b6f1 100644 --- a/modules/home/lib/src/presentation/home_screen.dart +++ b/modules/home/lib/src/presentation/home_screen.dart @@ -43,7 +43,7 @@ class HomeScreen extends ConsumerWidget { children: [ TextSpan( text: name, - style: TextStyle(fontWeight: FontWeight.bold), + style: TextStyle(fontWeight: FontWeight.w500), ), ], ), diff --git a/modules/home/lib/src/presentation/kid_wallet_screen.dart b/modules/home/lib/src/presentation/kid_wallet_screen.dart index 72cf2cba..04c5fe9b 100644 --- a/modules/home/lib/src/presentation/kid_wallet_screen.dart +++ b/modules/home/lib/src/presentation/kid_wallet_screen.dart @@ -2,6 +2,9 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:home/src/presentation/deposit_screen.dart'; +import 'package:home/src/presentation/extract_screen.dart'; +import 'package:home/src/presentation/goals_screen.dart'; +import 'package:home/src/presentation/lock_card_screen.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:home/src/presentation/limits_screen.dart'; import 'package:home/src/presentation/wage_screen.dart'; @@ -93,7 +96,12 @@ class KidWalletScreen extends ConsumerWidget { ), TextButton( style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), - onPressed: ()=>{}, + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => LockCardScreen(kid: kid) + ) + ), child: Row( spacing: 10, children: [ @@ -110,7 +118,7 @@ class KidWalletScreen extends ConsumerWidget { spacing: 16, children: [ Container( - padding: EdgeInsets.all(10), + padding: EdgeInsets.all(16), margin: EdgeInsets.only(top: 30), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -121,6 +129,7 @@ class KidWalletScreen extends ConsumerWidget { child: Row( children: [ TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), onPressed: () => Navigator.push( context, MaterialPageRoute( @@ -128,7 +137,7 @@ class KidWalletScreen extends ConsumerWidget { ) ), child: Column( - spacing: 10, + spacing: 8, children: [ Icon( Icons.add_circle_outline, @@ -149,6 +158,7 @@ class KidWalletScreen extends ConsumerWidget { ), Spacer(), TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), onPressed: () => Navigator.push( context, MaterialPageRoute( @@ -177,6 +187,7 @@ class KidWalletScreen extends ConsumerWidget { ), Spacer(), TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), onPressed: () => Navigator.push( context, MaterialPageRoute( @@ -205,7 +216,13 @@ class KidWalletScreen extends ConsumerWidget { ), Spacer(), TextButton( - onPressed: () => {}, + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => GoalsScreen(kid: kid) + ) + ), child: Column( spacing: 10, children: [ @@ -228,7 +245,13 @@ class KidWalletScreen extends ConsumerWidget { ), Spacer(), TextButton( - onPressed: () => {}, + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ExtractScreen(kid: kid) + ) + ), child: Column( spacing: 10, children: [ @@ -256,13 +279,13 @@ class KidWalletScreen extends ConsumerWidget { ), Container( padding: EdgeInsets.all(15), - height: 400, + height: 300, decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(20)) ), child: Expanded(child: Column( - spacing: 24, + //spacing: 24, children: [ Text("Últimos movimientos", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), activityList(context, theme) @@ -297,13 +320,14 @@ class KidWalletScreen extends ConsumerWidget { return Expanded( child: ListView( - padding: EdgeInsets.symmetric(vertical: 0), + padding: EdgeInsets.symmetric(vertical: 0), children: [ ...List.generate(activity.length, (int index) { - return Column( - spacing: 16, + return Container(padding: EdgeInsets.only(top: 24), + child: Column( + spacing: 8, children: [ - Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18)), + Align(alignment: Alignment.bottomLeft, child: Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18, height: 1.5))), Column( spacing: 16, children: List.generate( @@ -347,9 +371,9 @@ class KidWalletScreen extends ConsumerWidget { ) ) ] - ); + )); }), - TextButton(onPressed: () => {}, child: Text("Ver todos")), + TextButton(onPressed: () => {}, child: Align(alignment: Alignment.bottomLeft, child: Text("Ver todos"))), ] ) ); diff --git a/modules/home/lib/src/presentation/limits_screen.dart b/modules/home/lib/src/presentation/limits_screen.dart index 3f61160f..a3374424 100644 --- a/modules/home/lib/src/presentation/limits_screen.dart +++ b/modules/home/lib/src/presentation/limits_screen.dart @@ -22,7 +22,7 @@ class LimitsScreenState extends ConsumerState { @override void initState() { super.initState(); - dailyLimits = [ + 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,14 +41,27 @@ 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", "edit": false}, - {"title": "Transporte", "limit": "10", "edit": false}, - {"title": "Alimentación", "limit": "10", "edit": false}, + {"title": "Alimentación", "limit": "10", "active": true, "edit": false}, + {"title": "Transporte", "limit": "10", "active": false, "edit": false}, + {"title": "Alimentación", "limit": "10", "active": false, "edit": false}, + ]; + blocks = [ + {"title": "Alojamiento y Hoteles", "active": true}, + {"title": "Supermercados", "active": true}, + {"title": "Gasolineras", "active": true}, + {"title": "Restaurantes", "active": true}, + {"title": "Bares y discotecas", "active": true}, + {"title": "Licorerías", "active": true}, + {"title": "Estancos", "active": true}, ]; - blocks = []; } @override @@ -57,16 +70,32 @@ class LimitsScreenState extends ConsumerState { return WalletManagementLayout( kid: widget.kid, - footer: Column( - children: [ - FilledButton( - onPressed: () => {}, - child: SizedBox( - width: double.infinity, - child: Center(child: Text("Guardar límites")), + footer: Container( + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)) + ), + padding: EdgeInsets.all(24), + child: Column( + children: [ + PrimaryButton( + onPressed: () => {}, + text: "Guardar límites", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), - ), - ], + TextButton( + onPressed: ()=>Navigator.pop(context), + child: Text( + "Cancelar", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: theme.getColorFor(ThemeCode.textPrimary) + ) + ) + ) + ], + ), ), children: [ Container( @@ -78,9 +107,12 @@ class LimitsScreenState extends ConsumerState { child: Column( spacing: 10, children: [ - Text( - "Pon límite de gastos", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + Align( + alignment: Alignment.topLeft, + 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) { @@ -103,7 +135,9 @@ class LimitsScreenState extends ConsumerState { ), ], ), - if (dailyLimits[index]["edit"]) CustomTextField(), + if (dailyLimits[index]["edit"]) CustomTextField( + hint: "5€", + ), ], ); }), @@ -136,22 +170,113 @@ 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(), + if (timeLimits[index]["edit"]) CustomTextField( + hint: "5€", + ), ], ); }), ], ), ), + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(24)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + child: Column( + spacing: 24, + children: [ + Text( + "Condiciones", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), + ), + Column( + spacing: 8, + 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), + ), + 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, + ) + ] + ) + ), + ) + ], + ) + ), + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(24)) + ), + child: Column( + spacing: 24, + children: [ + Text( + "Comercios bloqueados", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0), + ), + Column( + spacing: 8, + children: List.generate(blocks.length, (int index) => + CheckboxListTile( + value: blocks[index]["active"], + onChanged: (_) => setState(() { + blocks[index]["active"] = !blocks[index]["active"]; + }), + title: Text( + blocks[index]["title"], + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + checkboxScaleFactor: 2, + 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/lock_card_screen.dart b/modules/home/lib/src/presentation/lock_card_screen.dart new file mode 100644 index 00000000..f8d92bfe --- /dev/null +++ b/modules/home/lib/src/presentation/lock_card_screen.dart @@ -0,0 +1,105 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:home/src/presentation/wallet_management_layout.dart'; +import 'package:sf_shared/sf_shared.dart'; + +class LockCardScreen extends ConsumerWidget{ + final Kid kid; + + @override + LockCardScreen({ + super.key, + required this.kid, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return WalletManagementLayout( + kid: kid, + 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( + "Bloqueo de tarjeta", + 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) + )) + ], + ), + 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( + "\"De momento hemos bloqueado el dinero del reloj\"", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0) + )) + ], + ), + Column( + spacing: 8, + children: [ + Text( + "Escribir mensaje a ${kid.name} del motivo del bloqueo", + 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)) + ), + child: Column( + spacing: 16, + children: [ + PrimaryButton( + 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)) + ) + ], + ), + ) + ); + } +} \ 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 641d31d6..2a7ad797 100644 --- a/modules/home/lib/src/presentation/wage_screen.dart +++ b/modules/home/lib/src/presentation/wage_screen.dart @@ -34,13 +34,10 @@ class WageScreen extends ConsumerWidget { child: Column( spacing: 10, children: [ - FilledButton( + PrimaryButton( onPressed: () => {}, - child: Container( - width: double.infinity, - padding: EdgeInsets.all(20), - child: Center(child: Text("Activar paga automática")), - ), + text: "Activar paga automática", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), TextButton(onPressed: () => {}, child: Text("Cancelar")), ], @@ -56,9 +53,11 @@ class WageScreen extends ConsumerWidget { child: Column( spacing: 10, children: [ - Text( - "Paga automática", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + Align(alignment: Alignment.topLeft, + child: Text( + "Paga automática", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), + ), ), CustomTextField( numeric: true, @@ -74,16 +73,21 @@ class WageScreen extends ConsumerWidget { color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(20)), ), - padding: EdgeInsets.all(10), + padding: EdgeInsets.all(24), child: Column( spacing: 10, children: [ - Text( - "Frecuencia", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + Align(alignment: Alignment.topLeft, + child: Text( + "Frecuencia", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ) + ), + Align(alignment: Alignment.topLeft, + child: Text("Cuándo se envía el dinero"), ), - Text("Cuándo se envía el dinero"), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Semanal'), controlAffinity: ListTileControlAffinity.leading, value: frequence == "weekly", @@ -95,6 +99,7 @@ class WageScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Cada dos semanas'), controlAffinity: ListTileControlAffinity.leading, value: frequence == "biweekly", @@ -106,6 +111,7 @@ class WageScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Mensual'), controlAffinity: ListTileControlAffinity.leading, value: frequence == "monthly", @@ -116,38 +122,26 @@ class WageScreen extends ConsumerWidget { }, activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), - Container( - width: double.infinity, - child: DropdownMenu( - label: Text("Día de la semana"), - initialSelection: "Domingo", - dropdownMenuEntries: List.generate(7, ( - int index, - ) { - final days = [ - "Lunes", - "Martes", - "Miércoles", - "Jueves", - "Viernes", - "Sábado", - "Domingo", - ]; - return DropdownMenuEntry( - value: days[index], - label: days[index], - ); - }), - ), + CustomDropdown( + items: [ + Text("Lunes"), + Text("Martes"), + Text("Miércoles"), + Text("Jueves"), + Text("Viernes"), + Text("Sábado"), + Text("Domingo"), + ], + values: ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"], + onChanged: (value)=> {}, + hint: "Día de la semana", ), - DropdownMenu( - label: Text("Hora del día"), - initialSelection: 9, - dropdownMenuEntries: List.generate(24, ( - int index, - ) { - return DropdownMenuEntry(value: index, label: "$index:00"); + CustomDropdown( + hint: "Hora del día", + items: List.generate(24,(int index){ + return Text("$index:00"); }), + onChanged: (value)=> {}, ), CustomTextField( lines: 3, @@ -168,16 +162,21 @@ class WageScreen extends ConsumerWidget { color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(20)), ), - padding: EdgeInsets.all(10), + padding: EdgeInsets.all(24), child: Column( spacing: 10, children: [ - Text( - "Condiciones", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + Align(alignment: Alignment.topLeft, + child: Text( + "Condiciones", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + ), + Align(alignment: Alignment.topLeft, + child: Text("Este dato aparecerá en el reloj del peque"), ), - Text("Este dato aparecerá en el reloj del peque"), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Sólo si cumple límites semanales'), controlAffinity: ListTileControlAffinity.leading, value: conditions["weeklyLimits"], @@ -189,6 +188,7 @@ class WageScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Sólo si no ha tenido incidencias'), controlAffinity: ListTileControlAffinity.leading, value: conditions["incidences"], @@ -200,6 +200,7 @@ class WageScreen extends ConsumerWidget { activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text('Pausar durante vacaciones'), controlAffinity: ListTileControlAffinity.leading, value: conditions["holidays"], diff --git a/modules/home/lib/src/presentation/wallet_item.dart b/modules/home/lib/src/presentation/wallet_item.dart index f85934e4..f4bbc00b 100644 --- a/modules/home/lib/src/presentation/wallet_item.dart +++ b/modules/home/lib/src/presentation/wallet_item.dart @@ -93,7 +93,13 @@ class WalletItem extends ConsumerWidget{ SizedBox( width: 169, height: 60, - child: FilledButton( + child: PrimaryButton( + onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))), + text: "+ Añadir dinero", + color: theme.getColorFor(ThemeCode.buttonSecondary), + radius: 12, + ) + /*FilledButton( onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))), style: ButtonStyle( shape: WidgetStateProperty.all( @@ -113,7 +119,7 @@ class WalletItem extends ConsumerWidget{ ) ) ) - ) + )*/ ) ]) ] @@ -138,7 +144,8 @@ class PhotoDialog extends ConsumerWidget{ decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.only( - topRight: Radius.circular(16), topLeft: Radius.circular(16)) + topRight: Radius.circular(16), topLeft: Radius.circular(16) + ) ), child: Column( spacing: 24, @@ -146,21 +153,10 @@ class PhotoDialog extends ConsumerWidget{ Column( spacing: 16, children: [ - FilledButton( - onPressed: () => {}, - child: Expanded( - child: Center( - child: Text( - "Cámara", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: theme.getColorFor( - ThemeCode.textSecondary) - ) - ) - ) - ) + PrimaryButton( + onPressed: ()=>{}, + text: "Cámara", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), OutlinedButton( onPressed: () => {}, diff --git a/modules/notifications/lib/src/presentation/activity_screen.dart b/modules/notifications/lib/src/presentation/activity_screen.dart index 43a7c26c..a8325912 100644 --- a/modules/notifications/lib/src/presentation/activity_screen.dart +++ b/modules/notifications/lib/src/presentation/activity_screen.dart @@ -21,7 +21,7 @@ class ActivityScreen extends ConsumerWidget { final content = [ Text( "Movimientos recientes", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 24), ), Row( spacing: 20, @@ -54,4 +54,4 @@ class ActivityScreen extends ConsumerWidget { ), ); } -} +} \ No newline at end of file diff --git a/modules/notifications/lib/src/presentation/alert_screen.dart b/modules/notifications/lib/src/presentation/alert_screen.dart index 86406541..9f8c98db 100644 --- a/modules/notifications/lib/src/presentation/alert_screen.dart +++ b/modules/notifications/lib/src/presentation/alert_screen.dart @@ -29,21 +29,29 @@ class AlertScreenState extends ConsumerState { Widget build(BuildContext context) { final theme = ref.watch(themePortProvider); - return Scaffold( - backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), - body: Container( - margin: EdgeInsets.all(30), + return SafeArea( + child: Container( + color: theme.getColorFor(ThemeCode.backgroundSecondary), + margin: EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( + spacing: 32, children: [ Row( children: [ - Text("Alertas"), + Text( + "Alertas", + style: TextStyle( + fontSize: 24, + letterSpacing: 0, + fontWeight: FontWeight.w500 + ) + ), Spacer(), TextButton( onPressed: () => setState(() { edit = !edit; }), - child: Text("Editar"), + child: Text("Editar", style: TextStyle(fontSize: 16, letterSpacing: 0)), ), ], ), diff --git a/modules/profile/lib/src/presentation/profile_screen.dart b/modules/profile/lib/src/presentation/profile_screen.dart index 031607d5..6f86ac46 100644 --- a/modules/profile/lib/src/presentation/profile_screen.dart +++ b/modules/profile/lib/src/presentation/profile_screen.dart @@ -20,9 +20,16 @@ class ProfileScreen extends ConsumerWidget { {"type": "lock"}, ]; + final kids = [ + Kid(name: "Ana", balance: 15, savings: 5), + Kid(name: "Carlos", balance: 15, savings: 5) + ]; + final name = "Juan"; final total = 95.03; final available = 44.09; + final savings = 4.16; + final savingsPlan = 30.0; final content = [ Row( @@ -54,10 +61,10 @@ class ProfileScreen extends ConsumerWidget { LineGraph(), DepositBlock(max: 150 - total), Container( - padding: EdgeInsets.all(20), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), ), child: TextButton( onPressed: ()=>{}, @@ -98,7 +105,7 @@ class ProfileScreen extends ConsumerWidget { children: [ DecoratedBox( decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(30)), + borderRadius: const BorderRadius.only(bottomRight: Radius.circular(24), bottomLeft: Radius.circular(24)), color: Color(0xFF4B4B4B), ), child: SizedBox(width: double.infinity, height: 200), diff --git a/modules/profile/lib/src/settings_screen.dart b/modules/profile/lib/src/settings_screen.dart index 42f9477b..dfe1c93a 100644 --- a/modules/profile/lib/src/settings_screen.dart +++ b/modules/profile/lib/src/settings_screen.dart @@ -41,7 +41,7 @@ class SettingsScreen extends ConsumerWidget { ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -57,15 +57,15 @@ class SettingsScreen extends ConsumerWidget { ), Spacer(), TextButton(onPressed: () => {}, child: Text("Editar wallet")), - Icon(Icons.attach_money), + Icon(Icons.account_balance, size: 24), ], ), - Text(relation), + Align(alignment: Alignment.centerLeft, child: Text(relation)), ], ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -73,7 +73,6 @@ class SettingsScreen extends ConsumerWidget { child: Column( children: [ Row( - spacing: 10, children: [ Text( "Datos personales", @@ -83,47 +82,56 @@ class SettingsScreen extends ConsumerWidget { TextButton(onPressed: () => {}, child: Text("Editar")), ], ), - Text.rich( - TextSpan( - text: "Nombre: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: fullName, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], - ), + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Nombre: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: fullName, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), + ) ), - Text.rich( - TextSpan( - text: "Fecha de nacimiento: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: birthDate, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], - ), + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Fecha de nacimiento: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: birthDate, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), + ) ), - Text.rich( - TextSpan( - text: "Familiar: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: relation, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], - ), + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Familiar: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: relation, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), + ) ), ], ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -131,7 +139,6 @@ class SettingsScreen extends ConsumerWidget { child: Column( children: [ Row( - spacing: 10, children: [ Text( "Dirección", @@ -141,47 +148,56 @@ class SettingsScreen extends ConsumerWidget { TextButton(onPressed: () => {}, child: Text("Editar")), ], ), - Text.rich( - TextSpan( - text: "Dirección: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: address, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Dirección: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: address, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), ), ), - Text.rich( - TextSpan( - text: "País: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: country, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "País: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: country, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), ), ), - Text.rich( - TextSpan( - text: "Nacionalidad: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: nationality, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Nacionalidad: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: nationality, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), ), - ), + ) ], ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -189,7 +205,6 @@ class SettingsScreen extends ConsumerWidget { child: Column( children: [ Row( - spacing: 10, children: [ Text( "Usuario", @@ -199,42 +214,46 @@ class SettingsScreen extends ConsumerWidget { TextButton(onPressed: () => {}, child: Text("Editar")), ], ), - Text.rich( - TextSpan( - text: "Correo: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: email, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Correo: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: email, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), ), ), - Text.rich( - TextSpan( - text: "Teléfono: ", - style: TextStyle(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: phone, - style: TextStyle(fontWeight: FontWeight.normal), - ), - ], + Align( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + text: "Teléfono: ", + style: TextStyle(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: phone, + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + ), ), - ), + ) ], ), ), - Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), ), child: Row( - spacing: 10, children: [ Text( "Cambio de contraseña", @@ -246,7 +265,7 @@ class SettingsScreen extends ConsumerWidget { ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -254,7 +273,6 @@ class SettingsScreen extends ConsumerWidget { child: Column( children: [ Row( - spacing: 10, children: [ Text( "Método de pago", @@ -269,7 +287,26 @@ class SettingsScreen extends ConsumerWidget { ), ), Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + child: Column( + spacing: 24, + children: [ + Text( + "Retirar y reembolsar dinero del wallet", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + Text("Para transferirte el saldo a tu cuenta necesitamos tu IBAN y algunos datos básics. Así nos aseguramos de que la transferencia sea segura y rápida."), + CustomTextField(label: "Nombre y Apellidos", hint: "******"), + CustomTextField(label: "IBAN con número español", hint: "******") + ], + ), + ), + Container( + padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20)), color: theme.getColorFor(ThemeCode.backgroundTertiary), @@ -277,7 +314,6 @@ class SettingsScreen extends ConsumerWidget { child: Column( children: [ Row( - spacing: 10, children: [ Text( "Plan anual", @@ -287,15 +323,81 @@ class SettingsScreen extends ConsumerWidget { TextButton(onPressed: () => {}, child: Text("Cambiar Plan")), ], ), - Text("Sin permanencia"), - Text("Llamadas y datos ilimitados"), - Text("2 meses gratis"), + Align( + alignment: Alignment.centerLeft, + child: Row( + spacing: 8, + children: [ + Icon(Icons.check, size: 24), + Text( + "Sin permanencia", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ], + ) + ), + Align( + alignment: Alignment.centerLeft, + child: Row( + spacing: 8, + children: [ + Icon(Icons.check, size: 24), + Text( + "Llamadas y datos ilimitados", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ], + ) + ), + Align( + alignment: Alignment.centerLeft, + child: Row( + spacing: 8, + children: [ + Icon(Icons.check, size: 24), + Text( + "2 meses gratis", + style: TextStyle(fontSize: 16, letterSpacing: 0), + ), + ], + ) + ), ], ), ), - - TextButton(onPressed: () => {}, child: Text("Contáctanos")), - TextButton(onPressed: () => {}, child: Text("Preguntas frecuentes")), + Column( + spacing: 16, + children: [ + Align( + alignment: Alignment.topLeft, + child: TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon(Icons.contact_support_outlined, size: 24), + Text("Contáctanos") + ], + ) + ) + ), + Align( + alignment: Alignment.topLeft, + child: TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + onPressed: () => {}, + child: Row( + spacing: 4, + children: [ + Icon(Icons.contact_support_outlined, size: 24), + Text("Preguntas frecuentes") + ], + ) + ) + ), + ], + ) ]; return Scaffold( @@ -304,7 +406,7 @@ class SettingsScreen extends ConsumerWidget { children: [ DecoratedBox( decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(30)), + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), color: Color(0xFF4B4B4B), ), child: SizedBox(width: double.infinity, height: 200), @@ -336,13 +438,10 @@ class SettingsScreen extends ConsumerWidget { ), child: Column( children: [ - FilledButton( + PrimaryButton( onPressed: () => {}, - child: Container( - width: double.infinity, - padding: EdgeInsets.all(20), - child: Center(child: Text("Guardar cambios")), - ), + text: "Guardar cambios", + color: theme.getColorFor(ThemeCode.buttonPrimary) ), TextButton( onPressed: () => Navigator.pop(context), diff --git a/packages/design_system/lib/design_system.dart b/packages/design_system/lib/design_system.dart index 96b767ff..65eca86b 100644 --- a/packages/design_system/lib/design_system.dart +++ b/packages/design_system/lib/design_system.dart @@ -4,4 +4,8 @@ export 'src/steps/step_indicator.dart'; export 'src/texts/money_text.dart'; export 'src/progress_bars/progress_bar.dart'; export 'src/inputs/textfields.dart'; -export 'src/snackbars/snackbar.dart'; \ No newline at end of file +export 'src/snackbars/snackbar.dart'; +export 'src/buttons/primary_button.dart'; +export 'src/buttons/secondary_button.dart'; +export 'src/buttons/custom_text_button.dart'; +export 'src/dropdowns/dropdown.dart'; \ No newline at end of file diff --git a/packages/design_system/lib/src/buttons/custom_text_button.dart b/packages/design_system/lib/src/buttons/custom_text_button.dart new file mode 100644 index 00000000..7288fc73 --- /dev/null +++ b/packages/design_system/lib/src/buttons/custom_text_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +class CustomTextButton extends StatelessWidget{ + + final onPressed; + final String text; + final double size; + final FontWeight weight; + final Color? color; + + @override + const CustomTextButton({ + super.key, + required this.onPressed, + required this.text, + this.size = 14, + this.weight = FontWeight.normal, + this.color + }); + + @override + Widget build(BuildContext context) { + + return TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)), + onPressed: onPressed, + child: Text( + text, + style: TextStyle( + fontSize: size, + fontWeight: weight, + letterSpacing: 0, + color: color?? Color(0xFF4B4B4B), + decoration: TextDecoration.underline + ), + ) + ); + } +} \ No newline at end of file diff --git a/packages/design_system/lib/src/buttons/primary_button.dart b/packages/design_system/lib/src/buttons/primary_button.dart new file mode 100644 index 00000000..b3d48880 --- /dev/null +++ b/packages/design_system/lib/src/buttons/primary_button.dart @@ -0,0 +1,54 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class PrimaryButton extends StatelessWidget{ + final onPressed; + final String text; + final Color color; + final double height; + final double? width; + final double size; + final double radius; + final double padding; + + PrimaryButton({ + required this.onPressed, + required this.text, + required this.color, + this.height = 60, + this.width, + this.size = 18, + this.radius = 18, + this.padding = 0, + }); + + @override + Widget build(BuildContext context) { + + return FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll(color), + padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: padding)), + shape: WidgetStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), + )), + ), + onPressed: onPressed, + 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) + ) + )) + ) + ); + } +} \ No newline at end of file diff --git a/packages/design_system/lib/src/buttons/secondary_button.dart b/packages/design_system/lib/src/buttons/secondary_button.dart new file mode 100644 index 00000000..ce360713 --- /dev/null +++ b/packages/design_system/lib/src/buttons/secondary_button.dart @@ -0,0 +1,67 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class SecondaryButton extends StatelessWidget { + + final onPressed; + final String? text; + final IconData? icon; + final String? label; + final Color? color; + final double radius; + final double padding; + final double height; + final double? width; + final double? size; + + @override + SecondaryButton({ + required this.onPressed, + this.text, + this.icon, + this.label, + this.color, + this.radius = 18, + this.padding = 0, + this.height = 60, + this.width, + this.size, + }); + + @override + Widget build(BuildContext context) { + + return OutlinedButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: padding)), + shape: WidgetStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), + side: BorderSide(color: Color(0xFF4B4B4B)) + )), + ), + onPressed: onPressed, + child: SizedBox( + width: width, + height: height, + child: Center(child: text!=null ? Text( + text!, + textAlign: TextAlign.center, + semanticsLabel: label, + style: TextStyle( + fontSize: size ?? 18, + fontWeight: FontWeight.w500, + letterSpacing: 0, + color: Color(0xFF4B4B4B) + ) + ) : Icon( + icon, + semanticLabel: label, + size: size ?? 24, + color: color ?? Color(0xFF4B4B4B) + )) + ) + ); + } + +} \ No newline at end of file diff --git a/packages/design_system/lib/src/dropdowns/dropdown.dart b/packages/design_system/lib/src/dropdowns/dropdown.dart new file mode 100644 index 00000000..53324997 --- /dev/null +++ b/packages/design_system/lib/src/dropdowns/dropdown.dart @@ -0,0 +1,67 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class CustomDropdown extends StatelessWidget{ + final List items; + final values; + final onChanged; + final value; + final String? hint; + final String? label; + final double radius; + final double height; + final double width; + final Color? color; + + const CustomDropdown({ + super.key, + required this.items, + this.values, + required this.onChanged, + this.value, + this.hint, + this.label, + this.radius = 12, + this.width = double.infinity, + this.height = 70, + this.color + }); + + @override + Widget build(BuildContext context) { + + return Column( + spacing: 8, + children: [ + if (label != null) Align( + alignment: Alignment.bottomLeft, + child: Text( + label!, + style: TextStyle(fontSize: 14, letterSpacing: 0), + ), + ), + SizedBox( + width: width, + height: height, + child: Center(child: DropdownButtonFormField( + dropdownColor: Colors.white, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), + borderSide: BorderSide(color: color??Color(0xFF4B4B4B)) + ) + ), + //underline: Container(), + initialValue: value, + onChanged: onChanged, + hint: Text(hint??""), + items: List.generate(items.length, (int index){ + return DropdownMenuItem(value: (values!=null)?values[index]:index, child: items[index]); + }) + )), + ) + ], + ) ; + } +} \ No newline at end of file diff --git a/packages/design_system/lib/src/inputs/textfields.dart b/packages/design_system/lib/src/inputs/textfields.dart index c4d5e011..40e36508 100644 --- a/packages/design_system/lib/src/inputs/textfields.dart +++ b/packages/design_system/lib/src/inputs/textfields.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -class CustomTextField extends ConsumerStatefulWidget{ +class CustomTextField extends StatefulWidget{ bool? showPassword; final bool numeric; final String hint; @@ -24,49 +24,60 @@ class CustomTextField extends ConsumerStatefulWidget{ }); @override - ConsumerState createState() => CustomTextFieldState(); + State createState() => CustomTextFieldState(); } -class CustomTextFieldState extends ConsumerState{ +class CustomTextFieldState extends State{ @override Widget build(BuildContext context) { - final theme = ref.watch(themePortProvider); - return TextFormField( - keyboardType: widget.numeric? TextInputType.number : TextInputType.text, - obscureText: !(widget.showPassword ?? true), - enableSuggestions: widget.showPassword ?? true, - autocorrect: !(widget.showPassword ?? false), - style: TextStyle(color: theme.getColorFor(ThemeCode.buttonSecondary)), - inputFormatters: widget.numeric? [ - FilteringTextInputFormatter.digitsOnly - ] : [], - decoration: InputDecoration( - counterText: "", - hintText: widget.hint, - labelText: widget.label, - floatingLabelBehavior: FloatingLabelBehavior.always, - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: theme.getColorFor(ThemeCode.textPrimary)), - gapPadding: 16 + return Column( + spacing: 8, + children: [ + ?widget.label == '' ? null : Align( + alignment: Alignment.bottomLeft, + child: Text( + widget.label, + style: TextStyle(fontSize: 14, letterSpacing: 0), + ) ), - suffixIcon: widget.showPassword!=null ? IconButton( - icon: Icon(widget.showPassword! - ? Icons.visibility_off - : Icons.visibility), - onPressed: () { - setState(() { - widget.showPassword = !widget.showPassword!; - }); - }, - ) : null, - ), - minLines: widget.lines ?? 1, - maxLines: widget.lines ?? 1, - maxLength: widget.length, - onChanged: widget.onChanged ?? (_)=>{}, + TextFormField( + keyboardType: widget.numeric? TextInputType.number : TextInputType.text, + obscureText: !(widget.showPassword ?? true), + enableSuggestions: widget.showPassword ?? true, + autocorrect: !(widget.showPassword ?? false), + style: TextStyle(color: Color(0xFF4B4B4B)), + inputFormatters: widget.numeric? [ + FilteringTextInputFormatter.digitsOnly + ] : [], + decoration: InputDecoration( + counterText: "", + hintText: widget.hint, + //labelText: widget.label, + //floatingLabelBehavior: FloatingLabelBehavior.always, + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: Color(0xFF4B4B4B)), + gapPadding: 16 + ), + suffixIcon: widget.showPassword!=null ? IconButton( + icon: Icon(widget.showPassword! + ? Icons.visibility_off + : Icons.visibility), + onPressed: () { + setState(() { + widget.showPassword = !widget.showPassword!; + }); + }, + ) : null, + ), + minLines: widget.lines ?? 1, + maxLines: widget.lines ?? 1, + maxLength: widget.length, + onChanged: widget.onChanged ?? (_)=>{}, + ) + ], ); } } \ No newline at end of file diff --git a/packages/design_system/lib/src/progress_bars/progress_bar.dart b/packages/design_system/lib/src/progress_bars/progress_bar.dart index 93251da8..e4434ec3 100644 --- a/packages/design_system/lib/src/progress_bars/progress_bar.dart +++ b/packages/design_system/lib/src/progress_bars/progress_bar.dart @@ -12,15 +12,15 @@ class ProgressBar extends ConsumerWidget{ final Color foregroundColor; final Color textColor; - const ProgressBar( - this.max, - this.value, - this.height, - this.textSize, - this.textSecondarySize, - this.backgroundColor, - this.foregroundColor, - this.textColor, + const ProgressBar({ + required this.max, + required this.value, + required this.height, + required this.textSize, + required this.textSecondarySize, + required this.backgroundColor, + required this.foregroundColor, + required this.textColor,} ); @override diff --git a/packages/design_system/lib/src/snackbars/snackbar.dart b/packages/design_system/lib/src/snackbars/snackbar.dart index e393bae2..e4a2c9c1 100644 --- a/packages/design_system/lib/src/snackbars/snackbar.dart +++ b/packages/design_system/lib/src/snackbars/snackbar.dart @@ -9,25 +9,24 @@ enum MessageType { success } -class CustomSnackBar extends ConsumerWidget{ - final MessageType type; +class CustomSnackBar extends StatelessWidget{ + final MessageType? type; final String message; - CustomSnackBar({ + const CustomSnackBar({ super.key, - this.type = MessageType.info, + this.type, required this.message, }); @override - SnackBar build(BuildContext context, WidgetRef ref) { - final theme = ref.watch(themePortProvider); + SnackBar build(BuildContext context) { late final Color foregroundColor; late final Color backgroundColor; late final IconData icon; - switch (type){ + switch (type??MessageType.info){ case MessageType.info: backgroundColor = Color(0xFFE3EFFD); foregroundColor = Color(0xFF1F4ECF); @@ -57,7 +56,7 @@ class CustomSnackBar extends ConsumerWidget{ spacing: 8, children: [ Icon(icon, color: foregroundColor), - Expanded(child: Text(message, style: TextStyle(color: theme.getColorFor(ThemeCode.textPrimary), fontSize: 14))) + Expanded(child: Text(message, style: TextStyle(color: Color(0xFF4B4B4B), fontSize: 14))) ], ), ); diff --git a/packages/design_system/lib/src/texts/money_text.dart b/packages/design_system/lib/src/texts/money_text.dart index d3c7cb66..976882c2 100644 --- a/packages/design_system/lib/src/texts/money_text.dart +++ b/packages/design_system/lib/src/texts/money_text.dart @@ -18,11 +18,12 @@ class MoneyText extends StatelessWidget { @override Widget build(BuildContext context) { - final units = text.split(".")[0]; - final cents = ",${text.split(".")[1]}"; + final split = text.contains(".") ? text.split(".") : text.split("€"); + final mainText = split[0]; + var secondaryText = text.contains(".") ? ",${split[1]}" : "€${split[1]}"; return Text.rich(TextSpan( - text: units, + text: mainText, style: TextStyle( fontWeight: FontWeight.bold, fontSize: size, @@ -31,7 +32,7 @@ class MoneyText extends StatelessWidget { ), children: [ TextSpan( - text: cents, + text: secondaryText, style: TextStyle( fontWeight: FontWeight.normal, fontSize: secondarySize ?? size, diff --git a/packages/design_system/pubspec.yaml b/packages/design_system/pubspec.yaml index 6167ab2d..c136e400 100644 --- a/packages/design_system/pubspec.yaml +++ b/packages/design_system/pubspec.yaml @@ -20,12 +20,14 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + golden_toolkit: ^0.15.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: diff --git a/packages/design_system/test/widget_test.dart b/packages/design_system/test/widget_test.dart new file mode 100644 index 00000000..8b29fdd8 --- /dev/null +++ b/packages/design_system/test/widget_test.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:design_system/design_system.dart'; + +void main() { + themePackages(); + + testGoldens('Step Indicator', (tester) async { + final widget = (int step)=>StepIndicator(max: 3, current: step, color: Colors.blueAccent); + final builder = GoldenBuilder.column() + ..addScenario('step -1', widget(-1)) + ..addScenario('step 0', widget(0)) + ..addScenario('step 1', widget(1)) + ..addScenario('step 2', widget(2)) + ..addScenario('step 3', widget(3)) + ..addScenario('step 4', widget(4)); + await tester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(tester, 'step_indicator'); + }); + + testGoldens('Progress Bar - Small', (tester) async { + final widget = (double value, double max) => ProgressBar(max: max, value: value, height: 24, textSize: 16, textSecondarySize: 12, backgroundColor: Colors.blueGrey, foregroundColor: Colors.blueAccent, textColor: Colors.black87); + + final builder = GoldenBuilder.column() + ..addScenario('full', widget(100, 100)) + ..addScenario('empty', widget(0, 100)) + ..addScenario('half', widget(75, 150)) + ..addScenario('overflowing', widget(200, 150)) + //..addScenario('negative', widget(-20, 83)) + ..addScenario('tiny value', widget(2.15, 150)); + await tester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(tester, 'progress_bar_small'); + }); + + testGoldens('Progress Bar - Large', (tester) async { + final widget = (double value, double max) => ProgressBar(max: max, value: value, height: 83, textSize: 40, textSecondarySize: 24, backgroundColor: Colors.blueGrey, foregroundColor: Colors.blueAccent, textColor: Colors.black87); + + final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1) + ..addScenario('full', widget(100, 100)) + ..addScenario('empty', widget(0, 100)) + ..addScenario('half', widget(75, 150)) + ..addScenario('overflowing', widget(200, 150)) + ..addScenario('tiny value', widget(2.15, 150)); + await tester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(tester, 'progress_bar_large'); + }); + + testGoldens('Text Field', (tester) async { + + final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1) + ..addScenario('basic', SizedBox(height: 70, width: 250, child: CustomTextField())) + ..addScenario('hint', SizedBox(height: 70, width: 250, child: CustomTextField(hint: "type something"))) + ..addScenario('label', SizedBox(height: 100, width: 250, child: CustomTextField(hint: "type something", label: "text input"))) + ..addScenario('numeric', SizedBox(height: 70, width: 250, child: CustomTextField(numeric: true))) + ..addScenario('password', SizedBox(height: 70, width: 250, child: CustomTextField(showPassword: false))) + ..addScenario('multiline', SizedBox(height: 200, width: 250, child: CustomTextField(lines: 4))); + + await tester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(tester, 'textfield'); + }); + + testGoldens('Primary Button', (tester) async { + final widget = ({onPressed, text}) => SizedBox( + height: 70, width: 250, + child:PrimaryButton(onPressed: onPressed, text: text, color: Colors.blueAccent) + ); + + final builder = GoldenBuilder.grid(columns: 2, widthToHeightRatio: 1) + ..addScenario('empty', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, text: "", color: Colors.blueAccent))) + ..addScenario('basic', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent))) + ..addScenario('round', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, radius: 100, text: "press me", color: Colors.blueAccent))) + ..addScenario('small', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, width: 100, text: "press me", color: Colors.blueAccent))); + + await tester.pumpWidgetBuilder(builder.build()); + + await screenMatchesGolden(tester, 'primary_button'); + }); + + testGoldens('Text Button', (tester) async { + final tapTarget = Key("tap-target"); + + final builder = GoldenBuilder.column() + ..addScenario('empty', CustomTextButton(onPressed: ()=>{}, text: "")) + ..addScenario('basic', CustomTextButton(onPressed: ()=>{}, text: "press me")) + ..addScenario('tapped', CustomTextButton(onPressed: ()=>{}, text: "press me", key: tapTarget)) + ..addScenario('large text', CustomTextButton(onPressed: ()=>{}, text: "press me", size: 40, weight: FontWeight.w500)) + ..addScenario('small text', CustomTextButton(onPressed: ()=>{}, text: "press me", size: 10)) + ..addScenario('colored', CustomTextButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent)); + await tester.pumpWidgetBuilder(builder.build()); + await tester.tap(find.byKey(tapTarget)); + await tester.pump(); + await screenMatchesGolden(tester, 'text_button'); + }); + + testGoldens('Money Text', (tester) async { + final builder = GoldenBuilder.column() + ..addScenario('basic', MoneyText(text: "29.13€", size: 20, color: Colors.blueAccent)) + ..addScenario('without cents', MoneyText(text: "50€", size: 20, color: Colors.blueAccent)) + ..addScenario('different sizes', MoneyText(text: "29.13€", size: 30, secondarySize: 15, color: Colors.blueAccent)) + ..addScenario('different sizes without cents', MoneyText(text: "50€", size: 30, secondarySize: 15, color: Colors.blueAccent)); + + await tester.pumpWidgetBuilder(builder.build()); + + await screenMatchesGolden(tester, 'money_text'); + }); + + testGoldens('Secondary Button', (tester) async { + final widget = ({onPressed, text}) => SizedBox( + height: 70, width: 250, + child:SecondaryButton(onPressed: onPressed, text: text) + ); + + final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1) + ..addScenario('empty', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: ""))) + ..addScenario('text', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: "press me"))) + ..addScenario('icon', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, icon: Icons.account_circle_outlined))) + ..addScenario('colored', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent,))) + ..addScenario('small', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, width: 100, text: "press me"))) + ..addScenario('round', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, radius: 100, text: "press me", color: Colors.blueAccent,))); + + await tester.pumpWidgetBuilder(builder.build()); + + await screenMatchesGolden(tester, 'secondary_button'); + }); + + final snackbarTest = ({message, type, testName}) { + testGoldens('Snackbar $testName', (tester) async { + const Key tapTarget = Key('tap-target'); + + final widget = () => + SizedBox( + width: 800, + height: 600, + child: MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + CustomSnackBar( + message: message, + type: type, + ).build(context) + ); + }, + behavior: HitTestBehavior.opaque, + key: tapTarget, + ); + } + ), + ), + ), + ); + + final builder = DeviceBuilder() + ..overrideDevicesForAllScenarios(devices: [ + Device(size: Size(750, 550), name: 'base') + ]) + ..addScenario(name: testName, widget: widget()); + + await tester.pumpWidgetBuilder(builder.build()); + await tester.tap(find.byKey(tapTarget)); + + await screenMatchesGolden(tester, 'snackbar/$testName'); + }); + }; + + final shortText = "Mensaje de prueba"; + final longText = "Mensaje de prueba largo para comprobar los casos en los que el texto ocupa varias líneas"; + + snackbarTest(message: "", type: null, testName: "default_empty"); + snackbarTest(message: shortText, type: null, testName: "default"); + snackbarTest(message: longText, type: null, testName: "default_long_text"); + snackbarTest(message: "", type: MessageType.info, testName: "info_empty"); + snackbarTest(message: shortText, type: MessageType.info, testName: "info"); + snackbarTest(message: longText, type: MessageType.info, testName: "info_long_text"); + snackbarTest(message: "", type: MessageType.success, testName: "success_empty"); + snackbarTest(message: shortText, type: MessageType.success, testName: "success"); + snackbarTest(message: longText, type: MessageType.success, testName: "success_long_text"); + snackbarTest(message: "", type: MessageType.warning, testName: "warning_empty"); + snackbarTest(message: shortText, type: MessageType.warning, testName: "warning"); + snackbarTest(message: longText, type: MessageType.warning, testName: "warning_long_text"); + snackbarTest(message: "", type: MessageType.error, testName: "error_empty"); + snackbarTest(message: shortText, type: MessageType.error, testName: "error"); + snackbarTest(message: longText, type: MessageType.error, testName: "error_long_text"); + + testGoldens('Dropdown', (tester) async { + final List emptyList = []; + final List shortList = [Text("A"), Text("B"), Text("C")]; + + final builder = GoldenBuilder.column() + ..addScenario('empty', CustomDropdown(items: emptyList, onChanged: (_)=>{}, width: 200)) + ..addScenario('initial value', CustomDropdown(items: shortList, value: 1, onChanged: (_)=>{}, width: 300)) + ..addScenario('hint', CustomDropdown(items: shortList, hint: "choose an option", onChanged: (_)=>{})) + ..addScenario('label', CustomDropdown(items: shortList, label: "select", onChanged: (_)=>{}, width: 200)); + await tester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(tester, 'dropdown'); + }); +} \ No newline at end of file diff --git a/packages/sf_shared/lib/sf_shared.dart b/packages/sf_shared/lib/sf_shared.dart index b9db2b43..c7242b27 100644 --- a/packages/sf_shared/lib/sf_shared.dart +++ b/packages/sf_shared/lib/sf_shared.dart @@ -1,7 +1,9 @@ export 'src/models/kid.dart'; +export 'src/models/task.dart'; +export 'src/models/savings_goal.dart'; export 'src/widgets/line_graph.dart'; export 'src/widgets/deposit_block.dart'; export 'src/screens/connection_error_screen.dart'; export 'src/screens/server_error_screen.dart'; export 'src/screens/no_plan_error_screen.dart'; -export 'src/widgets/wallet_balance_block.dart'; +export 'src/widgets/wallet_balance_block.dart'; \ No newline at end of file diff --git a/packages/sf_shared/lib/src/models/savings_goal.dart b/packages/sf_shared/lib/src/models/savings_goal.dart new file mode 100644 index 00000000..b2baae86 --- /dev/null +++ b/packages/sf_shared/lib/src/models/savings_goal.dart @@ -0,0 +1,12 @@ +class SavingsGoal{ + + final String name; + final double goal; + final double saved; + + const SavingsGoal({ + required this.name, + required this.goal, + required this.saved + }); +} \ No newline at end of file diff --git a/packages/sf_shared/lib/src/models/task.dart b/packages/sf_shared/lib/src/models/task.dart new file mode 100644 index 00000000..663b3e7b --- /dev/null +++ b/packages/sf_shared/lib/src/models/task.dart @@ -0,0 +1,21 @@ +class Task{ + + final double rewardAmount; + final List subtasks; + + const Task({ + required this.rewardAmount, + this.subtasks = const [] + }); +} + +class Subtask{ + + final String name; + final bool completed; + + const Subtask({ + required this.name, + required this.completed + }); +} \ No newline at end of file diff --git a/packages/sf_shared/lib/src/widgets/deposit_block.dart b/packages/sf_shared/lib/src/widgets/deposit_block.dart index c534092f..710145e1 100644 --- a/packages/sf_shared/lib/src/widgets/deposit_block.dart +++ b/packages/sf_shared/lib/src/widgets/deposit_block.dart @@ -32,7 +32,7 @@ class DepositBlock extends ConsumerWidget { spacing: 16, children: [ Row( - spacing: 10, + spacing: 16, children: [ Expanded( child: CustomTextField( @@ -41,20 +41,15 @@ class DepositBlock extends ConsumerWidget { numeric: true, ), ), - FilledButton( - onPressed: () => {}, - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonPrimary), - ), - shape: WidgetStatePropertyAll(RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(18)) - )) + Align( + alignment: Alignment.bottomRight, + child: PrimaryButton( + onPressed: () => {}, + text: "Ingresar", + color: theme.getColorFor(ThemeCode.buttonPrimary), + padding: 24, ), - child: SizedBox( - height: 60, - child: Center(child: Text("Ingresar", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)))), - ), + ) ], ), Align( diff --git a/packages/sf_shared/lib/src/widgets/line_graph.dart b/packages/sf_shared/lib/src/widgets/line_graph.dart index 3e764b41..e4d6ce8f 100644 --- a/packages/sf_shared/lib/src/widgets/line_graph.dart +++ b/packages/sf_shared/lib/src/widgets/line_graph.dart @@ -1,10 +1,13 @@ +import 'dart:math'; + import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class LineGraph extends ConsumerStatefulWidget { - final lines = [[0,1,0,1,0,1,0],[1,0,1,0,1,0,1]]; + final lines = [[0,1,0,3,0,1,0],[1,0,1,0,4,0,1]]; + late final maxValue = lines.map((x)=>x.reduce(max)).reduce(max); LineGraph({super.key}); @@ -42,25 +45,22 @@ class LineGraphState extends ConsumerState { Text("Gastos", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)), Spacer(), Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8)), color: theme.getColorFor(ThemeCode.backgroundSecondary), ), - child: DropdownButton( - underline: Container(), + child: + CustomDropdown( value: timeSpan, - onChanged: (String? value) { - setState(() { - timeSpan = value; - }); - }, - dropdownColor: theme.getColorFor(ThemeCode.backgroundPrimary), items: [ - DropdownMenuItem(value: "day", child: Text("Hoy", style: TextStyle(fontSize: 14, letterSpacing: 0))), - DropdownMenuItem(value: "week", child: Text("Esta semana", style: TextStyle(fontSize: 14, letterSpacing: 0))), - DropdownMenuItem(value: "month", child: Text("Este mes", style: TextStyle(fontSize: 14, letterSpacing: 0))), - ] + Text("Hoy", style: TextStyle(fontSize: 14, letterSpacing: 0)), + Text("Esta semana", style: TextStyle(fontSize: 14, letterSpacing: 0)), + Text("Este mes", style: TextStyle(fontSize: 14, letterSpacing: 0)) + ], + values: ["day", "week", "month"], + onChanged: (value)=>{}, + color: Colors.transparent, + width: 151, ), ) ]), @@ -136,33 +136,26 @@ class LineGraphState extends ConsumerState { top: const BorderSide(color: Colors.transparent), ), ), - lineBarsData: [ + lineBarsData: List.generate(widget.lines.length, (int i)=> LineChartBarData( - isCurved: true, - color: Colors.pink, - barWidth: 5, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData(show: false), - spots: List.generate(days.length, (int index){ - return FlSpot(index.toDouble(), (index+1)%2); - }) + isCurved: true, + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: theme.getCardColorFor(i) + ), + barWidth: 5, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData(show: false), + spots: List.generate(widget.lines[i].length, (int j){ + return FlSpot(j.toDouble(), widget.lines[i][j].toDouble()); + }) ), - LineChartBarData( - isCurved: true, - color: Colors.cyan, - barWidth: 5, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData(show: false), - spots: List.generate(days.length, (int index){ - return FlSpot(index.toDouble(), index%2); - }) - ), - ], + ), minX: 0, maxX: days.length-1, - maxY: 1, + maxY: widget.maxValue.toDouble(), minY: 0, )) ) diff --git a/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart b/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart index 823ca7c7..e5a5799e 100644 --- a/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart +++ b/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart @@ -49,8 +49,26 @@ class WalletBalanceBlock extends ConsumerWidget { Spacer(), Text("$savingsPlan€") ]), - ProgressBar(savingsPlan, savings, 24, 16, 12, theme.getColorFor(ThemeCode.backgroundSecondary), theme.getColorFor(ThemeCode.backgroundTertiary), theme.getColorFor(ThemeCode.textPrimary)), - ProgressBar(max, value, 83, 40, 24, theme.getColorFor(ThemeCode.backgroundTertiary), theme.getColorFor(ThemeCode.buttonPrimary), theme.getColorFor(ThemeCode.textSecondary)), + ProgressBar( + max: savingsPlan, + value: savings, + height: 24, + textSize: 16, + textSecondarySize: 12, + backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), + foregroundColor: theme.getColorFor(ThemeCode.backgroundTertiary), + textColor: theme.getColorFor(ThemeCode.textPrimary) + ), + ProgressBar( + max: max, + value: value, + height: 83, + textSize: 40, + textSecondarySize: 24, + backgroundColor: theme.getColorFor(ThemeCode.backgroundTertiary), + foregroundColor: theme.getColorFor(ThemeCode.buttonPrimary), + textColor: theme.getColorFor(ThemeCode.textSecondary) + ), Center(child: Text("Disponible")), ], ),