- 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.
This commit is contained in:
2025-11-21 15:28:46 +01:00
parent 4225f7510b
commit 8201bff0a7
56 changed files with 1991 additions and 1491 deletions

View File

@@ -21,6 +21,8 @@ class PlatformApp extends ConsumerWidget {
return MaterialApp.router(
title: 'SaveFamily',
theme: ThemeData(
fontFamily: 'Stolzl',
package: 'fonts',
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF329E95)),
),
routerConfig: appRouter,

View File

@@ -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);

View File

@@ -51,6 +51,8 @@ dependencies:
path: ../../packages/navigation
design_system:
path: ../../packages/design_system
fonts:
path: ../../packages/fonts
#dependencies go here
cupertino_icons: ^1.0.8

View File

@@ -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';

View File

@@ -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!"),
),
),

View File

@@ -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(

View File

@@ -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<void> buildPage(BuildContext context, GoRouterState state) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
return MaterialPage<void>(
key: state.pageKey,
child: DeviceSignupScreen(navigationContract: navigationContract),
);
}
}

View File

@@ -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<DeviceSignupScreen> createState() => DeviceSignupScreenState(navigationContract);
}
class DeviceSignupScreenState extends ConsumerState<DeviceSignupScreen>{
late int currentStep;
final NavigationContract navigationContract;
DeviceSignupScreenState(this.navigationContract);
@override
void initState() {
currentStep = 0;
}
@override
Widget build(BuildContext context) {
return getSteps()[currentStep];
}
List<Widget> 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)
];
}
}

View File

@@ -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"),
),
],
),
),
],
),
),
),
);
}
}

View File

@@ -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<Color>(
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)),
),
),
),
],
);
}
}

View File

@@ -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"),
]
)
],
),
],
);
}
}

View File

@@ -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<LinkWatchScreen> createState() => LinkWatchScreenState();
}
class LinkWatchScreenState extends ConsumerState<LinkWatchScreen>{
@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<Color>(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"))
],
);
}
}

View File

@@ -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)
],
),
),
),
),
);
));
}
}

View File

@@ -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: () =>

View File

@@ -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<PhoneCodeScreen> {
final focusNodes = List<FocusNode>.generate(6, (int i) {

View File

@@ -1,73 +1,133 @@
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<ConsumerStatefulWidget> 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<Color>(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<Color>(theme.getColorFor(ThemeCode.buttonSecondary))
),
onPressed: ()=>setState(() {
currentStep++;
}),
child: Expanded(child: Center( child: Text("Siguiente")))
),
TextButton(
onPressed: ()=>navigationContract.goTo('/link_phone'),
child: Text("Omitir")
)
],
);
}
}
List<Widget> 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)),
],
),
];

View File

@@ -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(

View File

@@ -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<NewPasswordScreen> {
void initState() {
passwordVisible = false;
equalPasswords = false;
String password = "";
password = "";
securityChecks = {
"min": false,
"capital": false,
@@ -36,6 +38,7 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
@override
Widget build(BuildContext context) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
final theme = ref.watch(themePortProvider);
return Scaffold(
@@ -50,25 +53,10 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
"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<NewPasswordScreen> {
}),
},
),
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<NewPasswordScreen> {
),
Spacer(flex: 1),
FilledButton(
onPressed: () => {},
onPressed: ()=>{navigationContract.pushTo('/main/home')},
child: Container(
width: double.infinity,
padding: EdgeInsets.all(20),

View File

@@ -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,

View File

@@ -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"),
),

View File

@@ -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<DropdownMenuEntry>.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"),
],
);
}

View File

@@ -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<DropdownMenuEntry>.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"),
],
);
}

View File

@@ -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<SignupScreen> createState() => SignupScreenState();
}
class SignupScreenState extends ConsumerState<SignupScreen> {
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: <Widget>[
Expanded(
child: OutlinedButton(
onPressed: controls.onStepCancel,
child: const Text('Atrás'),
),
),
Expanded(
child: FilledButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(
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<Step> getSteps() {
return <Step>[
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<Widget> 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<NavigationContract>().pushTo('/device_signup')},
previousStep: ()=>{setState(() {
currentStep--;
})},
),
];
}

View File

@@ -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<SignupUserScreen>{
@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<DropdownMenuEntry>.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: "*******"
),
],
);

View File

@@ -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<Widget> body;
final List<Widget>? 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<Color>(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<Color>(theme.getColorFor(ThemeCode.buttonSecondary))),
child: Expanded(child: Center(child: Text("Siguiente")))
))
]
);
}
}
}

View File

@@ -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,7 +174,7 @@ class DepositScreen extends ConsumerWidget {
},
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
if (program) TextField(),
if (program) CustomTextField(),
],
),
),

