From 8201bff0a79133b45914d72a6c85cf457367aeb8 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Fri, 21 Nov 2025 15:28:46 +0100 Subject: [PATCH 1/5] - created snackbar, step indicator, money text, text field and progress bar components. - created wallet balance block, wallet item and kid line chart widgets. - restructured onboarding, signup and device signup screens into layouts and main screens. - updated signup and kid wallet screens to 17/11 design. --- apps/mobile_app/lib/main.dart | 4 +- .../mobile_app/lib/navigation/app_router.dart | 7 +- apps/mobile_app/pubspec.yaml | 4 +- modules/auth/lib/auth.dart | 1 + .../src/device_sign_up/add_kid_screen.dart | 14 +- .../src/device_sign_up/contact_screen.dart | 31 +- .../device_sign_up/device_signup_builder.dart | 18 + .../device_sign_up/device_signup_screen.dart | 101 ++++ .../account_created_kid_screen.dart | 87 ---- .../link_watch/create_profile_screen.dart | 338 ++----------- .../link_watch_previous_screen.dart | 58 +++ .../link_watch/link_watch_screen.dart | 60 +++ .../login/presentation/link_phone_screen.dart | 32 +- .../src/login/presentation/login_screen.dart | 35 +- .../login/presentation/phone_code_screen.dart | 3 - .../presentation/welcome_screen.dart | 140 ++++-- .../presentation/email_sent_screen.dart | 3 +- .../presentation/new_password_screen.dart | 53 +- .../presentation/restore_password_screen.dart | 9 +- .../src/sign_up/account_created_screen.dart | 30 +- .../src/sign_up/signup_address_screen.dart | 58 ++- .../src/sign_up/signup_personal_screen.dart | 54 +- .../auth/lib/src/sign_up/signup_screen.dart | 173 ++----- .../lib/src/sign_up/signup_user_screen.dart | 64 +-- .../src/widgets/layouts/form_step_layout.dart | 91 ++++ .../lib/src/presentation/deposit_screen.dart | 32 +- .../lib/src/presentation/home_screen.dart | 214 +------- .../src/presentation/kid_wallet_screen.dart | 463 ++++++++++-------- .../lib/src/presentation/limits_screen.dart | 4 +- .../lib/src/presentation/wage_screen.dart | 29 +- .../lib/src/presentation/wallet_item.dart | 248 ++++++++++ .../wallet_management_layout.dart | 6 +- modules/home/pubspec.yaml | 2 + .../lib/src/core/activity_list.dart | 73 ++- .../lib/src/presentation/activity_screen.dart | 2 +- .../lib/src/presentation/alert_screen.dart | 6 +- .../profile/lib/src/core/kid_line_chart.dart | 178 +++++++ .../lib/src/presentation/profile_screen.dart | 71 ++- modules/profile/lib/src/profile_builder.dart | 1 - modules/profile/pubspec.yaml | 2 + packages/design_system/lib/design_system.dart | 5 + .../lib/src/inputs/textfields.dart | 72 +++ .../lib/src/progress_bars/progress_bar.dart | 56 +++ .../lib/src/snackbars/snackbar.dart | 65 +++ .../lib/src/steps/step_indicator.dart | 29 ++ .../lib/src/texts}/money_text.dart | 18 +- .../lib/src/theme/theme_port.dart | 5 + .../lib/src/theme/theme_sf_adapter.dart | 7 + packages/design_system/pubspec.yaml | 2 + packages/fonts/pubspec.yaml | 20 + packages/sf_shared/lib/sf_shared.dart | 1 + packages/sf_shared/lib/src/models/kid.dart | 3 +- .../lib/src/widgets/deposit_block.dart | 71 +-- .../sf_shared/lib/src/widgets/line_graph.dart | 268 +++++----- .../lib/src/widgets/wallet_balance_block.dart | 59 +++ pubspec.yaml | 2 +- 56 files changed, 1991 insertions(+), 1491 deletions(-) create mode 100644 modules/auth/lib/src/device_sign_up/device_signup_builder.dart create mode 100644 modules/auth/lib/src/device_sign_up/device_signup_screen.dart delete mode 100644 modules/auth/lib/src/device_sign_up/link_watch/account_created_kid_screen.dart create mode 100644 modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart create mode 100644 modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart create mode 100644 modules/auth/lib/src/widgets/layouts/form_step_layout.dart create mode 100644 modules/home/lib/src/presentation/wallet_item.dart create mode 100644 modules/profile/lib/src/core/kid_line_chart.dart create mode 100644 packages/design_system/lib/src/inputs/textfields.dart create mode 100644 packages/design_system/lib/src/progress_bars/progress_bar.dart create mode 100644 packages/design_system/lib/src/snackbars/snackbar.dart create mode 100644 packages/design_system/lib/src/steps/step_indicator.dart rename {modules/home/lib/src/presentation => packages/design_system/lib/src/texts}/money_text.dart (66%) create mode 100644 packages/fonts/pubspec.yaml create mode 100644 packages/sf_shared/lib/src/widgets/wallet_balance_block.dart diff --git a/apps/mobile_app/lib/main.dart b/apps/mobile_app/lib/main.dart index 37384b82..8fc0aaa8 100644 --- a/apps/mobile_app/lib/main.dart +++ b/apps/mobile_app/lib/main.dart @@ -21,10 +21,12 @@ class PlatformApp extends ConsumerWidget { return MaterialApp.router( title: 'SaveFamily', theme: ThemeData( + fontFamily: 'Stolzl', + package: 'fonts', colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF329E95)), ), routerConfig: appRouter, debugShowCheckedModeBanner: false, ); } -} +} \ No newline at end of file diff --git a/apps/mobile_app/lib/navigation/app_router.dart b/apps/mobile_app/lib/navigation/app_router.dart index 020f8866..6d0f5127 100644 --- a/apps/mobile_app/lib/navigation/app_router.dart +++ b/apps/mobile_app/lib/navigation/app_router.dart @@ -16,7 +16,7 @@ late final GoRouter appRouter; void configureAppRouter() { appRouter = GoRouter( navigatorKey: rootNavigatorKey, - initialLocation: '/login', + initialLocation: '/onboarding', // redirect: (context, state) {}, routes: [ GoRoute( @@ -44,6 +44,11 @@ void configureAppRouter() { name: 'recover_password', pageBuilder: RecoverPasswordBuilder().buildPage, ), + GoRoute( + path: '/device_signup', + name: 'device_signup', + pageBuilder: DeviceSignupBuilder().buildPage, + ), StatefulShellRoute.indexedStack( builder: (context, state, navShell) { return DashboardBuilder().build(context, navShell); diff --git a/apps/mobile_app/pubspec.yaml b/apps/mobile_app/pubspec.yaml index 3568458c..74320dc5 100644 --- a/apps/mobile_app/pubspec.yaml +++ b/apps/mobile_app/pubspec.yaml @@ -50,7 +50,9 @@ dependencies: navigation: path: ../../packages/navigation design_system: - path: ../../packages/design_system + path: ../../packages/design_system + fonts: + path: ../../packages/fonts #dependencies go here cupertino_icons: ^1.0.8 diff --git a/modules/auth/lib/auth.dart b/modules/auth/lib/auth.dart index 03108e74..f7e85138 100644 --- a/modules/auth/lib/auth.dart +++ b/modules/auth/lib/auth.dart @@ -4,3 +4,4 @@ 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 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 37531a5c..ea7d13f9 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,8 +1,9 @@ -import 'package:auth/src/device_sign_up/link_watch/create_profile_screen.dart'; import 'package:flutter/material.dart'; class AddKidScreen extends StatelessWidget { - const AddKidScreen({super.key}); + final nextStep; + + const AddKidScreen({super.key, required this.nextStep}); @override Widget build(BuildContext context) { @@ -13,7 +14,7 @@ class AddKidScreen extends StatelessWidget { spacing: 15, children: [ Spacer(flex: 6), - Text("Añade a tu peque"), + 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", ), @@ -32,7 +33,7 @@ class AddKidScreen extends StatelessWidget { ], ), ), - Text("¡Y todo listo para que tenga su dinero!"), + 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( "Si aún no lo tienes, puedes conseguirlo a través de nuestra web", @@ -41,10 +42,7 @@ class AddKidScreen extends StatelessWidget { Container( width: double.infinity, child: FilledButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => CreateProfileScreen()), - ), + onPressed: nextStep, child: Text("¡Empezar!"), ), ), 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 e7f7852c..6a7befe3 100644 --- a/modules/auth/lib/src/device_sign_up/contact_screen.dart +++ b/modules/auth/lib/src/device_sign_up/contact_screen.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; class ContactScreen extends StatelessWidget { @@ -36,32 +37,22 @@ class ContactScreen extends StatelessWidget { ], ), Expanded( - child: TextField( - decoration: InputDecoration( - labelText: "Nombre", - hintText: "Nombre y apellidos", - border: OutlineInputBorder(), - ), + child: CustomTextField( + label: "Nombre", + hint: "Nombre y apellidos", ), ), Expanded( - child: TextField( - decoration: InputDecoration( - labelText: "Correo electrónico", - hintText: "Correo electrónico", - border: OutlineInputBorder(), - ), + child: CustomTextField( + label: "Correo electrónico", + hint: "Correo electrónico", ), ), Expanded( - child: TextField( - minLines: 3, - maxLines: 3, - decoration: InputDecoration( - labelText: "Asunto del mensaje", - hintText: "Escribe tu mensaje", - border: OutlineInputBorder(), - ), + child: CustomTextField( + lines: 3, + label: "Asunto del mensaje", + hint: "Escribe tu mensaje", ), ), Expanded( diff --git a/modules/auth/lib/src/device_sign_up/device_signup_builder.dart b/modules/auth/lib/src/device_sign_up/device_signup_builder.dart new file mode 100644 index 00000000..f9ed93c6 --- /dev/null +++ b/modules/auth/lib/src/device_sign_up/device_signup_builder.dart @@ -0,0 +1,18 @@ +import 'package:auth/src/device_sign_up/device_signup_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:go_router/go_router.dart'; +import 'package:navigation/navigation.dart'; + +class DeviceSignupBuilder { + const DeviceSignupBuilder(); + + Page buildPage(BuildContext context, GoRouterState state) { + final NavigationContract navigationContract = GetIt.I(); + + return MaterialPage( + key: state.pageKey, + child: DeviceSignupScreen(navigationContract: navigationContract), + ); + } +} 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 new file mode 100644 index 00000000..1be84427 --- /dev/null +++ b/modules/auth/lib/src/device_sign_up/device_signup_screen.dart @@ -0,0 +1,101 @@ +import 'package:auth/auth.dart'; +import 'package:auth/src/device_sign_up/add_kid_screen.dart'; +import 'package:auth/src/device_sign_up/link_watch/link_watch_screen.dart'; +import 'package:auth/src/device_sign_up/link_watch/link_watch_previous_screen.dart'; +import 'package:auth/src/sign_up/account_created_screen.dart'; +import 'package:auth/src/widgets/layouts/form_step_layout.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:navigation/navigation.dart'; + +class DeviceSignupScreen extends ConsumerStatefulWidget{ + final NavigationContract navigationContract; + + const DeviceSignupScreen({super.key, required this.navigationContract}); + + @override + ConsumerState createState() => DeviceSignupScreenState(navigationContract); +} + +class DeviceSignupScreenState extends ConsumerState{ + late int currentStep; + final NavigationContract navigationContract; + + DeviceSignupScreenState(this.navigationContract); + + @override + void initState() { + currentStep = 0; + } + + @override + Widget build(BuildContext context) { + return getSteps()[currentStep]; + } + + List getSteps(){ + final theme = ref.watch(themePortProvider); + + final continueBtn = Container( + padding: EdgeInsets.all(24), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + child: FilledButton( + onPressed: ()=>{setState(() { + currentStep++; + })}, + child: Container( + width: double.infinity, + child: Expanded(child: Center(child: Text("Continuar")))) + ) + ); + + return [ + AddKidScreen(nextStep: ()=>{setState(() { + currentStep++; + })}), + FormStepLayout( + title: "Crea su perfil", + subtitle: "Necesitamos estos datos para crear su cuenta y gestionar sus pagos y gastos", + currentStep: 1, + numSteps: 3, + body: [CreateProfileScreen()], + footer: [Container( + padding: EdgeInsets.all(24), + color: theme.getColorFor(ThemeCode.backgroundPrimary), + child: continueBtn + )], + nextStep: ()=>{}, + previousStep: ()=>{} + ), + FormStepLayout( + title: "Vincula su correa y su reloj", + currentStep: 2, + numSteps: 3, + body: [LinkWatchPreviousScreen()], + footer: [continueBtn], + nextStep: ()=>{}, + previousStep: ()=>{} + ), + FormStepLayout( + title: "Vincula su correa\ny su reloj", + currentStep: 2, + numSteps: 3, + body: [LinkWatchScreen(step:1)], + footer: [continueBtn], + nextStep: ()=>{}, + previousStep: ()=>{} + ), + FormStepLayout( + title: "Vincula su correa\ny su reloj", + currentStep: 2, + numSteps: 3, + body: [LinkWatchScreen(step:2)], + footer: [continueBtn], + nextStep: ()=>{}, + previousStep: ()=>{} + ), + AccountCreatedScreen(navigationContract: navigationContract, kidAccount: true) + ]; + } +} \ No newline at end of file diff --git a/modules/auth/lib/src/device_sign_up/link_watch/account_created_kid_screen.dart b/modules/auth/lib/src/device_sign_up/link_watch/account_created_kid_screen.dart deleted file mode 100644 index ca63a267..00000000 --- a/modules/auth/lib/src/device_sign_up/link_watch/account_created_kid_screen.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:design_system/design_system.dart'; -import 'package:flutter/material.dart'; -import 'package:auth/src/device_sign_up/add_kid_screen.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class AccountCreatedKidScreen extends ConsumerWidget { - const AccountCreatedKidScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = ref.watch(themePortProvider); - - final model = "SaveWatch Plus 2"; - final id = "1106652524"; - final fullName = "Carlos Pérez Cruz"; - - return Scaffold( - backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), - body: Container( - margin: EdgeInsets.all(30), - child: Center( - child: Column( - spacing: 20, - children: [ - Spacer(flex: 2), - Icon( - Icons.check, - color: theme.getColorFor(ThemeCode.backgroundPrimary), - size: 50, - ), - Text( - "Cuenta creada", - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), - ), - Text.rich( - TextSpan( - text: "Has creado la cuenta para:\n", - children: [ - TextSpan( - text: fullName, - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ), - Text("Reloj: $model"), - Text("ID del reloj: $id"), - Text( - "Ya puedes darle su primera paga paa que empiece a disfrutarla en su reloj", - style: TextStyle(fontWeight: FontWeight.bold), - ), - Spacer(flex: 6), - Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(20), - topLeft: Radius.circular(20), - ), - ), - child: Column( - children: [ - Expanded( - child: FilledButton( - onPressed: () => { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => AddKidScreen()), - ), - }, - child: Text("Dale su primera paga"), - ), - ), - TextButton( - onPressed: () => {}, - child: Text("Añadir otro peque"), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} 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 23b2973c..a26e8ed2 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 @@ -1,304 +1,70 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; class CreateProfileScreen extends ConsumerWidget { const CreateProfileScreen({super.key}); - final int currentStep = 0; + final bool firstTime = false; @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: Center( - child: Column( - spacing: 10, - children: [ - Stepper( - type: StepperType.horizontal, - currentStep: currentStep, - onStepCancel: () => currentStep == 0, - // ? null - // : - // setState(() { - // currentStep -= 1; - // }), - controlsBuilder: - (BuildContext context, ControlsDetails controls) { - return FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonPrimary), - ), - ), - onPressed: controls.onStepContinue, - child: const Text('Continuar'), - ); - }, - steps: [ - Step( - state: currentStep > 0 - ? StepState.complete - : StepState.indexed, - isActive: currentStep >= 0, - stepStyle: currentStep >= 0 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.transparent, - boxShadow: BoxShadow(spreadRadius: 5), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: Text(""), - content: Column( - spacing: 10, - children: [ - Text( - "Crea su perfil", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 30, - ), - ), - Text( - "Necesitamos estos datos para crear su cuenta y gestionar sus pagas y gastos", - ), - Text( - "Comienza con un peque; luego podrás agregar más", - style: TextStyle(fontWeight: FontWeight.bold), - ), - TextField( - decoration: InputDecoration( - labelText: "Nombre", - hintText: "Nombre", - border: OutlineInputBorder(), - ), - ), - TextField( - decoration: InputDecoration( - labelText: "Apellidos", - hintText: "Apellidos", - border: OutlineInputBorder(), - ), - ), - Row( - spacing: 10, - children: [ - Expanded( - child: TextField( - decoration: InputDecoration( - label: Text("Fecha de nacimiento"), - hintText: "DD", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ), - ), - Expanded( - child: TextField( - decoration: InputDecoration( - hintText: "MM", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ), - ), - Expanded( - child: TextField( - decoration: InputDecoration( - hintText: "AAAA", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ), - ), - ], - ), - TextField( - decoration: InputDecoration( - labelText: "Dirección completa", - hintText: "Nombre de la calle", - border: OutlineInputBorder(), - ), - ), - TextButton( - onPressed: () => {}, - child: Text( - "Cambiar dirección", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - Step( - state: currentStep > 1 - ? StepState.complete - : StepState.indexed, - isActive: currentStep >= 1, - stepStyle: currentStep >= 1 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.transparent, - boxShadow: BoxShadow(spreadRadius: 5), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: Text(""), - content: Column( - spacing: 10, - children: [ - Text( - "Vincula su correa y su reloj", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 30, - ), - ), - SvgPicture.asset("assets/images/ui/formulario.svg"), - Row( - spacing: 10, - children: [ - Text("1"), - Column( - children: [ - Text("Escanea la correa"), - Text("El peque podrá realizar pagos"), - ], - ), - ], - ), - Row( - spacing: 10, - children: [ - Text("2"), - Column( - children: [ - Text("Escanea el reloj"), - Text("Visualizarás los gastos que se hagan"), - ], - ), - ], - ), - ], - ), - ), - Step( - state: currentStep > 2 - ? StepState.complete - : StepState.indexed, - isActive: currentStep >= 2, - stepStyle: currentStep >= 2 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.transparent, - boxShadow: BoxShadow(spreadRadius: 5), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: Text(""), - content: Column( - spacing: 10, - children: [ - Text( - "¡Dale su primera paga!", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 30, - ), - ), - Text( - "Enséñales a gestionar su dinero recargando su reloj", - ), - TextField( - decoration: InputDecoration( - labelText: "Cantidad de dinero de la paga", - hintText: "0€", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - ), - Text("Cantidad mínima: 10€"), - Text( - "Por seguridad sólo se puede disponer de un máximo de 150€ por wallet", - ), - Text("Método de ingreso"), - Row( - spacing: 20, - children: [ - OutlinedButton( - onPressed: () => {}, - child: SvgPicture.asset( - "assets/images/ui/visa.svg", - ), - ), - OutlinedButton( - onPressed: () => {}, - child: SvgPicture.asset( - "assets/images/ui/paypal.svg", - ), - ), - OutlinedButton( - onPressed: () => {}, - child: Row( - children: [ - SvgPicture.asset( - "assets/images/ui/banco.svg", - ), - Text("Transferencia"), - ], - ), - ), - ], - ), - Row( - children: [ - Icon(Icons.lock_outline), - Text( - "EL pago en esta app es seguro y cumple la normativa europea. Sólo se usará el dinero que decidas para las huchas de tus hijos.", - ), - ], - ), - ], - ), - ), - ], + return Column( + spacing: 24, + children: [ + Text( + "Comienza con un peque; luego podrás agregar más", + style: TextStyle(fontWeight: FontWeight.bold), + ), + CustomTextField( + label: "Nombre", + hint: "Nombre", + ), + CustomTextField( + label: "Apellidos", + hint: "Apellidos", + ), + Row( + spacing: 10, + children: [ + Expanded( + child: CustomTextField( + numeric: true, + label: "Fecha de nacimiento", + hint: "DD", + length: 2, ), - ], + ), + Expanded( + child: CustomTextField( + numeric: true, + hint: "MM", + length: 2, + ), + ), + Expanded( + child: CustomTextField( + numeric: true, + hint: "AAAA", + length: 4, + ), + ), + ], + ), + CustomTextField( + 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)), ), ), - ), + ], ); } } 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 new file mode 100644 index 00000000..77cc1f12 --- /dev/null +++ b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_previous_screen.dart @@ -0,0 +1,58 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +class LinkWatchPreviousScreen extends ConsumerWidget{ + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Column( + spacing: 10, + children: [ + SvgPicture.asset("assets/images/ui/formulario.svg"), + Row( + spacing: 16, + children: [ + Container( + decoration: ShapeDecoration( + shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))), + color: theme.getColorFor(ThemeCode.backgroundSecondary) + ), + width: 48, + height: 48, + child: Center(child: Text("1", style: TextStyle(fontSize: 24))), + ), + Column( + children: [ + Text("Escanea la correa", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)), + Text("El peque podrá realizar pagos"), + ], + ), + ], + ), + Row( + spacing: 16, + children: [ + Container( + decoration: ShapeDecoration( + shape: CircleBorder(side: BorderSide(color: theme.getColorFor(ThemeCode.backgroundSecondary))), + color: theme.getColorFor(ThemeCode.backgroundSecondary) + ), + width: 48, + height: 48, + child: Center(child: Text("2", style: TextStyle(fontSize: 24))), + ), + Column( + children: [ + Text("Escanea el reloj", textAlign: TextAlign.left, style: TextStyle(fontSize: 24)), + Text("Visualizarás los gastos que se hagan"), + ] + ) + ], + ), + ], + ); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..c6a8a9d6 --- /dev/null +++ b/modules/auth/lib/src/device_sign_up/link_watch/link_watch_screen.dart @@ -0,0 +1,60 @@ +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'; + +class LinkWatchScreen extends ConsumerStatefulWidget{ + final int step; + + const LinkWatchScreen({super.key, required this.step}); + + @override + ConsumerState createState() => LinkWatchScreenState(); +} + +class LinkWatchScreenState extends ConsumerState{ + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + + return Column( + spacing: 32, + children: [ + Row( + children: [ + Text("Escanea la correa"), + Spacer(), + Text("Escanea el reloj") + ], + ), + Container( + padding: EdgeInsets.all(40), + decoration: BoxDecoration( + border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)), + borderRadius: BorderRadius.all(Radius.circular(16)) + ), + 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( + 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)))) + )) + ], + ), + Text("Si no consigues vincular su correa o reloj"), + TextButton(onPressed: ()=>{}, child: Text("Contáctanos")) + ], + ); + } +} \ No newline at end of file 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 e8fe2563..9c28b293 100644 --- a/modules/auth/lib/src/login/presentation/link_phone_screen.dart +++ b/modules/auth/lib/src/login/presentation/link_phone_screen.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:navigation/navigation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,17 +10,18 @@ class LinkPhoneScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - TextEditingController phoneController = TextEditingController(); + // TextEditingController phoneController = TextEditingController(); // String? phone; - return Scaffold( - body: Container( - margin: EdgeInsets.all(30), + return Scaffold(body: SafeArea( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 24), child: Expanded( child: Center( child: Column( - spacing: 10, + spacing: 48, children: [ + Spacer(flex: 8), Text( "¡Nos alegra mucho tenerte por aquí!", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), @@ -43,18 +45,11 @@ class LinkPhoneScreen extends ConsumerWidget { }), ), Expanded( - child: TextField( - onSubmitted: (String value) { - // phone = value; - }, - controller: phoneController, - decoration: InputDecoration( - labelText: "Teléfono móvil", - hintText: "Teléfono", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - ), + child: CustomTextField( + label: "Teléfono móvil", + hint: "Teléfono", + numeric: true + ) ), ], ), @@ -65,11 +60,12 @@ class LinkPhoneScreen extends ConsumerWidget { child: Text("Siguiente"), ), ), + 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 fd7899af..e374bd39 100644 --- a/modules/auth/lib/src/login/presentation/login_screen.dart +++ b/modules/auth/lib/src/login/presentation/login_screen.dart @@ -1,5 +1,6 @@ import 'package:auth/src/login/presentation/loading_google_screen.dart'; import 'package:auth/src/sign_up/signup_screen.dart'; +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navigation/navigation.dart'; @@ -25,34 +26,14 @@ class LoginScreen extends ConsumerWidget { "¡Te damos la bienvenida!", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), - TextField( - decoration: InputDecoration( - hintText: "Nombre de usuario", - labelText: "Nombre de usuario", - border: OutlineInputBorder(), - ), + CustomTextField( + hint: "Nombre de usuario", + label: "Nombre de usuario", ), - TextField( - obscureText: passwordVisible, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - labelText: "Contraseña", - hintText: "********", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon( - passwordVisible - ? Icons.visibility - : Icons.visibility_off, - ), - onPressed: () { - // setState(() { - // passwordVisible = !passwordVisible; - // }); - }, - ), - ), + CustomTextField( + showPassword: passwordVisible, + label: "Contraseña", + hint: "********" ), TextButton( onPressed: () => 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 51b220d8..772703aa 100644 --- a/modules/auth/lib/src/login/presentation/phone_code_screen.dart +++ b/modules/auth/lib/src/login/presentation/phone_code_screen.dart @@ -1,15 +1,12 @@ import 'package:flutter/material.dart'; import 'package:navigation/navigation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -// import 'package:sf_app_platform/payments/view/screens/core/dashboard_screen.dart'; -// import 'package:sf_app_platform/payments/view/screens/login_screen.dart'; class PhoneCodeScreen extends ConsumerWidget { // final String phone; final NavigationContract navigationContract; PhoneCodeScreen({super.key, required this.navigationContract}); - // const PhoneCodeScreen({super.key, required this.phone}); // class PhoneCodeScreenState extends State { final focusNodes = List.generate(6, (int i) { diff --git a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart index 9db4fad3..0ae92371 100644 --- a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart +++ b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart @@ -1,75 +1,135 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navigation/navigation.dart'; -class WelcomeScreen extends ConsumerWidget { +class WelcomeScreen extends ConsumerStatefulWidget { final NavigationContract navigationContract; const WelcomeScreen({super.key, required this.navigationContract}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => WelcomeScreenState(navigationContract: navigationContract); + +} + +class WelcomeScreenState extends ConsumerState{ + late int currentStep; + final NavigationContract navigationContract; + + WelcomeScreenState({required this.navigationContract}); + + @override + void initState() { + super.initState(); + currentStep = 0; + } + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + return Scaffold( - body: Center( - child: Column( - children: [ - Spacer(), - Expanded( - child: CarouselView( - scrollDirection: Axis.horizontal, - itemExtent: double.infinity, - itemSnapping: true, - shrinkExtent: 400, - children: generateSteps(), + body: Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Center( + child: Column( + spacing: 48, + children: [ + Spacer(), + generateSteps()[currentStep], + Column( + spacing: 24, + children: [ + StepIndicator(max: 3, current: currentStep+1, color: theme.getColorFor(ThemeCode.buttonSecondary)), + generateButtons(theme, 3, currentStep+1) + ] ), - ), - FilledButton( - onPressed: () => navigationContract.goTo('/link_phone'), - child: const Text('Continuar'), - ), - Spacer(), - ], - ), - ), + Spacer() + ] + ) + ) + ) ); } - void jumpToNext(BuildContext context) { - // Navigator.pushReplacement( - // context, - // MaterialPageRoute(builder: (_) => LinkPhoneScreen()), - // ); - return; + Widget generateButtons(ThemePort theme, int max, int step){ + if (step==max) { + return FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonPrimary)) + ), + onPressed: () => navigationContract.goTo('/link_phone'), + child: Expanded(child: Center(child: Text('Continuar'))) + ); + } else { + return Column( + spacing: 16, + children: [ + FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary)) + ), + onPressed: ()=>setState(() { + currentStep++; + }), + child: Expanded(child: Center( child: Text("Siguiente"))) + ), + TextButton( + onPressed: ()=>navigationContract.goTo('/link_phone'), + child: Text("Omitir") + ) + ], + ); + } } List generateSteps() { return [ Column( - spacing: 30, + spacing: 48, children: [ SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"), - Text( - "Aprende a gestionar su dinero", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - ), - Text("Tu peque crea hábitos y se divierte mientras lo hace"), - ], + Column( + spacing: 16, + children: [ + Text( + "Aprende a gestionar su dinero", + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30) + ), + Text( + "Tu peque crea hábitos y se divierte mientras lo hace", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18) + ) + ] + ) + ] ), Column( children: [ SvgPicture.asset("assets/images/ui/bienvenida_paso2.svg"), - Text("Tranquilidad en cada pago que hacen"), - Text("Supervisa gastos, fija límites y acompáñalos en cada paso"), + 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( children: [ SvgPicture.asset("assets/images/ui/bienvenida_paso3.svg"), - Text("Pagos fáciles y seguros en sus manos"), - Text("Podrá pagar desde su reloj.\n Sin móvil ni efectivo"), + 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)), ], ), ]; } -} +} \ No newline at end of file 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 index 58694e37..3e21e2c1 100644 --- a/modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart +++ b/modules/auth/lib/src/recover_password/presentation/email_sent_screen.dart @@ -1,3 +1,4 @@ +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'; @@ -8,7 +9,7 @@ class EmailSentScreen extends ConsumerWidget { const EmailSentScreen({super.key, required this.email}); @override - Widget build(BuildContext contex, WidgetRef ref) { + Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themePortProvider); return Scaffold( 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 35abf19d..ed2cb62d 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 @@ -1,6 +1,8 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:get_it/get_it.dart'; +import 'package:navigation/navigation.dart'; class NewPasswordScreen extends ConsumerStatefulWidget { const NewPasswordScreen({super.key}); @@ -24,7 +26,7 @@ class NewPasswordScreenState extends ConsumerState { void initState() { passwordVisible = false; equalPasswords = false; - String password = ""; + password = ""; securityChecks = { "min": false, "capital": false, @@ -36,6 +38,7 @@ class NewPasswordScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final NavigationContract navigationContract = GetIt.I(); final theme = ref.watch(themePortProvider); return Scaffold( @@ -50,25 +53,10 @@ class NewPasswordScreenState extends ConsumerState { "Recuperar contraseña", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), ), - TextField( - obscureText: passwordVisible, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - labelText: "Nueva contraseña", - hintText: "********", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon( - passwordVisible ? Icons.visibility : Icons.visibility_off, - ), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ), + CustomTextField( + showPassword: passwordVisible, + label: "Nueva contraseña", + hint: "********", onChanged: (value) => { setState(() { password = value; @@ -76,25 +64,10 @@ class NewPasswordScreenState extends ConsumerState { }), }, ), - TextField( - obscureText: passwordVisible, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - labelText: "Repetir contraseña", - hintText: "********", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon( - passwordVisible ? Icons.visibility : Icons.visibility_off, - ), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ), + CustomTextField( + showPassword: passwordVisible, + label: "Repetir contraseña", + hint: "********", onChanged: (value) => { setState(() { equalPasswords = password == value; @@ -159,7 +132,7 @@ class NewPasswordScreenState extends ConsumerState { ), Spacer(flex: 1), FilledButton( - onPressed: () => {}, + onPressed: ()=>{navigationContract.pushTo('/main/home')}, child: Container( width: double.infinity, padding: EdgeInsets.all(20), 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 7dc81a88..56972a94 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 @@ -28,12 +28,9 @@ class RestorePasswordScreen extends ConsumerWidget { Text( "Introduce tu email para enviarte un enlace de recuperación", ), - TextField( - decoration: InputDecoration( - labelText: "Correo electrónico", - hintText: "Correo electrónico", - border: OutlineInputBorder(), - ), + CustomTextField( + label: "Correo electrónico", + hint: "Correo electrónico", ), Row( spacing: 20, 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 263202ff..b9b8f8fd 100644 --- a/modules/auth/lib/src/sign_up/account_created_screen.dart +++ b/modules/auth/lib/src/sign_up/account_created_screen.dart @@ -1,10 +1,17 @@ -import 'package:auth/src/device_sign_up/add_kid_screen.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:navigation/navigation.dart'; class AccountCreatedScreen extends ConsumerWidget { - const AccountCreatedScreen({super.key}); + final bool kidAccount; + final NavigationContract navigationContract; + + const AccountCreatedScreen({ + super.key, + required this.kidAccount, + required this.navigationContract, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -12,6 +19,8 @@ class AccountCreatedScreen extends ConsumerWidget { final email = "usuario@example.com"; final fullName = "Carlos Pérez Cruz"; + final model = "SaveWatch Plus 2"; + final id = "1106652524"; return Scaffold( backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), @@ -24,7 +33,7 @@ class AccountCreatedScreen extends ConsumerWidget { Spacer(flex: 10), Icon( Icons.check, - color: theme.getColorFor(ThemeCode.backgroundPrimary), + color: theme.getColorFor(ThemeCode.buttonPrimary), size: 50, ), Text( @@ -32,6 +41,7 @@ class AccountCreatedScreen extends ConsumerWidget { style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ), Text.rich( + textAlign: TextAlign.center, TextSpan( text: "Has creado la cuenta para:\n", children: [ @@ -42,7 +52,8 @@ class AccountCreatedScreen extends ConsumerWidget { ], ), ), - Text.rich( + if (!kidAccount) Text.rich( + textAlign: TextAlign.center, TextSpan( text: "Hemos enviado un email de verificación a:\n", children: [ @@ -53,15 +64,18 @@ class AccountCreatedScreen extends ConsumerWidget { ], ), ), + if (kidAccount) Text("Reloj: $model\nID del reloj: $id"), Text( "Crea la cuenta de tu peque e ingresa su \nprimera paga para utilizarla con su reloj", + textAlign: TextAlign.center, ), FilledButton( onPressed: () => { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => AddKidScreen()), - ), + if (kidAccount){ + navigationContract.goTo('/main/home') + } else { + navigationContract.pushTo('/device_signup') + } }, child: Text("Continuar"), ), 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 d9cbc557..e5e3ac68 100644 --- a/modules/auth/lib/src/sign_up/signup_address_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_address_screen.dart @@ -1,3 +1,4 @@ + import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; class SignupAddressScreen extends StatelessWidget { @@ -6,28 +7,40 @@ class SignupAddressScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - spacing: 30, + spacing: 24, children: [ - Text("Domicilio"), - Text( - "Tu dirección", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + 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, + )), + ] ), - Text( - "Tu dirección nos ayuda a verificar y mantener la seguridad de tu cuenta", - ), - TextField( - decoration: InputDecoration( - hintText: "Dirección completa", - border: OutlineInputBorder(), - ), - ), - TextField( - decoration: InputDecoration( - hintText: "Ciudad", - border: OutlineInputBorder(), - ), + 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"), + ], ), + 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"); @@ -35,12 +48,7 @@ class SignupAddressScreen extends StatelessWidget { hintText: "País", width: double.infinity, ), - TextField( - decoration: InputDecoration( - hintText: "Nacionalidad", - border: OutlineInputBorder(), - ), - ), + CustomTextField(label: "Nacionalidad", hint: "España"), ], ); } 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 1e3aecb0..a8046eb4 100644 --- a/modules/auth/lib/src/sign_up/signup_personal_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_personal_screen.dart @@ -1,5 +1,5 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class SignupPersonalScreen extends StatelessWidget{ const SignupPersonalScreen({super.key}); @@ -7,41 +7,25 @@ class SignupPersonalScreen extends StatelessWidget{ @override Widget build(BuildContext context) { return Column( - spacing: 30, + spacing: 24, children: [ - Text("Datos personales"), - Text("Identifícate", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30)), - Text("Nos aseguraremos de que la cuenta está a nombre del adulto responsable"), - TextField(decoration: InputDecoration(labelText: "Nombre", hintText: "Nombre", border: OutlineInputBorder())), - TextField(decoration: InputDecoration(labelText: "Apellidos", hintText: "Apellidos", border: OutlineInputBorder())), - Row( - children: [ - Expanded( child: TextField( - decoration: InputDecoration(label: Text("Fecha de nacimiento"), hintText: "DD", border: OutlineInputBorder()), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - )), - Expanded( child: TextField( - decoration: InputDecoration(hintText: "MM", border: OutlineInputBorder()), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - )), - Expanded( child: TextField( - decoration: InputDecoration(hintText: "AAAA", border: OutlineInputBorder()), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - )), - ], - ), - 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"), - ], - ), + 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 + )) + ]), + 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 a6432de6..361389c2 100644 --- a/modules/auth/lib/src/sign_up/signup_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_screen.dart @@ -1,136 +1,71 @@ -import 'package:auth/src/sign_up/account_created_screen.dart'; -import 'package:design_system/design_system.dart'; +import 'package:auth/src/widgets/layouts/form_step_layout.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'; import 'package:auth/src/sign_up/signup_user_screen.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:get_it/get_it.dart'; +import 'package:navigation/navigation.dart'; -class SignupScreen extends ConsumerWidget { +class SignupScreen extends ConsumerStatefulWidget { SignupScreen({super.key}); - int currentStep = 0; + ConsumerState createState() => SignupScreenState(); +} + +class SignupScreenState extends ConsumerState { + late int currentStep; @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = ref.watch(themePortProvider); - - return MaterialApp( - home: Scaffold( - body: Center( - child: Container( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - padding: const EdgeInsets.all(20), - child: SizedBox( - child: Stepper( - controlsBuilder: - (BuildContext context, ControlsDetails controls) { - return Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: controls.onStepCancel, - child: const Text('Atrás'), - ), - ), - Expanded( - child: FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonSecondary), - ), - ), - onPressed: controls.onStepContinue, - child: const Text('Siguiente'), - ), - ), - ], - ); - }, - type: StepperType.horizontal, - currentStep: currentStep, - onStepCancel: () => currentStep == 0, - // ? null - // : setState(() { - // currentStep -= 1; - // }), - onStepContinue: () { - bool isLastStep = (currentStep == getSteps().length - 1); - if (isLastStep) { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => AccountCreatedScreen()), - ); - } else { - // setState(() { - // currentStep += 1; - // }); - } - }, - steps: getSteps(), - ), - ), - ), - ), - ), - ); + void initState() { + super.initState(); + currentStep = 0; } - List getSteps() { - return [ - Step( - state: currentStep > 0 ? StepState.complete : StepState.indexed, - isActive: currentStep >= 0, - stepStyle: currentStep >= 0 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.transparent, - boxShadow: BoxShadow(spreadRadius: 5), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: const Text(""), - content: SignupPersonalScreen(), + @override + Widget build(BuildContext context) { + return getSteps()[currentStep]; + } + + List getSteps() { + return [ + FormStepLayout( + title: "Crea tu usuario", + subtitle: "Nos aseguraremos de que la cuenta esté a nombre del adulto responsable", + supertitle: "Datos personales", + currentStep: 1, + numSteps: 3, + body: [SignupPersonalScreen()], + nextStep: ()=>{setState(() { + currentStep++; + })}, + previousStep: ()=>{}, ), - Step( - state: currentStep > 1 ? StepState.complete : StepState.indexed, - isActive: currentStep >= 1, - stepStyle: currentStep >= 1 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.white, - boxShadow: BoxShadow(spreadRadius: 1), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: const Text(""), - content: SignupAddressScreen(), + FormStepLayout( + title: "Tu dirección", + subtitle: "Tu dirección nos ayudará a verificar y mantener la seguridad de tu cuenta", + supertitle: "Domicilio", + currentStep: 2, + numSteps: 3, + body: [SignupAddressScreen()], + nextStep: ()=>{setState(() { + currentStep++; + })}, + previousStep: ()=>{setState(() { + currentStep--; + })}, ), - Step( - state: currentStep > 2 ? StepState.complete : StepState.indexed, - isActive: currentStep >= 2, - stepStyle: currentStep >= 2 - ? StepStyle( - connectorThickness: 0, - color: Color(0xFF329e95), - indexStyle: TextStyle(color: Colors.transparent), - ) - : StepStyle( - connectorThickness: 0, - color: Colors.white, - boxShadow: BoxShadow(spreadRadius: 1), - indexStyle: TextStyle(color: Colors.transparent), - ), - title: const Text(""), - content: SignupUserScreen(), + FormStepLayout( + title: "Identifícate", + subtitle: "Con tu email y tu número podremos mantenerte siempre informado", + supertitle: "Usuario y contacto", + currentStep: 3, + numSteps: 3, + body: [SignupUserScreen()], + nextStep: ()=>{GetIt.I().pushTo('/device_signup')}, + previousStep: ()=>{setState(() { + currentStep--; + })}, ), ]; } diff --git a/modules/auth/lib/src/sign_up/signup_user_screen.dart b/modules/auth/lib/src/sign_up/signup_user_screen.dart index 1b9da81f..b152964a 100644 --- a/modules/auth/lib/src/sign_up/signup_user_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_user_screen.dart @@ -1,3 +1,4 @@ +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; class SignupUserScreen extends StatefulWidget{ @@ -13,62 +14,17 @@ class SignupUserScreenState extends State{ @override Widget build(BuildContext context) { return Column( - spacing: 30, + spacing: 24, children: [ - Text("Usuario y contacto"), - Text("Crea tu usuario", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30)), - Text("Con tu email y tu número podremos mantenerte siempre informado"), - TextField(decoration: InputDecoration(labelText: "Correo electrónico", hintText: "Correo electrónico", border: OutlineInputBorder())), - Row(children: [ - DropdownMenu( - initialSelection: "es", - dropdownMenuEntries: List.generate(3, (int index){ - return DropdownMenuEntry(labelWidget: Icon(Icons.outlined_flag), label: "es", value: "es"); - }) - ), - Expanded(child: TextField( - decoration: InputDecoration(labelText: "Teléfono móvil", hintText: "Teléfono", border: OutlineInputBorder()), - keyboardType: TextInputType.number) - ) - ]), - TextField( - obscureText: passwordVisible, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - labelText: "Contraseña", - hintText: "********", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(passwordVisible - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ) + CustomTextField( + showPassword: passwordVisible, + label: "Contraseña", + hint: "********" ), - TextField(obscureText: passwordVisible, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - labelText: "Repetir contraseña", - hintText: "*******", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(passwordVisible - ? Icons.visibility - : Icons.visibility_off), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ) + CustomTextField( + showPassword: passwordVisible, + label: "Repetir contraseña", + hint: "*******" ), ], ); diff --git a/modules/auth/lib/src/widgets/layouts/form_step_layout.dart b/modules/auth/lib/src/widgets/layouts/form_step_layout.dart new file mode 100644 index 00000000..90ac4668 --- /dev/null +++ b/modules/auth/lib/src/widgets/layouts/form_step_layout.dart @@ -0,0 +1,91 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class FormStepLayout extends ConsumerWidget{ + final int currentStep; + final int numSteps; + final String? supertitle; + final String title; + final String? subtitle; + final List body; + final List? footer; + final VoidCallback nextStep; + final VoidCallback previousStep; + + const FormStepLayout({ + super.key, + required this.title, + this.subtitle, + this.supertitle, + required this.currentStep, + required this.numSteps, + required this.body, + this.footer, + required this.nextStep, + required this.previousStep + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Scaffold( + body: SafeArea(child: SingleChildScrollView(child: Container( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + padding: EdgeInsets.only(top: 40, left: 24, right: 24), + child: Column( + spacing: 32, + children: [ + Column( + spacing: 24, + children: [ + StepIndicator( + max: numSteps, + current: currentStep, + color: theme.getColorFor(ThemeCode.buttonPrimary) + ), + if (supertitle!=null) Text(supertitle!, textAlign: TextAlign.center, style: TextStyle(fontSize: 18)), + Text(title, textAlign: TextAlign.center, style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), + if (subtitle!=null) Text(subtitle!, textAlign: TextAlign.center, style: TextStyle(fontSize: 18)), + ] + ), + Column( + spacing: 40, + children: [ + ...body, + if (footer==null) navigationButtons(currentStep, numSteps, nextStep, previousStep, theme), + ...(footer?? []) + ] + ) + ] + ) + ))) + ); + } + + Widget navigationButtons(int currentStep, int numSteps, VoidCallback nextStep, VoidCallback previousStep, ThemePort theme) { + if (currentStep == numSteps){ + return FilledButton( + onPressed: nextStep, + style: ButtonStyle(backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonPrimary))), + child: Expanded(child: Center(child: Text("Continuar"))) + ); + } else { + return Row( + spacing: 16, + children: [ + Expanded(child: OutlinedButton( + onPressed: previousStep, + child: Expanded(child: Center(child: Text("Atrás"))) + )), + Expanded(child: FilledButton( + onPressed: nextStep, + style: ButtonStyle(backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary))), + child: Expanded(child: Center(child: Text("Siguiente"))) + )) + ] + ); + } + } +} \ No newline at end of file diff --git a/modules/home/lib/src/presentation/deposit_screen.dart b/modules/home/lib/src/presentation/deposit_screen.dart index cc6f80e0..f5ea4be9 100644 --- a/modules/home/lib/src/presentation/deposit_screen.dart +++ b/modules/home/lib/src/presentation/deposit_screen.dart @@ -1,6 +1,5 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:home/src/presentation/wallet_management_layout.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -56,14 +55,10 @@ class DepositScreen extends ConsumerWidget { "Ingresar dinero en el wallet", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), - TextField( - decoration: InputDecoration( - labelText: "Cantidad", - hintText: "0€", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + CustomTextField( + numeric: true, + label: "Cantidad", + hint: "0€", ), Align( alignment: Alignment.topLeft, @@ -130,16 +125,11 @@ class DepositScreen extends ConsumerWidget { }, activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), - TextField( - minLines: 3, - maxLines: 3, - maxLength: 150, - decoration: InputDecoration( - labelText: - "Escribir mensaje a ${kid.name} del motivo del ingreso", - hintText: "Escribe tu mensaje", - border: OutlineInputBorder(), - ), + CustomTextField( + lines: 3, + length: 150, + label: "Escribir mensaje a ${kid.name} del motivo del ingreso", + hint: "Escribe tu mensaje", ), Align( alignment: Alignment.topLeft, @@ -184,11 +174,11 @@ class DepositScreen extends ConsumerWidget { }, activeColor: theme.getColorFor(ThemeCode.buttonPrimary), ), - if (program) TextField(), + if (program) CustomTextField(), ], ), ), ], ); } -} +} \ 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 8047b036..ae75259c 100644 --- a/modules/home/lib/src/presentation/home_screen.dart +++ b/modules/home/lib/src/presentation/home_screen.dart @@ -1,29 +1,31 @@ -import 'package:auth/auth.dart'; 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/kid_wallet_screen.dart'; -import 'package:home/src/presentation/money_text.dart'; +import 'package:get_it/get_it.dart'; +import 'package:home/src/presentation/wallet_item.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:navigation/navigation.dart'; class HomeScreen extends ConsumerWidget { final String name = "Juan"; final double total = 95.03; final List kids = [ - Kid(name: "Carlos", balance: 25.47), - Kid(name: "Ana", balance: 25.47), + Kid(name: "Carlos", balance: 25.47, savings: 8.32), + Kid(name: "Ana", balance: 25.47, savings: 8.32), ]; late final double available = double.parse( kids.fold(total, (t, e) => t - e.balance).toStringAsFixed(2), ); + late final double savings = double.parse( + kids.fold(0.0, (t, e) => t + e.savings).toStringAsFixed(2), + ); HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themePortProvider); + final NavigationContract navigationContract = GetIt.I(); return SafeArea( child: SingleChildScrollView( @@ -51,10 +53,7 @@ class HomeScreen extends ConsumerWidget { Align( alignment: Alignment.topLeft, child: TextButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => CreateProfileScreen()), - ), + onPressed: () => navigationContract.pushTo('/device_signup'), child: Text( "+ Añadir otro peque", style: TextStyle( @@ -64,66 +63,7 @@ class HomeScreen extends ConsumerWidget { ), ), ), - Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), - ), - child: Column( - spacing: 5, - children: [ - Row( - children: [ - Text( - "Wallet", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - Spacer(), - MoneyText( - text: "$total€ total", - size: 25, - resize: true, - color: theme.getColorFor(ThemeCode.textPrimary), - ), - ], - ), - Stack( - children: [ - LinearProgressIndicator( - value: available / total, - minHeight: 70, - borderRadius: BorderRadius.all(Radius.circular(16)), - color: theme.getColorFor(ThemeCode.buttonPrimary), - backgroundColor: theme.getColorFor( - ThemeCode.backgroundTertiary, - ), - ), - FractionallySizedBox( - widthFactor: available / total, - child: Container( - padding: EdgeInsets.symmetric(vertical: 10), - child: Center( - child: MoneyText( - text: "$available€", - size: 35, - resize: true, - color: theme.getColorFor( - ThemeCode.textSecondary, - ), - ), - ), - ), - ), - ], - ), - Center(child: Text("Disponible")), - ], - ), - ), + WalletBalanceBlock(max: total, value: total - available, savings: savings), DepositBlock(max: 150 - total), ], ), @@ -133,141 +73,11 @@ class HomeScreen extends ConsumerWidget { } Widget walletsList(BuildContext context, List kids, WidgetRef ref) { - final theme = ref.watch(themePortProvider); return Column( spacing: 20, children: List.generate(kids.length, (int index) { - return GestureDetector( - onTap: () => { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => KidWalletScreen(kid: kids[index]), - ), - ), - }, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(16.0)), - child: Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: theme.getCardColorFor(index), - ), - ), - child: Column( - children: [ - Align( - alignment: Alignment.topLeft, - child: Text( - kids[index].name, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 25, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - ), - ), - Row( - spacing: 10, - children: [ - SizedBox( - height: 60, - width: 60, - child: SvgPicture.asset("assets/images/ui/face.svg"), - ), - Spacer(), - Column( - children: [ - MoneyText( - text: "${kids[index].balance}€", - size: 50, - resize: true, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - "en su hucha", - style: TextStyle( - color: theme.getColorFor(ThemeCode.textSecondary), - ), - ), - ], - ), - ], - ), - Row( - children: [ - TextButton( - onPressed: () => showDialog( - context: context, - builder: (BuildContext context) => Dialog( - child: Container( - height: 100, - width: double.infinity, - child: Column( - children: [ - FilledButton( - onPressed: () => {}, - child: Text("Cámara"), - ), - OutlinedButton( - onPressed: () => {}, - child: Text("Galería de fotos"), - ), - ], - ), - ), - ), - ), - child: Row( - spacing: 10, - children: [ - Icon( - Icons.edit, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - "Editar", - style: TextStyle( - color: theme.getColorFor( - ThemeCode.textSecondary, - ), - ), - ), - ], - ), - ), - Spacer(), - FilledButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => DepositScreen(kid: kids[index]), - ), - ), - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonSecondary), - ), - ), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 0, - vertical: 10, - ), - child: Text("+ Añadir dinero"), - ), - ), - ], - ), - ], - ), - ), - ), - ); + return WalletItem(context, kids[index], index); }), ); } diff --git a/modules/home/lib/src/presentation/kid_wallet_screen.dart b/modules/home/lib/src/presentation/kid_wallet_screen.dart index 7519dc04..72cf2cba 100644 --- a/modules/home/lib/src/presentation/kid_wallet_screen.dart +++ b/modules/home/lib/src/presentation/kid_wallet_screen.dart @@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:home/src/presentation/deposit_screen.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:home/src/presentation/limits_screen.dart'; -import 'package:home/src/presentation/money_text.dart'; import 'package:home/src/presentation/wage_screen.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,6 +15,7 @@ class KidWalletScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themePortProvider); + final bool locked = false; return Scaffold( backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), @@ -23,19 +23,19 @@ class KidWalletScreen 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)), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: theme.getCardColorFor(0), - ), + colors: locked? theme.getDisabledCardColors() : theme.getCardColorFor(0) + ) ), - child: SizedBox(width: double.infinity, height: 300), + child: SizedBox(width: double.infinity, height: 420) ), Container( margin: EdgeInsets.symmetric(vertical: 50, horizontal: 20), child: Column( - spacing: 15, + spacing: 24, children: [ Row( spacing: 7, @@ -45,191 +45,237 @@ class KidWalletScreen extends ConsumerWidget { icon: Icon( Icons.arrow_back_ios_new_outlined, color: theme.getColorFor(ThemeCode.backgroundPrimary), - ), + size: 24 + ) ), SizedBox( height: 50, - child: SvgPicture.asset("assets/images/ui/face.svg"), + child: SvgPicture.asset("assets/images/ui/face.svg") ), Text( kid.name, style: TextStyle( color: theme.getColorFor(ThemeCode.backgroundPrimary), fontWeight: FontWeight.bold, - fontSize: 20, - ), + fontSize: 20 + ) ), Spacer(), SizedBox( height: 30, - child: SvgPicture.asset("assets/images/ui/face.svg"), + child: SvgPicture.asset("assets/images/ui/face.svg") + ) + ] + ), + Column( + spacing: 16, + children: [ + MoneyText( + text: "${kid.balance.toString()}€", + size: 60, + secondarySize: 24, + color: theme.getColorFor(ThemeCode.textSecondary) ), - ], - ), - MoneyText( - text: "${kid.balance.toString()}€", - size: 60, - resize: true, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - "Saldo disponible", - style: TextStyle( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - ), - ), - LinearProgressIndicator( - value: 0.7, - color: theme.getColorFor(ThemeCode.backgroundPrimary), - backgroundColor: theme - .getColorFor(ThemeCode.backgroundPrimary) - .withAlpha(0x4C), - minHeight: 10, - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - Container( - padding: EdgeInsets.all(10), - margin: EdgeInsets.only(top: 30), - decoration: BoxDecoration( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), - ), - child: Expanded( - child: Center( + Text( + "Saldo disponible", + style: TextStyle( + color: theme.getColorFor(ThemeCode.backgroundPrimary) + ) + ), + LinearProgressIndicator( + value: 0.7, + color: theme.getColorFor(ThemeCode.backgroundPrimary), + backgroundColor: theme + .getColorFor(ThemeCode.backgroundPrimary) + .withAlpha(0x4C), + minHeight: 10, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + TextButton( + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + onPressed: ()=>{}, child: Row( spacing: 10, children: [ - TextButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => DepositScreen(kid: kid), + Icon(Icons.lock_outline, size: 24, color: theme.getColorFor(ThemeCode.textSecondary)), + locked ? + Text("Desbloquear tarjeta", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary))) : + Text("Bloquear tarjeta", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary))) + ] + ) + ) + ] + ), + Column( + spacing: 16, + children: [ + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.only(top: 30), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(20)) + ), + child: Expanded( + child: Center( + child: Row( + children: [ + TextButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => DepositScreen(kid: kid) + ) + ), + child: Column( + spacing: 10, + children: [ + Icon( + Icons.add_circle_outline, + color: theme.getColorFor( + ThemeCode.textPrimary, + ) + ), + Text( + "Añadir", + style: TextStyle( + color: theme.getColorFor( + ThemeCode.textPrimary + ) + ) + ) + ] + ) ), - ), - child: Column( - spacing: 10, - children: [ - Icon( - Icons.add_circle_outline, - color: theme.getColorFor( - ThemeCode.textPrimary, + Spacer(), + TextButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => WageScreen(kid: kid), ), ), - Text( - "Añadir", - style: TextStyle( - color: theme.getColorFor( - ThemeCode.textPrimary, + child: Column( + spacing: 10, + children: [ + Icon( + Icons.account_balance_wallet_outlined, + color: theme.getColorFor( + ThemeCode.textPrimary, + ), ), - ), + Text( + "Paga", + style: TextStyle( + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), + ), + ], ), - ], - ), - ), - Spacer(), - TextButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => WageScreen(kid: kid), ), - ), - child: Column( - spacing: 10, - children: [ - Icon( - Icons.account_balance_wallet_outlined, - color: theme.getColorFor( - ThemeCode.textPrimary, + Spacer(), + TextButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => LimitsScreen(kid: kid), ), ), - Text( - "Paga", - style: TextStyle( - color: theme.getColorFor( - ThemeCode.textPrimary, + child: Column( + spacing: 10, + children: [ + Icon( + Icons.list_alt_outlined, + color: theme.getColorFor( + ThemeCode.textPrimary, + ), ), - ), + Text( + "Límites", + style: TextStyle( + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), + ), + ], ), - ], - ), - ), - Spacer(), - TextButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => LimitsScreen(kid: kid), ), - ), - child: Column( - spacing: 10, - children: [ - Icon( - Icons.list_alt_outlined, - color: theme.getColorFor( - ThemeCode.textPrimary, - ), - ), - Text( - "Límites", - style: TextStyle( - color: theme.getColorFor( - ThemeCode.textPrimary, + Spacer(), + TextButton( + onPressed: () => {}, + child: Column( + spacing: 10, + children: [ + Icon( + Icons.emoji_events_outlined, + color: theme.getColorFor( + ThemeCode.textPrimary, + ), ), - ), - ), - ], - ), - ), - Spacer(), - TextButton( - onPressed: () => {}, - child: Column( - spacing: 10, - children: [ - Icon( - Icons.emoji_events_outlined, - color: theme.getColorFor( - ThemeCode.textPrimary, - ), - ), - Text( - "Metas", - style: TextStyle( - color: theme.getColorFor( - ThemeCode.textPrimary, + Text( + "Metas", + style: TextStyle( + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), ), - ), + ], ), - ], - ), + ), + Spacer(), + TextButton( + onPressed: () => {}, + child: Column( + spacing: 10, + children: [ + Icon( + Icons.cancel_outlined, + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), + Text( + "Quitar", + style: TextStyle( + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), + ), + ], + ), + ), + ], ), - ], + ), ), ), - ), - ), - Container( - padding: EdgeInsets.all(15), - height: 400, - decoration: BoxDecoration( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), - ), - child: Column( - children: [ - Text("Últimos movimientos"), - activityList(context, theme), - TextButton(onPressed: () => {}, child: Text("Ver todos")), - ], - ), - ), - ], - ), - ), - ], - ), + Container( + padding: EdgeInsets.all(15), + height: 400, + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(20)) + ), + child: Expanded(child: Column( + spacing: 24, + children: [ + Text("Últimos movimientos", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), + activityList(context, theme) + ] + )) + ) + ] + ) + ] + ) + ) + ] + ) ); } @@ -251,58 +297,61 @@ class KidWalletScreen extends ConsumerWidget { return Expanded( child: ListView( - children: List.generate(activity.length, (int index) { - return Column( - spacing: 20, - children: [ - Text(activity[index]["date"].toString()), - Column( - spacing: 15, - children: List.generate( - (activity[index]["payments"] as List).length, - (int i) { - //var a = (activity[index]["payments"] as List)[i]; - return Row( - spacing: 7, - children: [ - Container( - padding: EdgeInsets.all(9), - decoration: BoxDecoration( - color: theme.getColorFor( - ThemeCode.backgroundTertiary, + padding: EdgeInsets.symmetric(vertical: 0), + children: [ + ...List.generate(activity.length, (int index) { + return Column( + spacing: 16, + children: [ + Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18)), + Column( + spacing: 16, + children: List.generate( + (activity[index]["payments"] as List).length, + (int i) { + return Row( + spacing: 12, + children: [ + Container( + padding: EdgeInsets.all(9), + decoration: BoxDecoration( + color: theme.getColorFor( + ThemeCode.backgroundTertiary, + ), + borderRadius: BorderRadius.all(Radius.circular(16)) ), - borderRadius: BorderRadius.all(Radius.circular(16)), + child: Icon( + Icons.local_pizza_outlined, + color: theme.getColorFor(ThemeCode.buttonPrimary) + ) ), - child: Icon( - Icons.local_pizza_outlined, - color: theme.getColorFor(ThemeCode.buttonPrimary), + Column( + children: [ + Text( + "Vips", + style: TextStyle(fontWeight: FontWeight.bold) + ), + Text("20:15") + ] ), - ), - Column( - children: [ - Text( - "Vips", - style: TextStyle(fontWeight: FontWeight.bold), - ), - Text("20:15"), - ], - ), - Spacer(), - MoneyText( - text: "5.1€", - size: 20, - resize: true, - color: theme.getColorFor(ThemeCode.textPrimary), - ), - ], - ); - }, - ), - ), - ], - ); - }), - ), + Spacer(), + MoneyText( + text: "5.1€", + size: 16, + secondarySize: 12, + color: theme.getColorFor(ThemeCode.textPrimary), + ) + ] + ); + } + ) + ) + ] + ); + }), + TextButton(onPressed: () => {}, child: Text("Ver todos")), + ] + ) ); } -} +} \ No newline at end of file diff --git a/modules/home/lib/src/presentation/limits_screen.dart b/modules/home/lib/src/presentation/limits_screen.dart index 7b0866f9..3f61160f 100644 --- a/modules/home/lib/src/presentation/limits_screen.dart +++ b/modules/home/lib/src/presentation/limits_screen.dart @@ -103,7 +103,7 @@ class LimitsScreenState extends ConsumerState { ), ], ), - if (dailyLimits[index]["edit"]) TextField(), + if (dailyLimits[index]["edit"]) CustomTextField(), ], ); }), @@ -144,7 +144,7 @@ class LimitsScreenState extends ConsumerState { ), ], ), - if (timeLimits[index]["edit"]) TextField(), + if (timeLimits[index]["edit"]) CustomTextField(), ], ); }), diff --git a/modules/home/lib/src/presentation/wage_screen.dart b/modules/home/lib/src/presentation/wage_screen.dart index eff4788f..641d31d6 100644 --- a/modules/home/lib/src/presentation/wage_screen.dart +++ b/modules/home/lib/src/presentation/wage_screen.dart @@ -1,6 +1,5 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:home/src/presentation/wallet_management_layout.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -61,14 +60,10 @@ class WageScreen extends ConsumerWidget { "Paga automática", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), - TextField( - decoration: InputDecoration( - labelText: "Cantidad", - hintText: "0€", - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + CustomTextField( + numeric: true, + label: "Cantidad", + hint: "0€", ), Text("Saldo total disponible después: 30 €"), ], @@ -154,16 +149,12 @@ class WageScreen extends ConsumerWidget { return DropdownMenuEntry(value: index, label: "$index:00"); }), ), - TextField( - minLines: 3, - maxLines: 3, - maxLength: 150, - decoration: InputDecoration( - labelText: - "Escribir mensaje a ${kid.name} del motivo del ingreso", - hintText: "Escribe tu mensaje", - border: OutlineInputBorder(), - ), + CustomTextField( + lines: 3, + length: 150, + label: + "Escribir mensaje a ${kid.name} del motivo del ingreso", + hint: "Escribe tu mensaje", ), Align( alignment: Alignment.topLeft, diff --git a/modules/home/lib/src/presentation/wallet_item.dart b/modules/home/lib/src/presentation/wallet_item.dart new file mode 100644 index 00000000..f85934e4 --- /dev/null +++ b/modules/home/lib/src/presentation/wallet_item.dart @@ -0,0 +1,248 @@ +import 'dart:ui' as ui; + +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:home/src/presentation/deposit_screen.dart'; +import 'package:home/src/presentation/kid_wallet_screen.dart'; +import 'package:sf_shared/sf_shared.dart'; + + +class WalletItem extends ConsumerWidget{ + final BuildContext context; + final Kid kid; + final int index; + + WalletItem(this.context, this.kid, this.index); + + @override + Widget build(BuildContext context, WidgetRef ref){ + final theme = ref.watch(themePortProvider); + + return GestureDetector( + onTap: ()=>{Navigator.push(context, MaterialPageRoute(builder: (_)=>KidWalletScreen(kid: kid)))}, + child: SizedBox( + height: 227, + width: 382, + child: Stack( + children: [ + SizedBox(height:227, width:382, child: CustomPaint(painter: WalletPainter(ref, index))), + Column( + children: [ + Container( + padding: EdgeInsets.only(left: 24, right: 24, top: 10, bottom: 0), + child: Column( + children: [ + Align( + alignment: Alignment.topLeft, + child: Text(kid.name, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 30, + color: theme.getColorFor(ThemeCode.textSecondary), + ) + ) + ), + Row( + children: [ + SizedBox( + height: 100, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 70, + width: 70, + child: SvgPicture.asset("assets/images/ui/face.svg"), + ) + ) + ), + Spacer(), + Column(children: [ + MoneyText( + text: "${kid.balance}€", + size: 50, + secondarySize: 30, + color: theme.getColorFor(ThemeCode.textSecondary), + height: 0 + ), + Text("en su hucha", style: TextStyle(fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary), height: 0)), + Text("Ahorrado ${kid.savings}€", style: TextStyle(fontSize: 14, color: theme.getColorFor(ThemeCode.textSecondary), height: 0)) + ]) + ] + ), + ], + ), + ), + Spacer(), + Row(children: [ + TextButton( + onPressed: ()=>showModalBottomSheet( + context: context, + builder: (BuildContext context) => PhotoDialog() + ), + child: Row( + spacing: 8, + children: [ + Icon(Icons.edit_outlined, size: 24, color: theme.getColorFor(ThemeCode.textSecondary)), + Text("Editar", style: TextStyle(fontSize: 16, color: theme.getColorFor(ThemeCode.textSecondary))) + ] + ) + ), + Spacer(), + SizedBox( + width: 169, + height: 60, + child: FilledButton( + onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))), + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ) + ), + backgroundColor: WidgetStatePropertyAll(theme.getColorFor(ThemeCode.buttonSecondary)), + padding: WidgetStatePropertyAll(EdgeInsets.all(0)) + ), + child: Center( + child: Text( + "+ Añadir dinero", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500 + ) + ) + ) + ) + ) + ]) + ] + ) + ] + ) + ) + ); + } +} + +class PhotoDialog extends ConsumerWidget{ + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Container( + height: 247, + width: double.infinity, + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.only( + topRight: Radius.circular(16), topLeft: Radius.circular(16)) + ), + child: Column( + spacing: 24, + children: [ + 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) + ) + ) + ) + ) + ), + OutlinedButton( + onPressed: () => {}, + child: Expanded( + child: Center( + child: Text( + "Galería de fotos", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: theme.getColorFor( + ThemeCode.textPrimary) + ) + ) + ) + ) + ) + ] + ), + TextButton( + onPressed: () => {Navigator.pop(context)}, + child: Text( + "Cancelar", + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16 + ) + ) + ) + ], + ) + ); + } +} + +class WalletPainter extends CustomPainter { + + final WidgetRef ref; + final int index; + + WalletPainter(this.ref, this.index); + + @override + void paint(Canvas canvas, Size size) { + final theme = ref.watch(themePortProvider); + final gradient = theme.getCardColorFor(index); + + final brush = Paint() + ..shader = ui.Gradient.linear( + Offset(0, 0), + Offset(size.width, size.height), + gradient, + List.generate(gradient.length, (int index){ + return index/(gradient.length-1); + }) + ) + ..strokeWidth = 3 + ..strokeCap = StrokeCap.round; + + final double radius = 20; + final double buttonHeight = 60; + final double buttonWidth = 169; + + final path = Path() + ..moveTo(radius, 0) + ..lineTo(size.width-radius, 0) + ..arcToPoint(Offset(size.width, radius), radius: Radius.circular(radius)) + ..lineTo(size.width, size.height-buttonHeight-1.5*radius) + ..arcToPoint(Offset(size.width-radius, size.height-buttonHeight-0.5*radius), radius: Radius.circular(radius)) + ..lineTo(size.width-buttonWidth+0.5*radius, size.height-buttonHeight-0.5*radius) + ..arcToPoint(Offset(size.width-buttonWidth-0.5*radius, size.height-buttonHeight+0.5*radius), radius: Radius.circular(radius), clockwise: false) + ..lineTo(size.width-buttonWidth-0.5*radius, size.height-radius) + ..arcToPoint(Offset(size.width-buttonWidth-1.5*radius, size.height), radius: Radius.circular(radius)) + ..lineTo(radius, size.height) + ..arcToPoint(Offset(0, size.height-radius), radius: Radius.circular(radius)) + ..lineTo(0, radius) + ..arcToPoint(Offset(radius, 0), radius: Radius.circular(radius)) + ..close(); + + canvas.drawPath(path, brush); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} \ No newline at end of file diff --git a/modules/home/lib/src/presentation/wallet_management_layout.dart b/modules/home/lib/src/presentation/wallet_management_layout.dart index a2d0deab..3e9ebacb 100644 --- a/modules/home/lib/src/presentation/wallet_management_layout.dart +++ b/modules/home/lib/src/presentation/wallet_management_layout.dart @@ -19,6 +19,8 @@ class WalletManagementLayout extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themePortProvider); + final bool locked = false; + final content = [ Container( padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), @@ -82,11 +84,11 @@ class WalletManagementLayout extends ConsumerWidget { children: [ DecoratedBox( decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(30)), + borderRadius: const BorderRadius.all(Radius.circular(24)), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: theme.getCardColorFor(0), + colors: locked? theme.getDisabledCardColors() : theme.getCardColorFor(0) ), ), child: SizedBox(width: double.infinity, height: 200), diff --git a/modules/home/pubspec.yaml b/modules/home/pubspec.yaml index 8cd79e9b..dc31c5d4 100644 --- a/modules/home/pubspec.yaml +++ b/modules/home/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: path: ../../packages/design_system sf_shared: path: ../../packages/sf_shared + fonts: + path: ../../packages/fonts #dependencies go here flutter_svg: ^2.2.1 flutter_riverpod: ^3.0.3 diff --git a/modules/notifications/lib/src/core/activity_list.dart b/modules/notifications/lib/src/core/activity_list.dart index 686a86b5..a1ad9dda 100644 --- a/modules/notifications/lib/src/core/activity_list.dart +++ b/modules/notifications/lib/src/core/activity_list.dart @@ -25,82 +25,65 @@ class ActivityListState extends ConsumerState { Widget build(BuildContext context) { final theme = ref.watch(themePortProvider); - final colors = [ - Colors.cyan, - Colors.pinkAccent, - Colors.deepOrangeAccent, - Colors.red, - ]; - final icons = { - "wage": Icons.wallet, - "goal": Icons.emoji_events_outlined, - "lock": Icons.lock_outline, - "reload": Icons.attach_money_outlined, - }; - final titles = { - "wage": "Entrega de paga", - "goal": "¡Objetivo cumplido!", - "lock": "Bloqueo de pago", - "reload": "Recarga familiar", - }; + final colors = [Colors.cyan, Colors.pinkAccent, Colors.deepOrangeAccent, Colors.red]; + final icons = {"wage": Icons.wallet, "goal": Icons.emoji_events_outlined, "lock": Icons.lock_outline, "reload": Icons.attach_money_outlined}; + final titles = {"wage": "Entrega de paga", "goal": "¡Objetivo cumplido!", "lock": "Bloqueo de pago", "reload": "Recarga familiar"}; return Column( - spacing: 20, + spacing: 32, children: List.generate(widget.activity.length, (int index) { var logItem = Container( - padding: EdgeInsets.all(20), + padding: EdgeInsets.all(24), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), - border: BoxBorder.fromLTRB( - left: BorderSide(color: colors[index % colors.length], width: 5), - ), + borderRadius: BorderRadius.all(Radius.circular(24)), + border: BoxBorder.fromLTRB(left: BorderSide( + color: colors[index % colors.length], width: 8)) ), child: Column( - spacing: 15, + spacing: 24, children: [ Row( + spacing: 8, children: [ - Icon( - icons[widget.activity[index]["type"]], + Icon(icons[widget.activity[index]["type"]], color: colors[index % colors.length], + size: 20 ), - Text( - titles[widget.activity[index]["type"]]!, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - Spacer(), - Text("14/01/2005"), - ], + Expanded(child: Text(titles[widget.activity[index]["type"]]!, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18, letterSpacing: 0) + )), + Text("14/01/2005", style: TextStyle(fontSize: 14, letterSpacing: 0)) + ] ), Align( alignment: Alignment.topLeft, - child: Text("Ana ya tiene su paga de 5€ en el reloj"), - ), - ], - ), + child: Text("Ana ya tiene su paga de 5€ en el reloj", style: TextStyle(fontSize: 14, letterSpacing: 0)), + ) + ] + ) ); if (widget.edit) { return Row( + spacing: 10, children: [ Checkbox( value: values[index], onChanged: (value) => { setState(() { values[index] = !values[index]; - }), - }, + })}, activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - semanticLabel: "Eliminar", + semanticLabel: "Eliminar" ), - Expanded(child: logItem), - ], + Expanded(child: logItem) + ] ); } else { return logItem; } - }), + }) ); } -} +} \ No newline at end of file diff --git a/modules/notifications/lib/src/presentation/activity_screen.dart b/modules/notifications/lib/src/presentation/activity_screen.dart index c0fe189d..43a7c26c 100644 --- a/modules/notifications/lib/src/presentation/activity_screen.dart +++ b/modules/notifications/lib/src/presentation/activity_screen.dart @@ -32,7 +32,7 @@ class ActivityScreen extends ConsumerWidget { TextButton(onPressed: () => {}, child: Text("Mes")), ], ), - SizedBox(height: 200, child: LineGraph()), + LineGraph(), ActivityList(activity: activity, edit: false), ]; diff --git a/modules/notifications/lib/src/presentation/alert_screen.dart b/modules/notifications/lib/src/presentation/alert_screen.dart index 4f46d168..86406541 100644 --- a/modules/notifications/lib/src/presentation/alert_screen.dart +++ b/modules/notifications/lib/src/presentation/alert_screen.dart @@ -47,7 +47,11 @@ class AlertScreenState extends ConsumerState { ), ], ), - ActivityList(activity: activity, edit: edit), + Expanded( + child: SingleChildScrollView( + child: ActivityList(activity: activity, edit: edit) + ) + ) ], ), ), diff --git a/modules/profile/lib/src/core/kid_line_chart.dart b/modules/profile/lib/src/core/kid_line_chart.dart new file mode 100644 index 00000000..d6eaf940 --- /dev/null +++ b/modules/profile/lib/src/core/kid_line_chart.dart @@ -0,0 +1,178 @@ +import 'package:design_system/design_system.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:sf_shared/sf_shared.dart'; + +class KidLineChart extends ConsumerWidget{ + final int index; + final Kid kid; + + final weekDays = const ["L", "M", "X", "J", "V", "S", "D"]; + + const KidLineChart({ + super.key, + required this.kid, + this.index = 0 + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Container( + width: 183, + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: LinearGradient(colors: theme.getCardColorFor(index)), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: Column( + spacing: 16, + children: [ + Row( + spacing: 16, + children: [ + SizedBox( + height: 50, + width: 50, + child: SvgPicture.asset("assets/images/ui/face.svg"), + ), + Column(children: [ + Text( + kid.name, + textAlign: TextAlign.left, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: theme.getColorFor(ThemeCode.textSecondary) + ) + ), + MoneyText( + text: "${kid.balance}€", + size: 26, + secondarySize: 16, + color: theme.getColorFor(ThemeCode.textSecondary) + ) + ]) + ], + ), + SizedBox( + height: 93, + child: LineChart(LineChartData( + gridData: FlGridData( + show: true, + drawHorizontalLine: false, + drawVerticalLine: true, + verticalInterval: 1, + getDrawingVerticalLine: (value)=>FlLine(strokeWidth: 12, color: Colors.black26) + ), + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + interval: 1, + showTitles: true, + reservedSize: 29, + getTitlesWidget: (double value, TitleMeta meta){ + String text = weekDays[value.toInt()]; + + return SideTitleWidget( + space: 4, + meta: meta, + child: Expanded( + child: Center( + child: Text( + text, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: theme.getColorFor(ThemeCode.textSecondary) + ) + ) + ) + ), + ); + }, + ), + ), + leftTitles: AxisTitles(), + topTitles: AxisTitles(), + rightTitles: AxisTitles() + ), + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + tooltipBorderRadius: BorderRadius.all(Radius.circular(7)), + getTooltipColor: (spot) => theme.getColorFor(ThemeCode.buttonSecondary), + getTooltipItems: (List touchedSpots) { + return touchedSpots.map((LineBarSpot touchedSpot) { + return LineTooltipItem( + "${touchedSpot.y}€", + TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: theme.getColorFor(ThemeCode.textSecondary) + ), + ); + }).toList(); + }, + ), + getTouchedSpotIndicator: ( + _, + indicators, + ) { + return indicators + .map((int index) => const TouchedSpotIndicatorData( + FlLine(color: Colors.transparent), + FlDotData(show: false), + )) + .toList(); + }, + touchSpotThreshold: 25, + distanceCalculator: + (Offset touchPoint, Offset spotPixelCoordinates) => + (touchPoint - spotPixelCoordinates).distance, + ), + borderData: FlBorderData( + show: true, + border: Border( + bottom: BorderSide( + color: Colors.lightBlue.withValues(alpha: 0.2), + width: 4 + ), + left: const BorderSide(color: Colors.transparent), + right: const BorderSide(color: Colors.transparent), + top: const BorderSide(color: Colors.transparent), + ), + ), + lineBarsData: [ + LineChartBarData( + isCurved: true, + color: Colors.white, + barWidth: 4, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData(show: false), + spots: List.generate(weekDays.length, (int index){ + return FlSpot(index.toDouble(), ((index+1)%2)/2+0.25); + }) + ) + ], + minX: 0, + maxX: weekDays.length-1, + maxY: 1, + minY: 0, + )) + ), + Row( + spacing: 16, + children: [ + Text("Editar hucha", style: TextStyle(color: theme.getColorFor(ThemeCode.textSecondary), fontSize: 14)), + Icon(Icons.edit_outlined, color: theme.getColorFor(ThemeCode.textSecondary), size: 13) + ] + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/modules/profile/lib/src/presentation/profile_screen.dart b/modules/profile/lib/src/presentation/profile_screen.dart index 71af4c7d..031607d5 100644 --- a/modules/profile/lib/src/presentation/profile_screen.dart +++ b/modules/profile/lib/src/presentation/profile_screen.dart @@ -1,6 +1,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:notifications/notifications.dart'; +import 'package:profile/src/core/kid_line_chart.dart'; import 'package:profile/src/settings_screen.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -49,53 +50,45 @@ class ProfileScreen extends ConsumerWidget { ), ], ), + WalletBalanceBlock(max: total, value: available, savings: savings, savingsPlan: savingsPlan), + LineGraph(), + DepositBlock(max: 150 - total), Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: theme.getColorFor(ThemeCode.backgroundPrimary), borderRadius: BorderRadius.all(Radius.circular(20)), ), - child: Column( - spacing: 5, - children: [ - Row( - children: [ - Text("Wallet", style: TextStyle(fontWeight: FontWeight.bold)), - Spacer(), - Text("$total€"), - ], - ), - Stack( - children: [ - LinearProgressIndicator( - value: available / total, - minHeight: 70, - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - FractionallySizedBox( - widthFactor: available / total, - child: Container( - padding: EdgeInsets.symmetric(vertical: 20), - child: Center( - child: Text( - "$available€", - style: TextStyle( - color: theme.getColorFor(ThemeCode.textSecondary), - fontSize: 20, - ), - ), - ), - ), - ), - ], - ), - Center(child: Text("Disponible")), - ], + child: TextButton( + onPressed: ()=>{}, + child: Row( + spacing: 10, + children: [ + Icon(Icons.output_outlined, + size: 24, + color: theme.getColorFor(ThemeCode.textPrimary) + ), + Text( + "Retirar dinero del wallet", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: theme.getColorFor(ThemeCode.textPrimary) + ) + ) + ], + ) + ) + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + spacing: 16, + children: List.generate(kids.length, (int index){ + return KidLineChart(kid: kids[index], index: index); + }) ), ), - SizedBox(height: 200, child: LineGraph()), - DepositBlock(max: 150 - total), - Row(), ActivityList(activity: activity, edit: false), ]; diff --git a/modules/profile/lib/src/profile_builder.dart b/modules/profile/lib/src/profile_builder.dart index e314bd25..27308ff0 100644 --- a/modules/profile/lib/src/profile_builder.dart +++ b/modules/profile/lib/src/profile_builder.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:notifications/notifications.dart'; import 'package:profile/profile.dart'; class ProfileBuilder { diff --git a/modules/profile/pubspec.yaml b/modules/profile/pubspec.yaml index bddcae03..55d1a18f 100644 --- a/modules/profile/pubspec.yaml +++ b/modules/profile/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: flutter_riverpod: ^3.0.3 get_it: ^9.0.5 go_router: ^17.0.0 + flutter_svg: ^2.2.2 + fl_chart: ^1.1.1 dev_dependencies: flutter_test: diff --git a/packages/design_system/lib/design_system.dart b/packages/design_system/lib/design_system.dart index 11d0bc22..96b767ff 100644 --- a/packages/design_system/lib/design_system.dart +++ b/packages/design_system/lib/design_system.dart @@ -1,2 +1,7 @@ export 'src/theme/theme_port.dart'; export 'src/theme/theme_sf_adapter.dart'; +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 diff --git a/packages/design_system/lib/src/inputs/textfields.dart b/packages/design_system/lib/src/inputs/textfields.dart new file mode 100644 index 00000000..c4d5e011 --- /dev/null +++ b/packages/design_system/lib/src/inputs/textfields.dart @@ -0,0 +1,72 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class CustomTextField extends ConsumerStatefulWidget{ + bool? showPassword; + final bool numeric; + final String hint; + final String label; + final int? lines; + final ValueChanged? onChanged; + final int? length; + + CustomTextField({ + super.key, + this.showPassword, + this.numeric = false, + this.hint = '', + this.label = '', + this.lines, + this.length, + this.onChanged, + }); + + @override + ConsumerState createState() => CustomTextFieldState(); +} + +class CustomTextFieldState extends ConsumerState{ + + @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 + ), + 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 new file mode 100644 index 00000000..93251da8 --- /dev/null +++ b/packages/design_system/lib/src/progress_bars/progress_bar.dart @@ -0,0 +1,56 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class ProgressBar extends ConsumerWidget{ + final double max; + final double value; + final double height; + final double textSize; + final double textSecondarySize; + final Color backgroundColor; + final Color foregroundColor; + final Color textColor; + + const ProgressBar( + this.max, + this.value, + this.height, + this.textSize, + this.textSecondarySize, + this.backgroundColor, + this.foregroundColor, + this.textColor, + ); + + @override + Widget build(BuildContext context, WidgetRef ref) { + + return + Stack( + children: [ + LinearProgressIndicator( + value: value / max, + minHeight: height, + borderRadius: BorderRadius.all(Radius.circular(24)), + color: foregroundColor, + backgroundColor: backgroundColor + ), + FractionallySizedBox( + widthFactor: value / max, + child: SizedBox( + height: height, + child: Center( + child: MoneyText( + text: "$value€", + size: textSize, + secondarySize: textSecondarySize, + color: textColor, + ), + ) + ), + ), + ] + ); + } +} \ No newline at end of file diff --git a/packages/design_system/lib/src/snackbars/snackbar.dart b/packages/design_system/lib/src/snackbars/snackbar.dart new file mode 100644 index 00000000..e393bae2 --- /dev/null +++ b/packages/design_system/lib/src/snackbars/snackbar.dart @@ -0,0 +1,65 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +enum MessageType { + info, + error, + warning, + success +} + +class CustomSnackBar extends ConsumerWidget{ + final MessageType type; + final String message; + + CustomSnackBar({ + super.key, + this.type = MessageType.info, + required this.message, + }); + + @override + SnackBar build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + late final Color foregroundColor; + late final Color backgroundColor; + late final IconData icon; + + switch (type){ + case MessageType.info: + backgroundColor = Color(0xFFE3EFFD); + foregroundColor = Color(0xFF1F4ECF); + icon = Icons.info; + case MessageType.error: + backgroundColor = Color(0xFFFBEDE9); + foregroundColor = Color(0xFFD12D00); + icon = Icons.cancel; + case MessageType.warning: + backgroundColor = Color(0xFFFBF3E2); + foregroundColor = Color(0xFFE34B04); + icon = Icons.warning_outlined; + case MessageType.success: + backgroundColor = Color(0xFFE2F4E8); + foregroundColor = Color(0xFF00713D); + icon = Icons.check_circle; + } + + return SnackBar( + behavior: SnackBarBehavior.floating, + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + side: BorderSide(color: foregroundColor, width: 1), + borderRadius: BorderRadius.all(Radius.circular(10)) + ), + content: Row( + spacing: 8, + children: [ + Icon(icon, color: foregroundColor), + Expanded(child: Text(message, style: TextStyle(color: theme.getColorFor(ThemeCode.textPrimary), fontSize: 14))) + ], + ), + ); + } +} \ No newline at end of file diff --git a/packages/design_system/lib/src/steps/step_indicator.dart b/packages/design_system/lib/src/steps/step_indicator.dart new file mode 100644 index 00000000..9123a50a --- /dev/null +++ b/packages/design_system/lib/src/steps/step_indicator.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class StepIndicator extends StatelessWidget{ + final int max; + final int current; + final Color color; + + const StepIndicator({super.key, required this.max, required this.current, required this.color}); + + @override + Widget build(BuildContext context) { + return Row( + spacing: 12, + children: [ + Spacer(), + ...List.generate(max, (int index){ + return DecoratedBox( + decoration: ShapeDecoration( + shape: CircleBorder(side: BorderSide(color: color)), + color: (index < current)? color: Colors.transparent + ), + child: SizedBox(width: 16, height: 16), + ); + }), + Spacer() + ] + ); + } +} \ No newline at end of file diff --git a/modules/home/lib/src/presentation/money_text.dart b/packages/design_system/lib/src/texts/money_text.dart similarity index 66% rename from modules/home/lib/src/presentation/money_text.dart rename to packages/design_system/lib/src/texts/money_text.dart index 842b644d..d3c7cb66 100644 --- a/modules/home/lib/src/presentation/money_text.dart +++ b/packages/design_system/lib/src/texts/money_text.dart @@ -3,10 +3,18 @@ import 'package:flutter/material.dart'; class MoneyText extends StatelessWidget { final String text; final double size; - final bool resize; + final double? secondarySize; final Color color; + final double height; - const MoneyText({super.key, required this.text, required this.size, required this.resize, required this.color}); + const MoneyText({ + super.key, + required this.text, + required this.size, + this.secondarySize, + required this.color, + this.height = 1, + }); @override Widget build(BuildContext context) { @@ -18,18 +26,18 @@ class MoneyText extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: size, - color: color + color: color, + height: height ), children: [ TextSpan( text: cents, style: TextStyle( fontWeight: FontWeight.normal, - fontSize: resize ? size/2 : size + fontSize: secondarySize ?? size, ) ) ] )); } - } \ No newline at end of file diff --git a/packages/design_system/lib/src/theme/theme_port.dart b/packages/design_system/lib/src/theme/theme_port.dart index 19b06231..ea346c1f 100644 --- a/packages/design_system/lib/src/theme/theme_port.dart +++ b/packages/design_system/lib/src/theme/theme_port.dart @@ -25,6 +25,7 @@ enum ThemeCode { abstract class ThemePort { late Map theme; late List> cardColors; + late List disabledCardColors; Color getColorFor(ThemeCode code) { Color? c = theme[code]; @@ -37,4 +38,8 @@ abstract class ThemePort { List getCardColorFor(int index) { return cardColors[index % cardColors.length]; } + + List getDisabledCardColors() { + return disabledCardColors; + } } diff --git a/packages/design_system/lib/src/theme/theme_sf_adapter.dart b/packages/design_system/lib/src/theme/theme_sf_adapter.dart index 846e3a90..c4a68ed4 100644 --- a/packages/design_system/lib/src/theme/theme_sf_adapter.dart +++ b/packages/design_system/lib/src/theme/theme_sf_adapter.dart @@ -25,4 +25,11 @@ class ThemeSfAdapter extends ThemePort { @override List> get cardColors => _cardColors; + + final List _disabledCardColors = [ + Color(0xFF989797), Color(0xFF797676), Color(0xFF5F5A5A) + ]; + + @override + List get disabledCardColors => _disabledCardColors; } diff --git a/packages/design_system/pubspec.yaml b/packages/design_system/pubspec.yaml index ea02fd3a..6167ab2d 100644 --- a/packages/design_system/pubspec.yaml +++ b/packages/design_system/pubspec.yaml @@ -13,6 +13,8 @@ dependencies: sdk: flutter flutter_riverpod: ^3.0.3 get_it: ^9.0.5 + fonts: + path: ../../packages/fonts dev_dependencies: flutter_test: diff --git a/packages/fonts/pubspec.yaml b/packages/fonts/pubspec.yaml new file mode 100644 index 00000000..349b983a --- /dev/null +++ b/packages/fonts/pubspec.yaml @@ -0,0 +1,20 @@ +name: fonts +# resolution: workspace +description: "A new Flutter package project." +version: 0.0.1 + +environment: + sdk: ^3.9.2 + +dependencies: + flutter: + sdk: flutter + +flutter: + uses-material-design: true + fonts: + - family: Stolzl + fonts: + - asset: lib/fonts/stolzl_regular.otf + - asset: lib/fonts/stolzl_bold.otf + weight: 500 \ 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 513ec32e..b9db2b43 100644 --- a/packages/sf_shared/lib/sf_shared.dart +++ b/packages/sf_shared/lib/sf_shared.dart @@ -4,3 +4,4 @@ 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'; diff --git a/packages/sf_shared/lib/src/models/kid.dart b/packages/sf_shared/lib/src/models/kid.dart index 7e3d2720..75838220 100644 --- a/packages/sf_shared/lib/src/models/kid.dart +++ b/packages/sf_shared/lib/src/models/kid.dart @@ -1,10 +1,11 @@ class Kid { final String name; final double balance; + final double savings; const Kid({ required this.name, required this.balance, - + required this.savings, }); } \ 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 b4f564a6..c534092f 100644 --- a/packages/sf_shared/lib/src/widgets/deposit_block.dart +++ b/packages/sf_shared/lib/src/widgets/deposit_block.dart @@ -1,6 +1,5 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class DepositBlock extends ConsumerWidget { @@ -20,40 +19,56 @@ class DepositBlock extends ConsumerWidget { ), margin: EdgeInsets.only(top: 10), child: Column( + spacing: 24, children: [ - Text( - "Ingresar dinero en el wallet", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + Align( + alignment: Alignment.centerLeft, + child: Text( + "Ingresar dinero en el wallet", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), + ), ), - Row( - spacing: 10, + Column( + spacing: 16, children: [ - Expanded( - child: TextField( - decoration: InputDecoration( - labelText: "Cantidad", - hintText: "0€", - border: OutlineInputBorder(), + Row( + spacing: 10, + children: [ + Expanded( + child: CustomTextField( + label: "Cantidad", + hint: "0€", + numeric: true, + ), ), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - ), + FilledButton( + onPressed: () => {}, + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + theme.getColorFor(ThemeCode.buttonPrimary), + ), + shape: WidgetStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(18)) + )) + ), + child: SizedBox( + height: 60, + child: Center(child: Text("Ingresar", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)))), + ), + ], ), - FilledButton( - onPressed: () => {}, - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.getColorFor(ThemeCode.buttonPrimary), - ), - ), - child: Text("Ingresar"), + Align( + alignment: Alignment.topLeft, + child: Row( + spacing: 4, + children: [ + Icon(Icons.info_outline, size: 16), + Text("Máximo que puedes añadir: $max€"), + ], + ) ), ], - ), - Align( - alignment: Alignment.topLeft, - child: Text("Máximo que puedes añadir: $max€"), - ), + ) ], ), ); diff --git a/packages/sf_shared/lib/src/widgets/line_graph.dart b/packages/sf_shared/lib/src/widgets/line_graph.dart index 4e42da11..3e764b41 100644 --- a/packages/sf_shared/lib/src/widgets/line_graph.dart +++ b/packages/sf_shared/lib/src/widgets/line_graph.dart @@ -4,10 +4,7 @@ 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,1,0,1,0],[1,0,1,0,1,0,1]]; LineGraph({super.key}); @@ -16,7 +13,8 @@ class LineGraph extends ConsumerStatefulWidget { } class LineGraphState extends ConsumerState { - final weekDays = ["L", "M", "X", "J", "V", "S", "D"]; + + final weekDays = ["L", "M", "X", "J", "V", "S", "D"]; String? timeSpan; late var days = weekDays; @@ -31,148 +29,146 @@ class LineGraphState extends ConsumerState { final theme = ref.watch(themePortProvider); return Container( - padding: EdgeInsets.all(15), + padding: EdgeInsets.all(20), decoration: BoxDecoration( - border: BoxBorder.fromLTRB( - left: BorderSide(color: Colors.cyan, width: 5), - ), - borderRadius: BorderRadius.all(Radius.circular(20)), - color: theme.getColorFor(ThemeCode.backgroundPrimary), + border: BoxBorder.fromLTRB(left: BorderSide(color: Colors.cyan, width: 5)), + borderRadius: BorderRadius.all(Radius.circular(13)), + color: theme.getColorFor(ThemeCode.backgroundPrimary) ), child: Column( - spacing: 10, + spacing: 32, children: [ - Row( - children: [ - Text("Gastos", style: TextStyle(fontWeight: FontWeight.bold)), - Spacer(), - Container( - padding: EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: theme.getColorFor(ThemeCode.backgroundSecondary), - ), - child: DropdownButton( - underline: Container(), - value: timeSpan, - onChanged: (String? value) { - setState(() { - timeSpan = value; - }); + Row(children: [ + 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(), + 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))), + ] + ), + ) + ]), + SizedBox( + height: 160, + child: LineChart(LineChartData( + gridData: FlGridData( + show: true, + drawHorizontalLine: false, + drawVerticalLine: true, + verticalInterval: 1, + getDrawingVerticalLine: (value)=>FlLine(strokeWidth: 43, color: theme.getColorFor(ThemeCode.backgroundSecondary)) + ), + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + interval: 1, + showTitles: true, + reservedSize: 40, + getTitlesWidget: (double value, TitleMeta meta){ + String text = weekDays[value.toInt()]; + + return SideTitleWidget( + space: 4, + meta: meta, + child: Expanded(child: Center(child: Text(text, style: TextStyle(fontSize: 12)))), + ); }, - dropdownColor: theme.getColorFor(ThemeCode.backgroundPrimary), - items: [ - DropdownMenuItem(value: "day", child: Text("Hoy")), - DropdownMenuItem(value: "week", child: Text("Esta semana")), - DropdownMenuItem(value: "month", child: Text("Este mes")), - ], ), ), - ], - ), - Expanded( - child: LineChart( - LineChartData( - gridData: FlGridData( - show: true, - drawHorizontalLine: false, - drawVerticalLine: true, - verticalInterval: 1, + leftTitles: AxisTitles(), + topTitles: AxisTitles(), + rightTitles: AxisTitles() + ), + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + tooltipBorderRadius: BorderRadius.all(Radius.circular(7)), + getTooltipColor: (spot) => theme.getColorFor(ThemeCode.buttonSecondary), + getTooltipItems: (List touchedSpots) { + return touchedSpots.map((LineBarSpot touchedSpot) { + return LineTooltipItem( + "${touchedSpot.y}€", + TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: theme.getColorFor(ThemeCode.textSecondary)), + ); + }).toList(); + }, + ), + getTouchedSpotIndicator: ( + _, + indicators, + ) { + return indicators + .map((int index) => const TouchedSpotIndicatorData( + FlLine(color: Colors.transparent), + FlDotData(show: false), + )) + .toList(); + }, + touchSpotThreshold: 25, + distanceCalculator: + (Offset touchPoint, Offset spotPixelCoordinates) => + (touchPoint - spotPixelCoordinates).distance, + ), + borderData: FlBorderData( + show: true, + border: Border( + bottom: BorderSide( + color: Colors.lightBlue.withValues(alpha: 0.2), + width: 4 ), - titlesData: FlTitlesData( - //show: false, - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 40, - getTitlesWidget: (double value, TitleMeta meta) => - SideTitleWidget( - space: 4, - meta: meta, - /*fitInside: fitInsideBottomTitle - ? SideTitleFitInsideData.fromTitleMeta(meta, distanceFromEdge: 0) - : SideTitleFitInsideData.disable(),*/ - child: Text(weekDays[value.toInt()]), - ), - ), - ), - leftTitles: AxisTitles(), - topTitles: AxisTitles(), - rightTitles: AxisTitles(), - ), - lineTouchData: LineTouchData( - touchTooltipData: LineTouchTooltipData( - getTooltipColor: (touchedSpot) => - theme.getColorFor(ThemeCode.buttonSecondary), - getTooltipItems: (List touchedBarSpots) { - return touchedBarSpots.map((barSpot) { - return LineTooltipItem( - "${barSpot.y} €", - TextStyle( - color: theme.getColorFor(ThemeCode.textSecondary), - ), - ); - }).toList(); - }, - ), - ), - borderData: FlBorderData( - show: true, - border: Border( - bottom: BorderSide( - color: Colors.lightBlue.withValues(alpha: 0.2), - width: 4, - ), - left: const BorderSide(color: Colors.transparent), - right: const BorderSide(color: Colors.transparent), - top: const BorderSide(color: Colors.transparent), - ), - ), - lineBarsData: [ - LineChartBarData( - isCurved: true, - color: Colors.pink, - barWidth: 5, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData(show: false), - spots: const [ - FlSpot(0, 1), - FlSpot(1, 0), - FlSpot(2, 1), - FlSpot(3, 0), - FlSpot(4, 1), - FlSpot(5, 0), - FlSpot(6, 1), - ], - ), - LineChartBarData( - isCurved: true, - color: Colors.cyan, - barWidth: 5, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData(show: false), - spots: const [ - FlSpot(0, 0), - FlSpot(1, 1), - FlSpot(2, 0), - FlSpot(3, 1), - FlSpot(4, 0), - FlSpot(5, 1), - FlSpot(6, 0), - ], - ), - ], - minX: 0, - maxX: days.length - 1, - maxY: 1, - minY: 0, + left: const BorderSide(color: Colors.transparent), + right: const BorderSide(color: Colors.transparent), + top: const BorderSide(color: Colors.transparent), ), ), - ), + lineBarsData: [ + 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); + }) + ), + 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, + minY: 0, + )) + ) ], ), ); } -} + +} \ No newline at end of file diff --git a/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart b/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart new file mode 100644 index 00000000..823ca7c7 --- /dev/null +++ b/packages/sf_shared/lib/src/widgets/wallet_balance_block.dart @@ -0,0 +1,59 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class WalletBalanceBlock extends ConsumerWidget { + final double max; + final double value; + final double savings; + final double savingsPlan; + + const WalletBalanceBlock({ + super.key, + required this.max, + required this.value, + required this.savings, + this.savingsPlan = 30.0 + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + + return Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + child: Column( + spacing: 16, + children: [ + Row( + children: [ + Text( + "Wallet", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + Spacer(), + MoneyText( + text: "$max€ total", + size: 26, + secondarySize: 16, + color: theme.getColorFor(ThemeCode.textPrimary), + ), + ], + ), + Row(children: [ + Text("Objetivos de ahorro"), + 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)), + Center(child: Text("Disponible")), + ], + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index cccb07d7..47595022 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ environment: # - modules/notifications # - modules/dashboard_shell dev_dependencies: - melos: ^6.3.3 + melos: 6.3.3 dependencies: flutter_secure_storage: ^9.2.4 dependency_overrides: From 62ffc9ef7cfa9f59971b91ed0823f99f21400129 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Wed, 3 Dec 2025 15:28:10 +0100 Subject: [PATCH 2/5] - 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")), ], ), From a5dd644a622783d934406c6b239b24424329c5c7 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Wed, 3 Dec 2025 16:43:24 +0100 Subject: [PATCH 3/5] merge develop into components --- .../mobile_app/lib/navigation/app_router.dart | 2 +- .../src/login/presentation/login_screen.dart | 7 +- .../login/presentation/phone_code_screen.dart | 2 +- .../presentation/welcome_screen.dart | 162 +++++++++++------- .../presentation/new_password_screen.dart | 2 +- .../src/sign_up/account_created_screen.dart | 4 +- .../auth/lib/src/sign_up/signup_screen.dart | 1 + .../lib/src/presentation/deposit_screen.dart | 2 +- .../lib/src/presentation/home_screen.dart | 2 +- .../lib/src/presentation/wage_screen.dart | 2 +- .../lib/src/core/activity_list.dart | 5 +- packages/navigation/lib/app_routes.dart | 2 + 12 files changed, 121 insertions(+), 72 deletions(-) diff --git a/apps/mobile_app/lib/navigation/app_router.dart b/apps/mobile_app/lib/navigation/app_router.dart index cef2a591..78155c03 100644 --- a/apps/mobile_app/lib/navigation/app_router.dart +++ b/apps/mobile_app/lib/navigation/app_router.dart @@ -15,7 +15,7 @@ late final GoRouter appRouter; void configureAppRouter() { appRouter = GoRouter( navigatorKey: rootNavigatorKey, - initialLocation: AppRoutes.login, + initialLocation: AppRoutes.onboarding, debugLogDiagnostics: true, routes: [ GoRoute( diff --git a/modules/auth/lib/src/login/presentation/login_screen.dart b/modules/auth/lib/src/login/presentation/login_screen.dart index c184e9cf..3cf9cdfe 100644 --- a/modules/auth/lib/src/login/presentation/login_screen.dart +++ b/modules/auth/lib/src/login/presentation/login_screen.dart @@ -19,7 +19,7 @@ class _LoginScreenState extends ConsumerState{ bool passwordVisible = false; @override - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final theme = ref.watch(themePortProvider); bool passwordVisible = true; @@ -30,6 +30,7 @@ class _LoginScreenState extends ConsumerState{ children: [ Icon(Icons.check, color: theme.getColorFor(ThemeCode.buttonPrimary), size: 50), Text( + // context.translate(I18n.example) "¡Te damos la bienvenida!", textAlign: TextAlign.center, style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), @@ -59,7 +60,7 @@ class _LoginScreenState extends ConsumerState{ child: CustomTextButton( text: "¿Has olvidado la contraseña?", onPressed: () => - widget.navigationContract.pushTo('/recover_password'), + widget.navigationContract.pushTo(AppRoutes.recoverPassword), size: 16, )), ], @@ -67,7 +68,7 @@ class _LoginScreenState extends ConsumerState{ ], ), PrimaryButton( - onPressed: () => widget.navigationContract.goTo('/main/home'), + onPressed: () => widget.navigationContract.goTo(AppRoutes.dashboardHome), text: "Iniciar sesión", color: theme.getColorFor(ThemeCode.buttonPrimary) ), 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 c80878ca..9ee6c3e1 100644 --- a/modules/auth/lib/src/login/presentation/phone_code_screen.dart +++ b/modules/auth/lib/src/login/presentation/phone_code_screen.dart @@ -78,7 +78,7 @@ class PhoneCodeScreen extends ConsumerWidget { spacing: 24, children: [ PrimaryButton( - onPressed: () => {navigationContract.pushTo('/login')}, + onPressed: () => {navigationContract.pushTo(AppRoutes.login)}, text: "Entrar", color: theme.getColorFor(ThemeCode.buttonPrimary), ), diff --git a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart index 57c9d79c..41747d8b 100644 --- a/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart +++ b/modules/auth/lib/src/onboarding/presentation/welcome_screen.dart @@ -4,68 +4,114 @@ import 'package:flutter_svg/svg.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:navigation/navigation.dart'; -class WelcomeScreen extends ConsumerWidget { +class WelcomeScreen extends ConsumerStatefulWidget { final NavigationContract navigationContract; const WelcomeScreen({super.key, required this.navigationContract}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => WelcomeScreenState(navigationContract: navigationContract); + +} + +class WelcomeScreenState extends ConsumerState{ + late int currentStep; + final NavigationContract navigationContract; + + WelcomeScreenState({required this.navigationContract}); + + @override + void initState() { + super.initState(); + currentStep = 0; + } + + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + return Scaffold( - body: Center( - child: Column( - children: [ - Spacer(), - Expanded( - child: CarouselView( - scrollDirection: Axis.horizontal, - itemExtent: double.infinity, - itemSnapping: true, - shrinkExtent: 400, - children: generateSteps(), - ), - ), - FilledButton( - onPressed: () => navigationContract.goTo(AppRoutes.linkPhone), - child: const Text('Continuar'), - ), - Spacer(), - ], - ), - ), + backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), + body: Container( + padding: EdgeInsets.only(top: 24), + child: Center( + child: Column( + spacing: 48, + children: [ + Spacer(), + generateSteps()[currentStep], + Column( + spacing: 24, + children: [ + StepIndicator( + max: 3, + current: currentStep+1, + color: theme.getColorFor(ThemeCode.buttonSecondary) + ), + generateButtons(theme, 3, currentStep+1) + ] + ), + Spacer() + ] + ) + ) + ) ); } - void jumpToNext(BuildContext context) { - // Navigator.pushReplacement( - // context, - // MaterialPageRoute(builder: (_) => LinkPhoneScreen()), - // ); - return; + Widget generateButtons(ThemePort theme, int max, int step){ + if (step==max) { + return PrimaryButton( + onPressed: () => navigationContract.goTo(AppRoutes.linkPhone), + text: "Continuar", + color: theme.getColorFor(ThemeCode.buttonPrimary), + width: 324, + ); + } else { + return Column( + spacing: 16, + children: [ + PrimaryButton( + onPressed: ()=>setState(() { + currentStep++; + }), + text: "Siguiente", + color: theme.getColorFor(ThemeCode.buttonSecondary), + width: 324, + ), + CustomTextButton( + onPressed: ()=>navigationContract.goTo(AppRoutes.linkPhone), + text: "Omitir", + size: 18, + weight: FontWeight.w500, + ) + ], + ); + } } List generateSteps() { return [ Column( - spacing: 48, - children: [ - SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"), - Column( - spacing: 16, - children: [ - const Text( - "Aprende a gestionar su dinero", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) - ), - const Text( - "Tu peque crea hábitos y se divierte mientras lo hace", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18, letterSpacing: 0) - ) - ] - ) - ] + spacing: 48, + children: [ + SvgPicture.asset("assets/images/ui/bienvenida_paso1.svg"), + Column( + spacing: 16, + children: [ + Text( + "Aprende a gestionar su dinero", + textAlign: TextAlign.center, + 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, letterSpacing: 0) + ) + ] + ) + ] ), Column( spacing: 48, @@ -74,15 +120,15 @@ class WelcomeScreen extends ConsumerWidget { Column( spacing: 16, children: [ - const Text( - "Tranquilidad en cada pago que hace", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) + Text( + "Tranquilidad en cada pago que hace", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) ), - const Text( - "Supervisa sus gastos, fija límites y acompáñale en cada paso", - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18, letterSpacing: 0) + Text( + "Supervisa sus gastos, fija límites y acompáñale en cada paso", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, letterSpacing: 0) ), ], ) @@ -95,12 +141,12 @@ class WelcomeScreen extends ConsumerWidget { Column( spacing: 16, children: [ - const Text( + Text( "Pagos fáciles y seguros, en sus manos", textAlign: TextAlign.center, style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0) ), - const Text( + 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/new_password_screen.dart b/modules/auth/lib/src/recover_password/presentation/new_password_screen.dart index 05e38efc..866fe532 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 @@ -169,7 +169,7 @@ class NewPasswordScreenState extends ConsumerState { ], ), PrimaryButton( - onPressed: ()=>{navigationContract.goTo('/main/home')}, + onPressed: ()=>{navigationContract.goTo(AppRoutes.dashboardHome)}, text: "Aceptar", color: theme.getColorFor(ThemeCode.buttonPrimary) ), 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 52f47761..f2828367 100644 --- a/modules/auth/lib/src/sign_up/account_created_screen.dart +++ b/modules/auth/lib/src/sign_up/account_created_screen.dart @@ -72,9 +72,9 @@ class AccountCreatedScreen extends ConsumerWidget { PrimaryButton( onPressed: () => { if (kidAccount){ - navigationContract.goTo('/main/home') + navigationContract.goTo(AppRoutes.dashboardHome) } else { - navigationContract.pushTo('/device_signup') + navigationContract.pushTo(AppRoutes.deviceSignup) } }, text: "Continuar", diff --git a/modules/auth/lib/src/sign_up/signup_screen.dart b/modules/auth/lib/src/sign_up/signup_screen.dart index 035559b1..6de7de56 100644 --- a/modules/auth/lib/src/sign_up/signup_screen.dart +++ b/modules/auth/lib/src/sign_up/signup_screen.dart @@ -17,6 +17,7 @@ class SignupScreen extends ConsumerStatefulWidget { super.key, required this.navigationContract }); + @override ConsumerState createState() => _SignupScreenState(); } diff --git a/modules/home/lib/src/presentation/deposit_screen.dart b/modules/home/lib/src/presentation/deposit_screen.dart index 2b2c7da9..73dc9d60 100644 --- a/modules/home/lib/src/presentation/deposit_screen.dart +++ b/modules/home/lib/src/presentation/deposit_screen.dart @@ -135,7 +135,7 @@ class _DepositScreenState extends ConsumerState { CustomTextField( lines: 3, length: 150, - label: "Escribir mensaje a ${kid.name} del motivo del ingreso", + label: "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", hint: "Escribe tu mensaje", ), const Align( diff --git a/modules/home/lib/src/presentation/home_screen.dart b/modules/home/lib/src/presentation/home_screen.dart index b7f4b6f1..9197b9d1 100644 --- a/modules/home/lib/src/presentation/home_screen.dart +++ b/modules/home/lib/src/presentation/home_screen.dart @@ -53,7 +53,7 @@ class HomeScreen extends ConsumerWidget { Align( alignment: Alignment.topLeft, child: TextButton( - onPressed: () => navigationContract.pushTo('/device_signup'), + onPressed: () => navigationContract.pushTo(AppRoutes.deviceSignup), child: Text( "+ Añadir otro peque", style: TextStyle( diff --git a/modules/home/lib/src/presentation/wage_screen.dart b/modules/home/lib/src/presentation/wage_screen.dart index aed5a6d6..f4e0c69a 100644 --- a/modules/home/lib/src/presentation/wage_screen.dart +++ b/modules/home/lib/src/presentation/wage_screen.dart @@ -154,7 +154,7 @@ class _WageScreenState extends ConsumerState { lines: 3, length: 150, label: - "Escribir mensaje a ${kid.name} del motivo del ingreso", + "Escribir mensaje a ${widget.kid.name} del motivo del ingreso", hint: "Escribe tu mensaje", ), const Align( diff --git a/modules/notifications/lib/src/core/activity_list.dart b/modules/notifications/lib/src/core/activity_list.dart index 6ebf19da..2908f499 100644 --- a/modules/notifications/lib/src/core/activity_list.dart +++ b/modules/notifications/lib/src/core/activity_list.dart @@ -67,14 +67,13 @@ class ActivityListState extends ConsumerState { children: [ Icon(icons[type], color: color), const SizedBox(width: 8), - Text( + Expanded(child: Text( titles[type]!, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), - ), - const Spacer(), + )), const Text("14/01/2005"), ], ), diff --git a/packages/navigation/lib/app_routes.dart b/packages/navigation/lib/app_routes.dart index 58fe7cba..b441a68c 100644 --- a/packages/navigation/lib/app_routes.dart +++ b/packages/navigation/lib/app_routes.dart @@ -1,8 +1,10 @@ class AppRoutes { static const login = '/login'; + static const signup = '/signup'; static const onboarding = '/onboarding'; static const linkPhone = '/link_phone'; static const phoneCode = '/phone_code'; + static const deviceSignup = '/device_signup'; static const recoverPassword = '/recover_password'; static const dashboard = '/dashboard'; From ca7c3755c244e9eb5016e0d270a29ab1b211995e Mon Sep 17 00:00:00 2001 From: aitorarana Date: Wed, 3 Dec 2025 17:42:26 +0100 Subject: [PATCH 4/5] changed savings goal goal type to int --- modules/home/lib/src/presentation/goals_screen.dart | 4 ++-- packages/sf_shared/lib/src/models/savings_goal.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/home/lib/src/presentation/goals_screen.dart b/modules/home/lib/src/presentation/goals_screen.dart index 640c5e45..c025904b 100644 --- a/modules/home/lib/src/presentation/goals_screen.dart +++ b/modules/home/lib/src/presentation/goals_screen.dart @@ -12,7 +12,7 @@ class GoalsScreen extends ConsumerStatefulWidget{ 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)]; + ]), Task(rewardAmount: 10)]; final List savingsGoals; @override @@ -407,7 +407,7 @@ class SavingsBlockState extends ConsumerState{ spacing: 8, children: [ ProgressBar( - max: widget.savings[index].goal, + max: widget.savings[index].goal+0.0, value: widget.savings[index].saved, height: 64, textSize: 40, diff --git a/packages/sf_shared/lib/src/models/savings_goal.dart b/packages/sf_shared/lib/src/models/savings_goal.dart index b2baae86..18b4b372 100644 --- a/packages/sf_shared/lib/src/models/savings_goal.dart +++ b/packages/sf_shared/lib/src/models/savings_goal.dart @@ -1,7 +1,7 @@ class SavingsGoal{ final String name; - final double goal; + final int goal; final double saved; const SavingsGoal({ From 1b0517344ef404c6e12b28ffd871ef32eecd3292 Mon Sep 17 00:00:00 2001 From: aitorarana Date: Thu, 4 Dec 2025 09:43:05 +0100 Subject: [PATCH 5/5] merge develop into components - modify onboarding screen to use step indicator component --- .../presentation/onboarding_screen.dart | 7 ++-- .../widgets/onboarding_dots_indicator.dart | 35 ------------------- .../src/widgets/layouts/form_step_layout.dart | 2 +- .../lib/src/steps/step_indicator.dart | 27 ++++++++------ packages/design_system/test/widget_test.dart | 2 +- 5 files changed, 22 insertions(+), 51 deletions(-) delete mode 100644 modules/auth/lib/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart diff --git a/modules/auth/lib/src/onboarding/presentation/onboarding_screen.dart b/modules/auth/lib/src/onboarding/presentation/onboarding_screen.dart index f647159e..bef2e8f7 100644 --- a/modules/auth/lib/src/onboarding/presentation/onboarding_screen.dart +++ b/modules/auth/lib/src/onboarding/presentation/onboarding_screen.dart @@ -1,7 +1,7 @@ import 'package:auth/src/onboarding/domain/onboarding_page.dart'; import 'package:auth/src/onboarding/presentation/onboarding_view_model.dart'; import 'package:auth/src/onboarding/presentation/widgets/onboarding_content.dart'; -import 'package:auth/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart'; +import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -62,9 +62,10 @@ class OnboardingScreen extends ConsumerWidget { }, ), ), - OnboardingDotsIndicator( - currentIndex: state.cardIndex, + StepIndicator( + current: state.cardIndex, total: onboardingPages.length, + color: const Color(0xFF4A4A4A), ), const SizedBox(height: 38), Container( diff --git a/modules/auth/lib/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart b/modules/auth/lib/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart deleted file mode 100644 index 2d6adbf9..00000000 --- a/modules/auth/lib/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; - -class OnboardingDotsIndicator extends StatelessWidget { - final int currentIndex; - final int total; - - const OnboardingDotsIndicator({ - super.key, - required this.currentIndex, - required this.total, - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(total, (index) { - final bool isActive = index == currentIndex; - - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - curve: Curves.easeOut, - margin: const EdgeInsets.symmetric(horizontal: 6), - width: 16.0, - height: 16.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isActive ? const Color(0xFF4A4A4A) : Colors.white, - border: Border.all(color: const Color(0xFF4A4A4A), width: 2), - ), - ); - }), - ); - } -} 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 65f03b54..d87d2dda 100644 --- a/modules/auth/lib/src/widgets/layouts/form_step_layout.dart +++ b/modules/auth/lib/src/widgets/layouts/form_step_layout.dart @@ -42,7 +42,7 @@ class FormStepLayout extends ConsumerWidget{ spacing: 24, children: [ StepIndicator( - max: numSteps, + total: numSteps, current: currentStep, color: theme.getColorFor(ThemeCode.buttonPrimary) ), diff --git a/packages/design_system/lib/src/steps/step_indicator.dart b/packages/design_system/lib/src/steps/step_indicator.dart index 9123a50a..0f4b78ec 100644 --- a/packages/design_system/lib/src/steps/step_indicator.dart +++ b/packages/design_system/lib/src/steps/step_indicator.dart @@ -1,28 +1,33 @@ import 'package:flutter/material.dart'; class StepIndicator extends StatelessWidget{ - final int max; + final int total; final int current; final Color color; - const StepIndicator({super.key, required this.max, required this.current, required this.color}); + const StepIndicator({super.key, required this.total, required this.current, required this.color}); @override Widget build(BuildContext context) { return Row( - spacing: 12, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Spacer(), - ...List.generate(max, (int index){ - return DecoratedBox( - decoration: ShapeDecoration( - shape: CircleBorder(side: BorderSide(color: color)), - color: (index < current)? color: Colors.transparent + ...List.generate(total, (index){ + final bool isActive = index < current; + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + margin: const EdgeInsets.symmetric(horizontal: 6), + width: 16.0, + height: 16.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isActive ? color : Colors.white, + border: Border.all(color: color, width: 2), ), - child: SizedBox(width: 16, height: 16), ); }), - Spacer() ] ); } diff --git a/packages/design_system/test/widget_test.dart b/packages/design_system/test/widget_test.dart index 8b29fdd8..17517e9d 100644 --- a/packages/design_system/test/widget_test.dart +++ b/packages/design_system/test/widget_test.dart @@ -7,7 +7,7 @@ void main() { themePackages(); testGoldens('Step Indicator', (tester) async { - final widget = (int step)=>StepIndicator(max: 3, current: step, color: Colors.blueAccent); + final widget = (int step)=>StepIndicator(total: 3, current: step, color: Colors.blueAccent); final builder = GoldenBuilder.column() ..addScenario('step -1', widget(-1)) ..addScenario('step 0', widget(0))