View File

@@ -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<Kid> 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<NavigationContract>();
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<Kid> kids, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Column(
spacing: 20,
children: List<Widget>.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<Color>(
theme.getColorFor(ThemeCode.buttonSecondary),
),
),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 0,
vertical: 10,
),
child: Text("+ Añadir dinero"),
),
),
],
),
],
),
),
),
);
return WalletItem(context, kids[index], index);
}),
);
}

View File

@@ -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<Widget>.generate(activity.length, (int index) {
return Column(
spacing: 20,
children: [
Text(activity[index]["date"].toString()),
Column(
spacing: 15,
children: List<Widget>.generate(
(activity[index]["payments"] as List<Object>).length,
(int i) {
//var a = (activity[index]["payments"] as List<Object>)[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<Widget>.generate(activity.length, (int index) {
return Column(
spacing: 16,
children: [
Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18)),
Column(
spacing: 16,
children: List<Widget>.generate(
(activity[index]["payments"] as List<Object>).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")),
]
)
);
}
}

View File

@@ -103,7 +103,7 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
),
],
),
if (dailyLimits[index]["edit"]) TextField(),
if (dailyLimits[index]["edit"]) CustomTextField(),
],
);
}),
@@ -144,7 +144,7 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
),
],
),
if (timeLimits[index]["edit"]) TextField(),
if (timeLimits[index]["edit"]) CustomTextField(),
],
);
}),

View File

@@ -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,

View File

@@ -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<void>(
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>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
)
),
backgroundColor: WidgetStatePropertyAll<Color>(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<double>.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;
}

View File

@@ -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),

View File

@@ -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

View File

@@ -25,82 +25,65 @@ class ActivityListState extends ConsumerState<ActivityList> {
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<Widget>.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;
}
}),
})
);
}
}

View File

@@ -32,7 +32,7 @@ class ActivityScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Mes")),
],
),
SizedBox(height: 200, child: LineGraph()),
LineGraph(),
ActivityList(activity: activity, edit: false),
];

View File

@@ -47,7 +47,11 @@ class AlertScreenState extends ConsumerState<AlertScreen> {
),
],
),
ActivityList(activity: activity, edit: edit),
Expanded(
child: SingleChildScrollView(
child: ActivityList(activity: activity, edit: edit)
)
)
],
),
),

View File

@@ -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<LineBarSpot> 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<FlSpot>.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)
]
)
]
)
);
}
}

View File

@@ -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<Widget>.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),
];

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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';

View File

@@ -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<String>? onChanged;
final int? length;
CustomTextField({
super.key,
this.showPassword,
this.numeric = false,
this.hint = '',
this.label = '',
this.lines,
this.length,
this.onChanged,
});
@override
ConsumerState<CustomTextField> createState() => CustomTextFieldState();
}
class CustomTextFieldState extends ConsumerState<CustomTextField>{
@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 ?? (_)=>{},
);
}
}

View File

@@ -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,
),
)
),
),
]
);
}
}

View File

@@ -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)))
],
),
);
}
}

View File

@@ -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<Widget>.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()
]
);
}
}

View File

@@ -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,
)
)
]
));
}
}

View File

@@ -25,6 +25,7 @@ enum ThemeCode {
abstract class ThemePort {
late Map<ThemeCode, Color> theme;
late List<List<Color>> cardColors;
late List<Color> disabledCardColors;
Color getColorFor(ThemeCode code) {
Color? c = theme[code];
@@ -37,4 +38,8 @@ abstract class ThemePort {
List<Color> getCardColorFor(int index) {
return cardColors[index % cardColors.length];
}
List<Color> getDisabledCardColors() {
return disabledCardColors;
}
}

View File

@@ -25,4 +25,11 @@ class ThemeSfAdapter extends ThemePort {
@override
List<List<Color>> get cardColors => _cardColors;
final List<Color> _disabledCardColors = [
Color(0xFF989797), Color(0xFF797676), Color(0xFF5F5A5A)
];
@override
List<Color> get disabledCardColors => _disabledCardColors;
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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';

View File

@@ -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,
});
}

View File

@@ -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<Color>(
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<Color>(
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"),
),
)
],
),
);

View File

@@ -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<LineGraph> {
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<LineGraph> {
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<LineBarSpot> 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<LineBarSpot> 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<FlSpot>.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<FlSpot>.generate(days.length, (int index){
return FlSpot(index.toDouble(), index%2);
})
),
],
minX: 0,
maxX: days.length-1,
maxY: 1,
minY: 0,
))
)
],
),
);
}
}

View File

@@ -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")),
],
),
);
}
}

View File

@@ -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: