components #2

Merged
Raul merged 7 commits from components into develop 2025-12-04 08:45:44 +00:00
73 changed files with 4645 additions and 2080 deletions

View File

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

View File

@@ -23,6 +23,11 @@ void configureAppRouter() {
name: 'login',
pageBuilder: LoginBuilder().buildPage,
),
GoRoute(
path: AppRoutes.signup,
name: 'signup',
pageBuilder: SignupBuilder().buildPage,
),
GoRoute(
path: AppRoutes.onboarding,
name: 'onboarding',
@@ -43,7 +48,11 @@ void configureAppRouter() {
name: 'recover_password',
pageBuilder: RecoverPasswordBuilder().buildPage,
),
GoRoute(
path: AppRoutes.deviceSignup,
name: 'device_signup',
pageBuilder: DeviceSignupBuilder().buildPage,
),
StatefulShellRoute.indexedStack(
builder: (context, state, navShell) {
return DashboardBuilder().build(context, navShell);

View File

@@ -54,7 +54,9 @@ dependencies:
design_system:
path: ../../packages/design_system
sf_localizations:
path: ../../packages/sf_localizations
path: ../../packages/sf_localizations
fonts:
path: ../../packages/fonts
#dependencies go here
cupertino_icons: ^1.0.8

View File

@@ -1,28 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
//await tester.pumpWidget(const PaymentsApp(di: di));
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

View File

@@ -4,3 +4,5 @@ export 'src/login/link_phone_builder.dart';
export 'src/login/phone_code_builder.dart';
export 'src/login/login_builder.dart';
export 'src/recover_password/recover_password_builder.dart';
export 'src/device_sign_up/device_signup_builder.dart';
export 'src/sign_up/signup_builder.dart';

View File

@@ -1,28 +1,60 @@
import 'package:auth/src/device_sign_up/link_watch/create_profile_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AddKidScreen extends StatelessWidget {
const AddKidScreen({super.key});
class AddKidScreen extends ConsumerWidget {
final nextStep;
const AddKidScreen({super.key, required this.nextStep});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: EdgeInsets.all(30),
child: Column(
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",
textAlign: TextAlign.center,
),
Container(
margin: EdgeInsets.symmetric(vertical: 30, horizontal: 50),
child: Row(
spacing: 10,
children: [
Column(children: [Text("1"), Text("2"), Text("3")]),
Stack(
children: [
Column(
spacing: 16,
children: List<Widget>.generate(3, (int index) =>
Container(
decoration: ShapeDecoration(
shape: CircleBorder(),
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
width: 32,
height: 32,
child: Center(child: Text(
(index + 1).toString(),
style: TextStyle(
color: theme.getColorFor(ThemeCode.backgroundPrimary)
)
))
)
)
),
Divider(color: Colors.red, thickness: 4,),
],
),
Column(
spacing: 16,
children: [
Text("Crea su perfil"),
Text("Vincula su correa y su reloj"),
@@ -32,21 +64,28 @@ class AddKidScreen extends StatelessWidget {
],
),
),
Text("¡Y todo listo para que tenga su dinero!"),
Text("Recuerda que necesitas tener un Plan SaveFamily"),
Text(
"¡Y todo listo para que tenga su dinero!",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
letterSpacing: 0
)
),
Text(
"Recuerda que necesitas tener un Plan SaveFamily",
textAlign: TextAlign.center
),
Text(
"Si aún no lo tienes, puedes conseguirlo a través de nuestra web",
textAlign: TextAlign.center,
),
Spacer(flex: 8),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => CreateProfileScreen()),
),
child: Text("¡Empezar!"),
),
PrimaryButton(
onPressed: nextStep,
text: "¡Empezar!",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
],
),

View File

@@ -1,11 +1,16 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ContactScreen extends StatelessWidget {
class ContactScreen extends ConsumerWidget {
const ContactScreen({super.key});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: EdgeInsets.all(30),
child: Center(
@@ -36,32 +41,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,99 @@
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(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: PrimaryButton(
onPressed: ()=>{setState(() {
currentStep++;
})},
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonPrimary)
)
);
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,84 @@
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",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0),
),
),
CustomTextField(
label: "Nombre",
hint: "Nombre",
),
CustomTextField(
label: "Apellidos",
hint: "Apellidos",
),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
"Fecha de nacimiento",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
Row(
spacing: 10,
children: [
Expanded(
child: CustomTextField(
numeric: true,
hint: "DD",
length: 2,
),
),
Expanded(
child: CustomTextField(
numeric: true,
hint: "MM",
length: 2,
),
),
Expanded(
child: CustomTextField(
numeric: true,
hint: "AAAA",
length: 4,
),
),
],
),
],
),
CustomTextField(
label: "Dirección completa",
hint: "Nombre de la calle",
),
Align(
alignment: Alignment.topLeft,
child: CustomTextButton(
onPressed: () => {},
text: "Cambiar dirección",
size: 18,
weight: FontWeight.w500,
),
)
],
);
}
}

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,128 @@
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: [
Stack(
children: [
Divider(
color: theme.getColorFor(ThemeCode.buttonPrimary),
thickness: 4,
indent: 93,
endIndent: 93,
height: 48,
),
if (widget.step==1)Divider(
color: theme.getColorFor(ThemeCode.backgroundSecondary),
thickness: 4,
indent: 186,
endIndent: 93,
height: 48,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 69),
child: Row(
children: [
Container(
decoration: ShapeDecoration(
shape: CircleBorder(),
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
width: 48,
height: 48,
child: Center(child: Text(
"1",
style: TextStyle(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
fontSize: 24
)
))
),
Spacer(),
Container(
decoration: ShapeDecoration(
shape: CircleBorder(),
color: theme.getColorFor(widget.step==1 ? ThemeCode.backgroundSecondary : ThemeCode.buttonPrimary)
),
width: 48,
height: 48,
child: Center(child: Text(
"2",
style: TextStyle(
color: theme.getColorFor(widget.step==1 ? ThemeCode.textPrimary : ThemeCode.backgroundSecondary),
fontSize: 24
)
))
),
],
),
)
],
),
Row(children: [
Spacer(),
Text("Escanea la correa"),
Spacer(),
Text("Escanea el reloj"),
Spacer(),
]),
Container(
padding: EdgeInsets.all(40),
decoration: BoxDecoration(
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)Column(
spacing: 16,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text("O inserta el código del reloj"),
),
Row(
spacing: 16,
children: [
Expanded(child: CustomTextField(
hint: "XXXXXXXXXX",
)),
Expanded(child: PrimaryButton(
onPressed: ()=>{},
text: "Continuar con código",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
))
],
),
],
),
Column(
spacing: 8,
children: [
Text("Si no consigues vincular su correa o reloj"),
CustomTextButton(onPressed: ()=>{}, text: "Contáctanos", weight: FontWeight.w500, size: 18)
],
)
],
);
}
}

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,68 +10,69 @@ class LinkPhoneScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
TextEditingController phoneController = TextEditingController();
final theme = ref.watch(themePortProvider);
// TextEditingController phoneController = TextEditingController();
// String? phone;
return Scaffold(
body: Container(
margin: EdgeInsets.all(30),
child: Expanded(
child: Center(
child: Column(
spacing: 10,
children: [
Text(
"¡Nos alegra mucho tenerte por aquí!",
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
Text(
"Para poder entrar de forma segura, te vamos a enviar un código al teléfono",
),
Row(
spacing: 10,
children: [
DropdownMenu(
initialSelection: "es",
dropdownMenuEntries: List<DropdownMenuEntry>.generate(3, (
int index,
) {
return DropdownMenuEntry(
labelWidget: Icon(Icons.outlined_flag),
label: "es",
value: "es",
);
}),
),
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,
),
),
],
),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () =>
navigationContract.pushTo(AppRoutes.phoneCode),
child: Text("Siguiente"),
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: SafeArea(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 24),
child: Expanded(
child: Center(
child: Column(
spacing: 48,
children: [
Spacer(flex: 8),
Text(
"¡Nos alegra mucho tenerte por aquí!",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.w500, letterSpacing: 0),
),
),
],
Text(
"Para poder entrar de forma segura, te vamos a enviar un código al teléfono",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.bottomLeft, child: Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)),
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
width: 80,
),
Expanded(
child: CustomTextField(
hint: "Teléfono",
numeric: true
)
),
],
),
],
),
PrimaryButton(
onPressed: () => navigationContract.pushTo(AppRoutes.phoneCode),
text: "Siguiente",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
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';
@@ -11,97 +12,149 @@ class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key, required this.navigationContract});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
ConsumerState<ConsumerStatefulWidget> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
class _LoginScreenState extends ConsumerState<LoginScreen>{
bool passwordVisible = false;
@override
Widget build(BuildContext context) {
// final l10n = SfLocalizations.of(context);
final theme = ref.watch(themePortProvider);
return Scaffold(
body: Center(
child: Container(
margin: const EdgeInsets.all(30),
child: Column(
spacing: 10,
bool passwordVisible = true;
final content = [
Column(
spacing: 8,
children: [
Icon(Icons.check, color: theme.getColorFor(ThemeCode.buttonPrimary), size: 50),
Text(
// context.translate(I18n.example)
"¡Te damos la bienvenida!",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
],
),
Column(
spacing: 32,
children: [
Column(
spacing: 24,
children: [
const Icon(Icons.check, color: Color(0xFF329e95), size: 50),
const Text(
// context.translate(I18n.example)
'¡Te damos la bienvenida!',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
CustomTextField(
hint: "Nombre de usuario",
label: "Nombre de usuario",
),
const TextField(
decoration: InputDecoration(
hintText: 'Nombre de usuario',
labelText: 'Nombre de usuario',
border: OutlineInputBorder(),
),
),
TextField(
obscureText: !passwordVisible,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Contraseña',
hintText: '********',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
),
TextButton(
onPressed: () =>
widget.navigationContract.pushTo(AppRoutes.recoverPassword),
child: const Text('¿Has olvidado la contraseña?'),
),
FilledButton(
onPressed: () =>
widget.navigationContract.pushTo(AppRoutes.dashboardHome),
child: const Text('Iniciar sesión'),
),
const Stack(children: [Divider(), Text('o continúa con')]),
Row(
spacing: 20,
Column(
spacing: 12,
children: [
OutlinedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const LoadingGoogleScreen(),
),
),
child: const Text('Google', semanticsLabel: 'Google'),
),
OutlinedButton(
onPressed: () {},
child: const Icon(Icons.apple, semanticLabel: 'Apple'),
CustomTextField(
showPassword: passwordVisible,
label: "Contraseña",
hint: "********"
),
Align(
alignment: Alignment.topLeft,
child: CustomTextButton(
text: "¿Has olvidado la contraseña?",
onPressed: () =>
widget.navigationContract.pushTo(AppRoutes.recoverPassword),
size: 16,
)),
],
),
const Text('¿No tienes cuenta?'),
TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SignupScreen()),
),
child: const Text('Crear una ahora'),
),
)
],
),
),
PrimaryButton(
onPressed: () => widget.navigationContract.goTo(AppRoutes.dashboardHome),
text: "Iniciar sesión",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
Container(
padding: EdgeInsets.only(top: 24),
child: Column(
spacing: 24,
children: [
Stack(children: [
Divider(endIndent: 74, indent: 74),
Align(
alignment: Alignment.center,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
child: Text("o continúa con"),
)
)
]),
Row(
spacing: 20,
children: [
Spacer(),
SecondaryButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => LoadingGoogleScreen(),
),
),
radius: 16,
padding: 44,
text: "Google",
label: "Google",
),
SecondaryButton(
onPressed: ()=>{},
radius: 16,
padding: 44,
icon: Icons.apple,
label: "Apple",
),
Spacer(),
],
),
],
),
),
Column(
spacing: 8,
children: [
Text(
"¿No tienes cuenta?",
style: TextStyle(fontSize: 18, letterSpacing: 0)
),
TextButton(
onPressed: () => widget.navigationContract.goTo(AppRoutes.signup),
child: Text(
"Crear una ahora",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
)
),
],
)
],
),
];
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Expanded(
child: Center(
child: Container(
margin: EdgeInsets.all(30),
child: ListView.separated(
itemBuilder: (BuildContext context, int index) {
return content[index];
},
separatorBuilder: (BuildContext context, int index) {
return Divider(color: Colors.transparent, height: 48);
},
itemCount: content.length,
),
),
)
)
);
}
}

View File

@@ -1,15 +1,14 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:navigation/navigation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 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) {
@@ -18,64 +17,87 @@ class PhoneCodeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: EdgeInsets.all(30),
child: Expanded(
child: Center(
child: Column(
spacing: 15,
spacing: 48,
children: [
Spacer(flex: 8),
Text(
"Conéctate",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
Text.rich(
TextSpan(
text: "Hemos enviado el código al ",
children: [
Column(
spacing: 24,
children: [
Text(
"Conéctate",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
Text.rich(
TextSpan(
// text: widget.phone,
style: TextStyle(fontWeight: FontWeight.bold),
text: "Hemos enviado el código al ",
children: [
TextSpan(
// text: widget.phone,
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
],
),
),
Text("Introduce el código aquí"),
Row(
spacing: 8,
children: List<Widget>.generate(6, (int i) {
return Expanded(
child: TextField(
focusNode: focusNodes[i],
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: "0",
counterText: "",
border: OutlineInputBorder(),
),
maxLength: 1,
onChanged: (String value) => {
value != ""
? focusNodes[i + 1].requestFocus()
: focusNodes[i - 1].requestFocus(),
},
),
);
}),
),
],
),
Text("Introduce el código aquí"),
Row(
spacing: 20,
children: List<Widget>.generate(6, (int i) {
return Expanded(
child: TextField(
focusNode: focusNodes[i],
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "0",
counterText: "",
border: OutlineInputBorder(),
Column(
spacing: 24,
children: [
PrimaryButton(
onPressed: () => {navigationContract.pushTo(AppRoutes.login)},
text: "Entrar",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Column(
spacing: 8,
children: [
Text(
"¿No lo has recibido?",
style: TextStyle(fontSize: 18, letterSpacing: 0, height: 1.5),
),
maxLength: 1,
onChanged: (String value) => {
value != ""
? focusNodes[i + 1].requestFocus()
: focusNodes[i - 1].requestFocus(),
},
),
);
}),
),
FilledButton(
onPressed: () => {navigationContract.pushTo(AppRoutes.login)},
child: Text("Entrar"),
),
Text("¿No lo has recibido?"),
TextButton(
onPressed: () => {},
child: Text(
"Volver a intentarlo",
style: TextStyle(fontWeight: FontWeight.bold),
),
CustomTextButton(
onPressed: () => {},
text: "Volver a intentarlo",
size: 18,
weight: FontWeight.w500,
),
],
)
],
),
Spacer(flex: 10),
],

View File

@@ -1,7 +1,7 @@
import 'package:auth/src/onboarding/domain/onboarding_page.dart';
import 'package:auth/src/onboarding/presentation/onboarding_view_model.dart';
import 'package:auth/src/onboarding/presentation/widgets/onboarding_content.dart';
import 'package:auth/src/onboarding/presentation/widgets/onboarding_dots_indicator.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -62,9 +62,10 @@ class OnboardingScreen extends ConsumerWidget {
},
),
),
OnboardingDotsIndicator(
currentIndex: state.cardIndex,
StepIndicator(
current: state.cardIndex,
total: onboardingPages.length,
color: const Color(0xFF4A4A4A),
),
const SizedBox(height: 38),
Container(

View File

@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
class OnboardingDotsIndicator extends StatelessWidget {
final int currentIndex;
final int total;
const OnboardingDotsIndicator({
super.key,
required this.currentIndex,
required this.total,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(total, (index) {
final bool isActive = index == currentIndex;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
margin: const EdgeInsets.symmetric(horizontal: 6),
width: 16.0,
height: 16.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? const Color(0xFF4A4A4A) : Colors.white,
border: Border.all(color: const Color(0xFF4A4A4A), width: 2),
),
);
}),
);
}
}

View File

@@ -1,83 +0,0 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class EmailSentScreen extends ConsumerWidget {
final String email;
const EmailSentScreen({super.key, required this.email});
@override
Widget build(BuildContext contex, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Scaffold(
body: Container(
margin: EdgeInsets.all(30),
child: Center(
child: Column(
spacing: 20,
children: [
Spacer(flex: 8),
Text(
"Recuperar contraseña",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
Spacer(flex: 1),
Row(
spacing: 10,
children: [
Icon(
Icons.check,
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Text(
"Correo enviado correctamente",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
Spacer(flex: 1),
Text(
"Revisa tu email y haz clic en el enlace para crear una nueva contraseña",
),
Text(
"Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"",
),
Row(
spacing: 10,
children: [
Expanded(
child: OutlinedButton(
onPressed: () => {},
child: Text("Reenviar correo"),
),
),
Expanded(
child: FilledButton(
onPressed: () => {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (_) => NewPasswordScreen(),
// ),
// ),
},
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(
theme.getColorFor(ThemeCode.buttonSecondary),
),
),
child: Text("Continuar"),
),
),
],
),
Spacer(flex: 10),
],
),
),
),
);
}
}

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});
@@ -37,141 +39,141 @@ class NewPasswordScreenState extends ConsumerState<NewPasswordScreen> {
@override
Widget build(BuildContext context) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: const EdgeInsets.all(30),
margin: const EdgeInsets.all(24),
child: Center(
child: Column(
spacing: 10,
spacing: 48,
children: [
const Spacer(flex: 4),
const Text(
'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: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
onChanged: (value) {
setState(() {
password = value;
securityChecks = checkSecurity(value);
});
},
),
TextField(
obscureText: !passwordVisible,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Repetir contraseña',
hintText: '********',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
onChanged: (value) {
setState(() {
equalPasswords = password == value;
});
},
),
Row(
const Spacer(),
Column(
spacing: 32,
children: [
Icon(
securityChecks['min']!
? Icons.check
: Icons.cancel_outlined,
color: securityChecks['min']!
? theme.getColorFor(ThemeCode.buttonPrimary)
: theme.getColorFor(ThemeCode.buttonSecondary),
const Text(
"Recuperar contraseña",
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0),
),
const SizedBox(width: 8),
const Text('Al menos 8 caracteres'),
Column(
spacing: 16,
children: [
CustomTextField(
showPassword: passwordVisible,
label: "Nueva contraseña",
hint: "********",
onChanged: (value) => {
setState(() {
password = value;
securityChecks = checkSecurity(value);
}),
},
),
CustomTextField(
showPassword: passwordVisible,
label: "Repetir contraseña",
hint: "********",
onChanged: (value) => {
setState(() {
equalPasswords = password == value;
}),
},
),
Column(
spacing: 4,
children: [
Row(
spacing: 8,
children: [
Icon(
Icons.check,
color: theme.getColorFor(securityChecks["min"]!
? ThemeCode.buttonPrimary
: ThemeCode.buttonSecondary),
),
const Text("Al menos 8 caracteres", style: TextStyle(fontSize: 14)),
],
),
Row(
spacing: 8,
children: [
Icon(
Icons.check,
color: theme.getColorFor(securityChecks["capital"]!
? ThemeCode.buttonPrimary
: ThemeCode.buttonSecondary),
),
const Text("Una mayúscula", style: TextStyle(fontSize: 14)),
],
),
Row(
spacing: 8,
children: [
Icon(
Icons.check,
color: theme.getColorFor(securityChecks["number"]!
? ThemeCode.buttonPrimary
: ThemeCode.buttonSecondary),
),
const Text("Un número", style: TextStyle(fontSize: 14)),
],
),
Row(
spacing: 8,
children: [
Icon(
Icons.check,
color: theme.getColorFor(securityChecks["special"]!
? ThemeCode.buttonPrimary
: ThemeCode.buttonSecondary),
),
const Text("Un carácter especial", style: TextStyle(fontSize: 14)),
],
),
],
)
],
),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: const Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
),
Row(
spacing: 8,
children: [
CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
width: 80,
),
Expanded(child: CustomTextField(
hint: "Teléfono",
numeric: true
))
]
),
],
)
],
),
Row(
children: [
Icon(
securityChecks['capital']!
? Icons.check
: Icons.cancel_outlined,
color: securityChecks['capital']!
? theme.getColorFor(ThemeCode.buttonPrimary)
: theme.getColorFor(ThemeCode.buttonSecondary),
),
const SizedBox(width: 8),
const Text('Una mayúscula'),
],
PrimaryButton(
onPressed: ()=>{navigationContract.goTo(AppRoutes.dashboardHome)},
text: "Aceptar",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
Row(
children: [
Icon(
securityChecks['number']!
? Icons.check
: Icons.cancel_outlined,
color: securityChecks['number']!
? theme.getColorFor(ThemeCode.buttonPrimary)
: theme.getColorFor(ThemeCode.buttonSecondary),
),
const SizedBox(width: 8),
const Text('Un número'),
],
),
Row(
children: [
Icon(
securityChecks['special']!
? Icons.check
: Icons.cancel_outlined,
color: securityChecks['special']!
? theme.getColorFor(ThemeCode.buttonPrimary)
: theme.getColorFor(ThemeCode.buttonSecondary),
),
const SizedBox(width: 8),
const Text('Un carácter especial'),
],
),
const Spacer(flex: 1),
FilledButton(
onPressed:
equalPasswords &&
securityChecks.values.every((check) => check)
? () {}
: null,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
child: const Center(child: Text('Aceptar')),
),
),
const Spacer(flex: 4),
const Spacer(),
],
),
),

View File

@@ -1,4 +1,4 @@
import 'package:auth/src/recover_password/presentation/email_sent_screen.dart';
import 'package:auth/src/recover_password/presentation/sent_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:navigation/navigation.dart';
@@ -14,53 +14,85 @@ class RestorePasswordScreen extends ConsumerWidget {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: EdgeInsets.all(30),
child: Center(
child: Column(
spacing: 30,
spacing: 48,
children: [
Spacer(flex: 8),
Text(
"Recuperar contaseña",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
Text(
"Introduce tu email para enviarte un enlace de recuperación",
),
TextField(
decoration: InputDecoration(
labelText: "Correo electrónico",
hintText: "Correo electrónico",
border: OutlineInputBorder(),
),
),
Row(
spacing: 20,
Column(
spacing: 32,
children: [
Expanded(
child: OutlinedButton(
onPressed: () => {Navigator.pop(context)},
child: Text("Volver"),
),
Text(
"Recuperar contaseña",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
Expanded(
child: FilledButton(
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EmailSentScreen(email: ""),
),
),
},
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(
theme.getColorFor(ThemeCode.buttonSecondary),
),
Text(
"Introduce tu email para enviarte un enlace de recuperación",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0),
),
],
),
Column(
spacing: 40,
children: [
CustomTextField(
label: "Correo electrónico",
hint: "Correo electrónico",
),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
"Teléfono móvil",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
),
child: Text("Enviar"),
),
Row(
spacing: 10,
children: [
CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
width: 80,
),
Expanded(
child: CustomTextField(
hint: "Teléfono",
numeric: true
)
),
],
),
],
),
Row(
spacing: 20,
children: [
Expanded( child: SecondaryButton(
onPressed: () => {Navigator.pop(context)},
text: "Volver"
)),
Expanded( child: PrimaryButton(
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SentScreen(format: "email"),
),
),
},
text: "Enviar",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
)),
],
),
],
),

View File

@@ -0,0 +1,98 @@
import 'package:auth/src/recover_password/presentation/new_password_screen.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SentScreen extends ConsumerWidget {
final String format;
const SentScreen({
super.key,
required this.format
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: Container(
margin: EdgeInsets.all(24),
child: Center(
child: Column(
spacing: 48,
children: [
Spacer(flex: 8),
Text(
"Recuperar contraseña",
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 30, letterSpacing: 0),
),
Row(
spacing: 10,
children: [
Spacer(),
Icon(
Icons.check,
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
Text(
format=="email"
?"Correo enviado correctamente"
:"SMS enviado correctamente",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Spacer(),
],
),
Column(
spacing: 16,
children: [
Text(
format=="email"
?"Revisa tu email y haz clic en el enlace para crear una nueva contraseña."
:"Revisa tu móvil y sigue las instrucciones para crear una nueva contraseña.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, letterSpacing: 0),
),
Text(
format=="email"
?"Si no recibes el correo en unos minutos, revisa tu carpeta de spam o pulsa \"Reenviar correo\"."
:"Si no recibes el SMS en unos minutos, asegúrate de tener cobertura o pulsa \"Reenviar SMS \".",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
],
),
Row(
spacing: 10,
children: [
Expanded( child: SecondaryButton(
onPressed: () => {},
text: format=="email"
?"Reenviar correo"
:"Reenviar SMS"
)),
Expanded(
child: PrimaryButton(
onPressed: ()=>Navigator.push(
context,
MaterialPageRoute(
builder: (_) => NewPasswordScreen(),
),
),
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonSecondary)
),
),
],
),
Spacer(flex: 10),
],
),
),
),
);
}
}

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,17 +64,21 @@ 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(
PrimaryButton(
onPressed: () => {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => AddKidScreen()),
),
if (kidAccount){
navigationContract.goTo(AppRoutes.dashboardHome)
} else {
navigationContract.pushTo(AppRoutes.deviceSignup)
}
},
child: Text("Continuar"),
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
Spacer(flex: 8),
],

View File

@@ -1,46 +1,102 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SignupAddressScreen extends StatelessWidget {
class SignupAddressScreen extends ConsumerStatefulWidget {
const SignupAddressScreen({super.key});
@override
ConsumerState<SignupAddressScreen> createState() => SignupAddressScreenState();
}
class SignupAddressScreenState extends ConsumerState<SignupAddressScreen>{
late String country;
late int relation;
@override
void initState() {
relation = 0;
super.initState();
}
@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),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.bottomLeft, child: Text(
"Fecha de nacimiento",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)),
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",
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
"¿Qué familiar eres?",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
CustomDropdown(
items: [Text("Padre"), Text("Madre"), Text("Tutor")],
hint: "¿Qué familiar eres?",
onChanged: (value)=>setState(() {
relation = value;
})
),
],
),
TextField(
decoration: InputDecoration(
hintText: "Dirección completa",
border: OutlineInputBorder(),
),
),
TextField(
decoration: InputDecoration(
hintText: "Ciudad",
border: OutlineInputBorder(),
),
),
DropdownMenu(
dropdownMenuEntries: List<DropdownMenuEntry>.generate(3, (int index) {
return DropdownMenuEntry(value: "España", label: "España");
}),
hintText: "País",
width: double.infinity,
),
TextField(
decoration: InputDecoration(
hintText: "Nacionalidad",
border: OutlineInputBorder(),
),
CustomTextField(label: "Dirección completa", hint: "Calle Gran Vía 30 6º, 28013"),
CustomTextField(label: "Ciudad", hint: "Ciudad"),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.bottomLeft,
child: Text(
"País",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
CustomDropdown(
items: [Text("España"), Text("Francia"), Text("Portugal")],
hint: "País",
onChanged: (value)=>setState(() {
country = value;
})
),
],
),
CustomTextField(label: "Nacionalidad", hint: "España"),
],
);
}

View File

@@ -0,0 +1,18 @@
import 'package:auth/src/sign_up/signup_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:get_it/get_it.dart';
import 'package:navigation/navigation.dart';
class SignupBuilder {
const SignupBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
final NavigationContract navigationContract = GetIt.I<NavigationContract>();
return MaterialPage<void>(
key: state.pageKey,
child: SignupScreen(navigationContract: navigationContract),
);
}
}

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,28 @@ 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())),
CustomTextField(label: "Nombre", hint: "Nombre"),
CustomTextField(label: "Apellidos", hint: "Apellidos"),
CustomTextField(label: "DNI", hint: "DNI"),
Row(
spacing: 8,
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"),
],
/*CustomDropdown(
value: 0,
items: [Icon(Icons.outlined_flag), Icon(Icons.outlined_flag), Icon(Icons.outlined_flag)],
onChanged: (value)=> {},
width: 80,
),*/
Expanded(child: CustomTextField(
label: "Teléfono móvil",
hint: "123456789",
numeric: true
))
]
),
CustomTextField(label: "Correo electrónico", hint: "Correo electrónico"),
],
);
}

View File

@@ -1,13 +1,22 @@
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: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';
import 'account_created_screen.dart';
class SignupScreen extends ConsumerStatefulWidget {
const SignupScreen({super.key});
NavigationContract navigationContract;
SignupScreen({
super.key,
required this.navigationContract
});
@override
ConsumerState<SignupScreen> createState() => _SignupScreenState();
@@ -15,133 +24,92 @@ class SignupScreen extends ConsumerStatefulWidget {
class _SignupScreenState extends ConsumerState<SignupScreen> {
int currentStep = 0;
bool acceptTerms = false;
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
return Scaffold(
body: Center(
child: Container(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
padding: const EdgeInsets.all(20),
child: SizedBox(
child: Stepper(
type: StepperType.horizontal,
currentStep: currentStep,
steps: getSteps(),
controlsBuilder:
(BuildContext context, ControlsDetails controls) {
final canGoBack = currentStep > 0;
return Row(
children: <Widget>[
Expanded(
child: OutlinedButton(
onPressed: canGoBack ? controls.onStepCancel : null,
child: const Text('Atrás'),
),
),
Expanded(
child: FilledButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(
theme.getColorFor(ThemeCode.buttonSecondary),
),
),
onPressed: controls.onStepContinue,
child: const Text('Siguiente'),
),
),
],
);
},
onStepCancel: () {
if (currentStep > 0) {
setState(() {
currentStep -= 1;
});
}
},
onStepContinue: () {
final isLastStep = currentStep == getSteps().length - 1;
if (isLastStep) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const AccountCreatedScreen(),
),
);
} else {
setState(() {
currentStep += 1;
});
}
},
),
),
),
),
);
return getSteps()[currentStep];
}
List<Step> getSteps() {
return <Step>[
Step(
state: currentStep > 0 ? StepState.complete : StepState.indexed,
isActive: currentStep >= 0,
stepStyle: currentStep >= 0
? const StepStyle(
connectorThickness: 0,
color: Color(0xFF329e95),
indexStyle: TextStyle(color: Colors.transparent),
)
: const StepStyle(
connectorThickness: 0,
color: Colors.transparent,
boxShadow: BoxShadow(spreadRadius: 5),
indexStyle: TextStyle(color: Colors.transparent),
),
title: const Text(""),
content: const SignupPersonalScreen(),
List<Widget> getSteps() {
final theme = ref.watch(themePortProvider);
return [
FormStepLayout(
title: "Crea tu usuario",
subtitle: "Con tu email y tu número podremos mantenerte siempre informado",
supertitle: "Usuario y contacto",
currentStep: 1,
numSteps: 3,
body: [SignupPersonalScreen()],
footer: [
Row(
spacing: 16,
children: [
Expanded(child: SecondaryButton(
onPressed: ()=>{},
text: "Atrás",
size: 16,
)),
Expanded(child: PrimaryButton(
onPressed: ()=>{setState(() {
currentStep++;
})},
text: "Siguiente",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
))
]
),
CheckboxListTile(
value: acceptTerms,
onChanged: (_) => setState(() {
acceptTerms = !acceptTerms;
}),
title: Text(
"Acepto los términos y condiciones",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
checkboxScaleFactor: 1.5,
contentPadding: EdgeInsets.zero,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
controlAffinity: ListTileControlAffinity.leading,
)
],
nextStep: ()=>{setState(() {
currentStep++;
})},
previousStep: ()=>{},
),
Step(
state: currentStep > 1 ? StepState.complete : StepState.indexed,
isActive: currentStep >= 1,
stepStyle: currentStep >= 1
? const StepStyle(
connectorThickness: 0,
color: Color(0xFF329e95),
indexStyle: TextStyle(color: Colors.transparent),
)
: const StepStyle(
connectorThickness: 0,
color: Colors.white,
boxShadow: BoxShadow(spreadRadius: 1),
indexStyle: TextStyle(color: Colors.transparent),
),
title: const Text(""),
content: const 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
? const StepStyle(
connectorThickness: 0,
color: Color(0xFF329e95),
indexStyle: TextStyle(color: Colors.transparent),
)
: const StepStyle(
connectorThickness: 0,
color: Colors.white,
boxShadow: BoxShadow(spreadRadius: 1),
indexStyle: TextStyle(color: Colors.transparent),
),
title: const Text(""),
content: const SignupUserScreen(),
FormStepLayout(
title: "Identifícate",
subtitle: "Contraseña mínima de 8 caracteres, con una mayúscula, un número y un carácter especial",
supertitle: "Usuario y contacto",
currentStep: 3,
numSteps: 3,
body: [SignupUserScreen()],
nextStep: ()=>{setState(() {
currentStep++;
})},
previousStep: ()=>{setState(() {
currentStep--;
})},
),
AccountCreatedScreen(navigationContract: widget.navigationContract, kidAccount: false)
];
}
}

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,94 @@
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(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: SafeArea(child: SingleChildScrollView(child: Container(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
padding: EdgeInsets.only(top: 40, left: 24, right: 24),
child: Column(
spacing: 32,
children: [
Column(
spacing: 24,
children: [
StepIndicator(
total: 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 PrimaryButton(
onPressed: nextStep,
text: "Continuar",
color: theme.getColorFor(ThemeCode.buttonPrimary)
);
} else {
return Row(
spacing: 16,
children: [
Expanded(child: SecondaryButton(
onPressed: previousStep,
text: "Atrás",
size: 16,
)),
Expanded(child: PrimaryButton(
onPressed: nextStep,
text: "Siguiente",
size: 16,
color: theme.getColorFor(ThemeCode.buttonSecondary)
))
]
);
}
}
}

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';
@@ -32,13 +31,10 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
padding: const EdgeInsets.all(10),
child: Column(
children: [
FilledButton(
onPressed: () {},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
child: const Center(child: Text('Añadir dinero')),
),
PrimaryButton(
onPressed: ()=>{},
text: "Añadir dinero",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
TextButton(
onPressed: () => Navigator.pop(context),
@@ -61,14 +57,10 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
'Ingresar dinero en el wallet',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextField(
decoration: const InputDecoration(
labelText: 'Cantidad',
hintText: '0€',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
CustomTextField(
numeric: true,
label: "Cantidad",
hint: "0€",
),
const Align(
alignment: Alignment.topLeft,
@@ -93,6 +85,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
),
const Text('Este dato aparecerá en el reloj del peque'),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Paga semanal'),
controlAffinity: ListTileControlAffinity.leading,
value: reason == 'weekly',
@@ -104,6 +97,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Objetivo semanal cumplido'),
controlAffinity: ListTileControlAffinity.leading,
value: reason == 'goal',
@@ -115,6 +109,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Gastos extraordinarios'),
controlAffinity: ListTileControlAffinity.leading,
value: reason == 'extraordinary',
@@ -126,6 +121,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Otro'),
controlAffinity: ListTileControlAffinity.leading,
value: reason == 'other',
@@ -136,16 +132,11 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
},
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
TextField(
minLines: 3,
maxLines: 3,
maxLength: 150,
decoration: InputDecoration(
labelText:
'Escribir mensaje a ${widget.kid.name} del motivo del ingreso',
hintText: 'Escribe tu mensaje',
border: const OutlineInputBorder(),
),
CustomTextField(
lines: 3,
length: 150,
label: "Escribir mensaje a ${widget.kid.name} del motivo del ingreso",
hint: "Escribe tu mensaje",
),
const Align(
alignment: Alignment.topLeft,
@@ -170,6 +161,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
),
const Text('Este dato aparecerá en el reloj del peque'),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Ahora'),
controlAffinity: ListTileControlAffinity.leading,
value: !program,
@@ -181,6 +173,7 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Programar'),
controlAffinity: ListTileControlAffinity.leading,
value: program,
@@ -191,17 +184,11 @@ class _DepositScreenState extends ConsumerState<DepositScreen> {
},
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
if (program)
const TextField(
decoration: InputDecoration(
labelText: 'Fecha / hora (placeholder)',
border: OutlineInputBorder(),
),
),
if (program) CustomTextField(),
],
),
),
],
);
}
}
}

View File

@@ -0,0 +1,111 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:home/src/presentation/wallet_management_layout.dart';
import 'package:sf_shared/sf_shared.dart';
class ExtractScreen extends ConsumerWidget{
final Kid kid;
@override
ExtractScreen({
super.key,
required this.kid,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return WalletManagementLayout(
kid: kid,
children: [Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary)
),
child: Column(
spacing: 24,
children: [
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Retirar dinero de la cuenta",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)
)),
Align(alignment: Alignment.topLeft, child: Text(
"Este dato aparecerá en el reloj del peque",
style: TextStyle(fontSize: 14, letterSpacing: 0)
))
],
),
CustomTextField(
label: "Selecciona la cantidad de dinero",
hint: "2€",
numeric: true,
),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Este es el mensaje fijado por defecto:",
style: TextStyle(fontSize: 16, letterSpacing: 0)
)),
Align(alignment: Alignment.topLeft, child: Text(
"\"Hemos quitado el dinero del reloj, ya no puedes pagar con él\"",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
))
],
),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Escribir mensaje a ${kid.name} del motivo de la retirada de su dinero",
style: TextStyle(fontSize: 14, letterSpacing: 0)
)),
CustomTextField(
hint: "Escribe tu mensaje",
lines: 4,
length: 150,
),
Row(
spacing: 4,
children: [
Icon(Icons.info_outline, size: 16),
Text("Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0))
],
)
],
)
],
),
)],
footer: Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.only(topRight: Radius.circular(24), topLeft: Radius.circular(24))
),
child: Column(
spacing: 16,
children: [
PrimaryButton(
onPressed: ()=>{Navigator.pop(context)},
text: "Enviar mensaje y bloquear",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: ()=>Navigator.pop(context),
child: Text("Cancelar", style: TextStyle(fontSize: 18))
)
],
),
)
);
}
}

View File

@@ -0,0 +1,786 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:home/src/presentation/wallet_management_layout.dart';
import 'package:sf_shared/sf_shared.dart';
class GoalsScreen extends ConsumerStatefulWidget{
final Kid kid;
List<Task> tasks = [Task(rewardAmount: 10, subtasks: [
Subtask(name: "Hacer la cama", completed: false),
Subtask(name: "Terminar los deberes", completed: true),
Subtask(name: "Recoger la mesa", completed: true),
Subtask(name: "Lavarse los dientes", completed: false)
]), Task(rewardAmount: 10)];
final List<SavingsGoal> savingsGoals;
@override
GoalsScreen({
super.key,
required this.kid,
this.savingsGoals = const [
SavingsGoal(name: "Cumpleaños de Emma", goal: 24, saved: 12.09),
SavingsGoal(name: "Protecciones nuevas de patines", goal: 10, saved: 0)
]
});
@override
ConsumerState<GoalsScreen> createState() => GoalsScreenState();
}
class GoalsScreenState extends ConsumerState<GoalsScreen>{
late bool fullPlan;
@override
void initState() {
super.initState();
fullPlan = true;
}
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
return WalletManagementLayout(
kid: widget.kid,
children: [
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(24))
),
child: Column(
spacing: 24,
children: [
Row(
spacing: 8,
children: [
Text("Metas", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)),
Icon(Icons.workspace_premium),
Spacer(),
Text("Sólo con Plan Completo", style: TextStyle(fontSize: 14, letterSpacing: 0))
],
),
Text(
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
],
),
),
SavingsBlock(fullPlan: fullPlan, savings: widget.savingsGoals),
TasksBlock(fullPlan: fullPlan, tasks: widget.tasks),
],
footer: Container()
);
}
}
class SavingsBlock extends ConsumerStatefulWidget {
final bool fullPlan;
final List savings;
@override
const SavingsBlock({
super.key,
required this.fullPlan,
required this.savings
});
@override
ConsumerState<SavingsBlock> createState() => SavingsBlockState();
}
class SavingsBlockState extends ConsumerState<SavingsBlock>{
late List<bool> showEdit;
late List<bool> showAdd;
late bool showNew;
@override
void initState() {
super.initState();
showEdit = widget.savings.map((_)=>false).toList();
showAdd = widget.savings.map((_)=>false).toList();
showNew = false;
}
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
var emptyBlock = ({fullPlan}) => Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(24)),
border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
),
child: Stack(
children: [
Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/ahorros.svg")),
Column(
spacing: 24,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Ahorros",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
)),
Align(alignment: Alignment.topLeft, child: SizedBox(
width: 200,
child: Text(
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
)),
if (fullPlan) Column(
spacing: 24,
children: [
Align(
alignment: Alignment.topLeft,
child: SecondaryButton(
onPressed: ()=>setState(() {
showNew = true;
}),
text: "Crear objetivo de ahorro",
radius: 8,
height: 44,
size: 14,
width: 230,
),
),
Align(
alignment: Alignment.topLeft,
child: TextButton(
onPressed: ()=>{},
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
child: Text(
"Ver estado de ahorros",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0)
)
)
)
],
),
if (!fullPlan) TextButton(
onPressed: ()=>{},
child: Row(
spacing: 4,
children: [
Icon(Icons.workspace_premium, size: 16),
Text("Suscribirme al Plan Completo")
],
)
)
],
)
],
)
);
var editBlock = ({create, index}) => Column(
spacing: 24,
children: [
CustomTextField(
hint: create? "Ponle un título para reconocerlo" : widget.savings[index].name,
label: "Motivo del ahorro",
lines: create? 2 : 1,
),
CustomTextField(
hint: "30€",
label: "Seleciona la cantidad a ahorrar",
numeric: true,
),
CheckboxListTile(
value: false,
onChanged: (_) => {},
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
title: Text(
"Ahorro automático desde su paga",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0),
),
),
CustomTextField(
hint: "2€",
label: "Selecciona la cantidad de dinero",
numeric: true,
),
CheckboxListTile(
value: false,
onChanged: (_) => {},
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
title: Text(
"Mandar automáticamente el dinero al reloj del peque cuando consiga su objetivo",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
),
Column(
spacing: 10,
children: [
Text(
"Este es el mensaje fijado por defecto que le llegará a su reloj:",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
Text(
"\"¡Genial, has conseguido ahorrar lo que querías!\"",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0),
)
],
),
Column(
spacing: 8,
children: [
CustomTextField(
hint: "Escribe tu mensaje",
label: "Escribir otro mensaje diferente:",
lines: 4,
length: 150,
),
Row(
spacing: 4,
children: [
Icon(Icons.info_outline, size: 16,),
Text(
"Máximo 150 caracteres",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
],
)
],
),
Column(
spacing: 24,
children: [
PrimaryButton(
onPressed: () => {},
text: "Guardar cambios",
color: theme.getColorFor(
ThemeCode.buttonPrimary)
),
TextButton(
onPressed: () {
if (create){
setState(() {
showNew = false;
});
} else {
setState(() {
showEdit[index] = false;
});
}
},
child: Text(
"Cancelar",
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
)
],
)
],
);
if (widget.fullPlan) {
if (showNew){
return Container(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundSecondary),
border: Border.all(color: theme.getColorFor(ThemeCode.textPrimary)),
borderRadius: BorderRadius.all(Radius.circular(24))
),
child: Column(
spacing: 24,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Ahorros",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
)),
Text(
"Enséñale a ahorrar y refuerza la importancia de no malgastar su dinero",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary)
),
child: editBlock(create: true, index: null)
)
],
),
);
} else {
if (widget.savings.isNotEmpty) {
return Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
//border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
),
child: Column(
spacing: 24,
children: [
Align(alignment: Alignment.topLeft,
child: Text(
"Ahorros",
style: TextStyle(fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
),
...List<Widget>.generate(widget.savings.length, (int index) =>
Container(
decoration: BoxDecoration(
border: BoxBorder.all(
color: theme.getColorFor(ThemeCode.textPrimary)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(24)),
),
padding: EdgeInsets.all(16),
child: Column(
spacing: 24,
children: [
Row(
spacing: 16,
children: [
Expanded(child: Column(
spacing: 8,
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
"Ahorro para",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16, letterSpacing: 0),
),
),
Text(
widget.savings[index].name,
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
],
)),
Container(
decoration: BoxDecoration(
color: theme.getColorFor(
ThemeCode.backgroundTertiary),
borderRadius: BorderRadius.all(
Radius.circular(16)),
),
padding: EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
child: Row(
spacing: 8,
children: [
Icon(Icons.account_balance, size: 24,
color: theme.getColorFor(
ThemeCode.buttonPrimary)),
MoneyText(
text: "${widget.savings[index]
.goal}",
size: 30,
secondarySize: 14,
color: theme.getColorFor(
ThemeCode.buttonPrimary)
)
],
),
)
]
),
if (index == 0)Column(
spacing: 8,
children: [
ProgressBar(
max: widget.savings[index].goal+0.0,
value: widget.savings[index].saved,
height: 64,
textSize: 40,
textSecondarySize: 24,
backgroundColor: theme.getColorFor(
ThemeCode.backgroundTertiary),
foregroundColor: theme.getColorFor(
ThemeCode.buttonPrimary),
textColor: theme.getColorFor(
ThemeCode.textSecondary)
),
Center(child: Text("Ahorrado")),
if (!showAdd[index]) TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.zero)),
onPressed: () =>
setState(() {
showAdd[index] = true;
}),
child: Text(
"+ Añadir dinero extra a este ahorro",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16, letterSpacing: 0),
)
),
if (showAdd[index]) CustomTextField(
hint: "5€",
numeric: true,
)
],
),
if (!showEdit[index]) Row(
children: [
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.zero)),
onPressed: () =>
setState(() {
showEdit[index] = true;
}),
child: Row(
spacing: 4,
children: [
Icon(Icons.edit_outlined, size: 24),
Text(
"Editar",
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0),
),
],
)
),
Spacer(),
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.zero)),
onPressed: () => {},
child: Row(
spacing: 4,
children: [
Icon(Icons.delete_outline_outlined,
size: 24),
Text(
"Eliminar",
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
],
)
),
],
),
if(showEdit[index]) editBlock(
create: false, index: index)
],
),
),
),
TextButton(
onPressed: () =>
setState(() {
showNew = true;
}),
child: Row(
spacing: 4,
children: [
Spacer(),
Icon(Icons.account_balance, size: 24),
Text(
"Crear otro ahorro",
style: TextStyle(fontSize: 18,
fontWeight: FontWeight.w500,
letterSpacing: 0),
),
Spacer()
],
)
),
],
),
);
} else {
return emptyBlock(fullPlan: true);
}
}
} else {
return emptyBlock(fullPlan: false);
}
}
}
class TasksBlock extends ConsumerStatefulWidget {
final bool fullPlan;
final List<Task> tasks;
@override
const TasksBlock({
super.key,
required this.fullPlan,
required this.tasks
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => TasksBlockState();
}
class TasksBlockState extends ConsumerState<TasksBlock>{
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
final emptyBlock = ({fullPlan})=>Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(24)),
border: BoxBorder.all(color: theme.getColorFor(ThemeCode.textPrimary))
),
child: Stack(
children: [
Align(alignment: Alignment.topRight, child: SvgPicture.asset("assets/images/ui/tareas.svg")),
Column(
spacing: 24,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Tareas",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
)),
Align(alignment: Alignment.topLeft, child: SizedBox(
width: 200,
child: Text(
"Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
)),
if (fullPlan) Align(
alignment: Alignment.topLeft,
child: SecondaryButton(
onPressed: ()=>{},
text: "Crear lista de tareas",
size: 14,
width: 190,
height: 44,
)
),
if (!fullPlan) TextButton(
onPressed: ()=>{},
child: Row(
spacing: 4,
children: [
Icon(Icons.workspace_premium, size: 16),
Text("Suscribirme al Plan Completo")
],
)
)
],
)
],
)
);
if (widget.fullPlan) {
if (widget.tasks.isNotEmpty) {
return Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
border: BoxBorder.all(
color: theme.getColorFor(ThemeCode.textPrimary)),
color: theme.getColorFor(ThemeCode.backgroundSecondary)
),
child: Column(
spacing: 24,
children: [
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Tareas",
style: TextStyle(fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)),
Text(
"Refuerza su sentido de la responsabilidad con pequeñas tareas que cumplir",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
],
),
...List<Widget>.generate(widget.tasks.length, (int i) =>
Container(
decoration: BoxDecoration(
border: BoxBorder.fromLTRB(top: BorderSide(
color: theme.getColorFor(ThemeCode.buttonPrimary),
width: 8)),
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary)
),
padding: EdgeInsets.all(24),
child: Column(
spacing: 16,
children: [
Row(children: [
Text(
"Lista de tareas",
style: TextStyle(fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0),
),
Spacer(),
Text(
"10 oct - 17 oct",
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
]),
...List<CheckboxListTile>.generate(
widget.tasks[i].subtasks.length, (int j) =>
CheckboxListTile(
contentPadding: EdgeInsets.zero,
checkboxScaleFactor: 2,
value: widget.tasks[i].subtasks[j].completed,
title: Text(widget.tasks[i].subtasks[j].name),
controlAffinity: ListTileControlAffinity
.leading,
activeColor: theme.getColorFor(
ThemeCode.buttonPrimary),
onChanged: (_) =>
setState(() {
widget.tasks[i].subtasks[j] = Subtask(
name: widget.tasks[i].subtasks[j]
.name,
completed: !widget.tasks[i]
.subtasks[j].completed);
}),
)
),
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets
.zero)),
onPressed: () => {},
child: Align(
alignment: Alignment.topLeft,
child: Text(
"+ Añadir tarea",
style: TextStyle(fontSize: 18,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
)
),
Container(
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode
.backgroundTertiary),
borderRadius: BorderRadius.all(Radius.circular(
24))
),
padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 16),
child: Row(
spacing: 16,
children: [
Expanded(child: Text(
"Recompensa por cumplimiento",
style: TextStyle(
fontSize: 16, letterSpacing: 0),
)),
Row(
spacing: 8,
children: [
Icon(Icons.emoji_events_outlined, size: 16,
color: theme.getColorFor(
ThemeCode.buttonPrimary)),
MoneyText(
text: "${widget.tasks[i]
.rewardAmount}",
size: 40,
secondarySize: 24,
color: theme.getColorFor(
ThemeCode.buttonPrimary)
)
],
)
],
),
),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
"Este es el mensaje que se enviará al cumplir con todas las tareas:",
style: TextStyle(
fontSize: 14, letterSpacing: 0),
),
),
Align(
alignment: Alignment.topLeft,
child: Text(
"¡Enhorabuena, has cumplido todas las tareas de esta semana!",
style: TextStyle(fontSize: 14,
fontWeight: FontWeight.w500,
letterSpacing: 0),
),
),
],
),
Align(
alignment: Alignment.topLeft,
child: TextButton(
onPressed: () => {},
child: Row(
spacing: 4,
children: [
Icon(Icons.delete_outline_outlined,
size: 24),
Text(
"Eliminar lista de tareas",
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0),
)
],
)
),
)
],
),
)
),
SecondaryButton(
onPressed: () => {},
text: "Crear una lista de tareas nueva",
size: 14,
),
],
)
);
} else {
return emptyBlock(fullPlan: true);
}
} else {
return emptyBlock(fullPlan: false);
}
}
}

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(
@@ -41,7 +43,7 @@ class HomeScreen extends ConsumerWidget {
children: <TextSpan>[
TextSpan(
text: name,
style: TextStyle(fontWeight: FontWeight.bold),
style: TextStyle(fontWeight: FontWeight.w500),
),
],
),
@@ -51,10 +53,7 @@ class HomeScreen extends ConsumerWidget {
Align(
alignment: Alignment.topLeft,
child: TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => CreateProfileScreen()),
),
onPressed: () => navigationContract.pushTo(AppRoutes.deviceSignup),
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: SizedBox(
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

@@ -2,9 +2,11 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:home/src/presentation/deposit_screen.dart';
import 'package:home/src/presentation/extract_screen.dart';
import 'package:home/src/presentation/goals_screen.dart';
import 'package:home/src/presentation/lock_card_screen.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:home/src/presentation/limits_screen.dart';
import 'package:home/src/presentation/money_text.dart';
import 'package:home/src/presentation/wage_screen.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -16,6 +18,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 +26,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 +48,257 @@ 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: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => LockCardScreen(kid: kid)
)
),
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(16),
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(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DepositScreen(kid: kid)
)
),
child: Column(
spacing: 8,
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(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
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(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
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,
),
Spacer(),
TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GoalsScreen(kid: kid)
)
),
Text(
"Límites",
style: TextStyle(
color: theme.getColorFor(
ThemeCode.textPrimary,
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(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ExtractScreen(kid: kid)
)
),
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: 300,
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 +320,62 @@ 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 Container(padding: EdgeInsets.only(top: 24),
child: Column(
spacing: 8,
children: [
Align(alignment: Alignment.bottomLeft, child: Text(activity[index]["date"].toString(), style: TextStyle(fontSize: 18, height: 1.5))),
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: Align(alignment: Alignment.bottomLeft, child: Text("Ver todos"))),
]
)
);
}
}
}

View File

@@ -22,7 +22,7 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
@override
void initState() {
super.initState();
dailyLimits = [
dailyLimits = [ //dey, week, month, year
{"title": "Diario L-V", "limit": "5", "edit": false},
{"title": "Fines de semana", "limit": "8", "edit": false},
{"title": "Semanal", "limit": "30", "edit": false},
@@ -41,14 +41,27 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
"end": "21:00",
"edit": false,
},
{"title": "Vacaciones", "start": "09:00", "end": "22:00", "edit": false},
{
"title": "Vacaciones",
"start": "09:00",
"end": "22:00",
"edit": false
},
];
conditions = [
{"title": "Alimentación", "limit": "10", "edit": false},
{"title": "Transporte", "limit": "10", "edit": false},
{"title": "Alimentación", "limit": "10", "edit": false},
{"title": "Alimentación", "limit": "10", "active": true, "edit": false},
{"title": "Transporte", "limit": "10", "active": false, "edit": false},
{"title": "Alimentación", "limit": "10", "active": false, "edit": false},
];
blocks = [
{"title": "Alojamiento y Hoteles", "active": true},
{"title": "Supermercados", "active": true},
{"title": "Gasolineras", "active": true},
{"title": "Restaurantes", "active": true},
{"title": "Bares y discotecas", "active": true},
{"title": "Licorerías", "active": true},
{"title": "Estancos", "active": true},
];
blocks = [];
}
@override
@@ -57,16 +70,32 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
return WalletManagementLayout(
kid: widget.kid,
footer: Column(
children: [
FilledButton(
onPressed: () => {},
child: SizedBox(
width: double.infinity,
child: Center(child: Text("Guardar límites")),
footer: Container(
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24))
),
padding: EdgeInsets.all(24),
child: Column(
children: [
PrimaryButton(
onPressed: () => {},
text: "Guardar límites",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
),
],
TextButton(
onPressed: ()=>Navigator.pop(context),
child: Text(
"Cancelar",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: theme.getColorFor(ThemeCode.textPrimary)
)
)
)
],
),
),
children: [
Container(
@@ -78,9 +107,12 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
child: Column(
spacing: 10,
children: [
Text(
"Pon límite de gastos",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
Align(
alignment: Alignment.topLeft,
child: Text(
"Pon límite de gastos",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
)
),
Text("Libertad para ellos, tranquilidad para ti"),
...List<Widget>.generate(dailyLimits.length, (int index) {
@@ -103,7 +135,9 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
),
],
),
if (dailyLimits[index]["edit"]) TextField(),
if (dailyLimits[index]["edit"]) CustomTextField(
hint: "5€",
),
],
);
}),
@@ -136,22 +170,113 @@ class LimitsScreenState extends ConsumerState<LimitsScreen> {
TextButton(
onPressed: () => {
setState(() {
timeLimits[index]["edit"] =
!timeLimits[index]["edit"];
timeLimits[index]["edit"] = !timeLimits[index]["edit"];
}),
},
child: Text("Editar"),
),
],
),
if (timeLimits[index]["edit"]) TextField(),
if (timeLimits[index]["edit"]) CustomTextField(
hint: "5€",
),
],
);
}),
],
),
),
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
),
child: Column(
spacing: 24,
children: [
Text(
"Condiciones",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
),
Column(
spacing: 8,
children: List<Widget>.generate(conditions.length, (int index)=>
Column(
spacing: 8,
children: [
Row(children: [
Expanded(child: CheckboxListTile(
value: conditions[index]["active"],
onChanged: (_)=>setState(() {
conditions[index]["active"] = !conditions[index]["active"];
}),
title: Text(
"${conditions[index]["title"]}: ${conditions[index]["limit"]}€/sem",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
)),
TextButton(
onPressed: ()=>setState(() {
conditions[index]["edit"] = ! conditions[index]["edit"];
}),
child: Text(
"Editar",
style: TextStyle(fontSize: 16, letterSpacing: 0),
)
)
]),
if (conditions[index]["edit"]) CustomTextField(
hint: "5€",
numeric: true,
)
]
)
),
)
],
)
),
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(24))
),
child: Column(
spacing: 24,
children: [
Text(
"Comercios bloqueados",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0),
),
Column(
spacing: 8,
children: List<Widget>.generate(blocks.length, (int index) =>
CheckboxListTile(
value: blocks[index]["active"],
onChanged: (_) => setState(() {
blocks[index]["active"] = !blocks[index]["active"];
}),
title: Text(
blocks[index]["title"],
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
)
)
)
],
),
)
],
);
}
}
}

View File

@@ -0,0 +1,105 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:home/src/presentation/wallet_management_layout.dart';
import 'package:sf_shared/sf_shared.dart';
class LockCardScreen extends ConsumerWidget{
final Kid kid;
@override
LockCardScreen({
super.key,
required this.kid,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
return WalletManagementLayout(
kid: kid,
children: [Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: theme.getColorFor(ThemeCode.backgroundPrimary)
),
child: Column(
spacing: 24,
children: [
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Bloqueo de tarjeta",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0)
)),
Align(alignment: Alignment.topLeft, child: Text(
"Este dato aparecerá en el reloj del peque",
style: TextStyle(fontSize: 14, letterSpacing: 0)
))
],
),
Column(
spacing: 8,
children: [
Align(alignment: Alignment.topLeft, child: Text(
"Este es el mensaje fijado por defecto:",
style: TextStyle(fontSize: 16, letterSpacing: 0)
)),
Align(alignment: Alignment.topLeft, child: Text(
"\"De momento hemos bloqueado el dinero del reloj\"",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, letterSpacing: 0)
))
],
),
Column(
spacing: 8,
children: [
Text(
"Escribir mensaje a ${kid.name} del motivo del bloqueo",
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
CustomTextField(
hint: "Escribe tu mensaje",
lines: 4,
length: 150,
),
Row(
spacing: 4,
children: [
Icon(Icons.info_outline, size: 16),
Text("Máximo 150 caracteres", style: TextStyle(fontSize: 14, letterSpacing: 0))
],
)
],
)
],
),
)],
footer: Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.only(topRight: Radius.circular(24), topLeft: Radius.circular(24))
),
child: Column(
spacing: 16,
children: [
PrimaryButton(
onPressed: ()=>{Navigator.pop(context)},
text: "Enviar mensaje y bloquear",
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: ()=>Navigator.pop(context),
child: Text("Cancelar", style: TextStyle(fontSize: 18))
)
],
),
)
);
}
}

View File

@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
class MoneyText extends StatelessWidget {
final String text;
final double size;
final bool resize;
final Color color;
const MoneyText({super.key, required this.text, required this.size, required this.resize, required this.color});
@override
Widget build(BuildContext context) {
final units = text.split(".")[0];
final cents = ",${text.split(".")[1]}";
return Text.rich(TextSpan(
text: units,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: size,
color: color
),
children: [
TextSpan(
text: cents,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: resize ? size/2 : size
)
)
]
));
}
}

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';
@@ -41,13 +40,10 @@ class _WageScreenState extends ConsumerState<WageScreen> {
child: Column(
spacing: 10,
children: [
FilledButton(
onPressed: () {},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
child: const Center(child: Text('Activar paga automática')),
),
PrimaryButton(
onPressed: () => {},
text: "Activar paga automática",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
TextButton(onPressed: () {}, child: const Text('Cancelar')),
],
@@ -63,18 +59,16 @@ class _WageScreenState extends ConsumerState<WageScreen> {
child: Column(
spacing: 10,
children: [
const Text(
'Paga automática',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextField(
decoration: const InputDecoration(
labelText: 'Cantidad',
hintText: '0€',
border: OutlineInputBorder(),
Align(alignment: Alignment.topLeft,
child: const Text(
"Paga automática",
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
),
CustomTextField(
numeric: true,
label: "Cantidad",
hint: "0€",
),
const Text('Saldo total disponible después: 30 €'),
],
@@ -86,16 +80,21 @@ class _WageScreenState extends ConsumerState<WageScreen> {
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(24),
child: Column(
spacing: 10,
children: [
const Text(
'Frecuencia',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
Align(alignment: Alignment.topLeft,
child: const Text(
"Frecuencia",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
)
),
Align(alignment: Alignment.topLeft,
child: const Text("Cuándo se envía el dinero"),
),
const Text('Cuándo se envía el dinero'),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Semanal'),
controlAffinity: ListTileControlAffinity.leading,
value: frequence == 'weekly',
@@ -107,6 +106,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Cada dos semanas'),
controlAffinity: ListTileControlAffinity.leading,
value: frequence == 'biweekly',
@@ -118,6 +118,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Mensual'),
controlAffinity: ListTileControlAffinity.leading,
value: frequence == 'monthly',
@@ -128,50 +129,33 @@ class _WageScreenState extends ConsumerState<WageScreen> {
},
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
SizedBox(
width: double.infinity,
child: DropdownMenu<String>(
label: const Text('Día de la semana'),
initialSelection: 'Domingo',
dropdownMenuEntries: () {
const days = [
'Lunes',
'Martes',
'Miércoles',
'Jueves',
'Viernes',
'Sábado',
'Domingo',
];
return List<DropdownMenuEntry<String>>.generate(
days.length,
(index) => DropdownMenuEntry<String>(
value: days[index],
label: days[index],
),
);
}(),
),
CustomDropdown(
items: [
Text("Lunes"),
Text("Martes"),
Text("Miércoles"),
Text("Jueves"),
Text("Viernes"),
Text("Sábado"),
Text("Domingo"),
],
values: ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"],
onChanged: (value)=> {},
hint: "Día de la semana",
),
DropdownMenu<int>(
label: const Text('Hora del día'),
initialSelection: 9,
dropdownMenuEntries: List<DropdownMenuEntry<int>>.generate(
24,
(index) =>
DropdownMenuEntry<int>(value: index, label: '$index:00'),
),
CustomDropdown(
hint: "Hora del día",
items: List<Widget>.generate(24,(int index){
return Text("$index:00");
}),
onChanged: (value)=> {},
),
TextField(
minLines: 3,
maxLines: 3,
maxLength: 150,
decoration: InputDecoration(
labelText:
'Escribir mensaje a ${widget.kid.name} del motivo del ingreso',
hintText: 'Escribe tu mensaje',
border: const OutlineInputBorder(),
),
CustomTextField(
lines: 3,
length: 150,
label:
"Escribir mensaje a ${widget.kid.name} del motivo del ingreso",
hint: "Escribe tu mensaje",
),
const Align(
alignment: Alignment.topLeft,
@@ -186,16 +170,21 @@ class _WageScreenState extends ConsumerState<WageScreen> {
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(24),
child: Column(
spacing: 10,
children: [
const Text(
'Condiciones',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
const Align(alignment: Alignment.topLeft,
child: Text(
"Condiciones",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
),
const Align(alignment: Alignment.topLeft,
child: Text("Este dato aparecerá en el reloj del peque"),
),
const Text('Este dato aparecerá en el reloj del peque'),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Sólo si cumple límites semanales'),
controlAffinity: ListTileControlAffinity.leading,
value: conditions['weeklyLimits'],
@@ -208,6 +197,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Sólo si no ha tenido incidencias'),
controlAffinity: ListTileControlAffinity.leading,
value: conditions['incidences'],
@@ -220,6 +210,7 @@ class _WageScreenState extends ConsumerState<WageScreen> {
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Pausar durante vacaciones'),
controlAffinity: ListTileControlAffinity.leading,
value: conditions['holidays'],

View File

@@ -0,0 +1,244 @@
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: PrimaryButton(
onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))),
text: "+ Añadir dinero",
color: theme.getColorFor(ThemeCode.buttonSecondary),
radius: 12,
)
/*FilledButton(
onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (_)=>DepositScreen(kid: kid))),
style: ButtonStyle(
shape: WidgetStateProperty.all<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: [
PrimaryButton(
onPressed: ()=>{},
text: "Cámara",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
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

@@ -47,46 +47,47 @@ class ActivityListState extends ConsumerState<ActivityList> {
};
return Column(
spacing: 20,
spacing: 32,
children: List<Widget>.generate(widget.activity.length, (int index) {
final color = colors[index % colors.length];
final type = widget.activity[index]["type"] as String;
final logItem = Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border(left: BorderSide(color: color, width: 5)),
border: Border(left: BorderSide(color: color, width: 8)),
),
child: Column(
spacing: 15,
spacing: 24,
children: [
Row(
spacing: 8,
children: [
Icon(icons[type], color: color),
const SizedBox(width: 8),
Text(
Expanded(child: Text(
titles[type]!,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const Spacer(),
)),
const Text("14/01/2005"),
],
),
const 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],
@@ -96,15 +97,15 @@ class ActivityListState extends ConsumerState<ActivityList> {
});
},
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
semanticLabel: "Eliminar",
semanticLabel: "Eliminar"
),
Expanded(child: logItem),
],
Expanded(child: logItem)
]
);
} else {
return logItem;
}
}),
})
);
}
}
}

View File

@@ -21,7 +21,7 @@ class ActivityScreen extends ConsumerWidget {
final content = [
Text(
"Movimientos recientes",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 24),
),
Row(
spacing: 20,
@@ -32,7 +32,7 @@ class ActivityScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Mes")),
],
),
SizedBox(height: 200, child: LineGraph()),
LineGraph(),
ActivityList(activity: activity, edit: false),
];
@@ -54,4 +54,4 @@ class ActivityScreen extends ConsumerWidget {
),
);
}
}
}

View File

@@ -29,25 +29,37 @@ class AlertScreenState extends ConsumerState<AlertScreen> {
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
body: Container(
margin: EdgeInsets.all(30),
return SafeArea(
child: Container(
color: theme.getColorFor(ThemeCode.backgroundSecondary),
margin: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
spacing: 32,
children: [
Row(
children: [
Text("Alertas"),
Text(
"Alertas",
style: TextStyle(
fontSize: 24,
letterSpacing: 0,
fontWeight: FontWeight.w500
)
),
Spacer(),
TextButton(
onPressed: () => setState(() {
edit = !edit;
}),
child: Text("Editar"),
child: Text("Editar", style: TextStyle(fontSize: 16, letterSpacing: 0)),
),
],
),
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';
@@ -19,9 +20,16 @@ class ProfileScreen extends ConsumerWidget {
{"type": "lock"},
];
final kids = [
Kid(name: "Ana", balance: 15, savings: 5),
Kid(name: "Carlos", balance: 15, savings: 5)
];
final name = "Juan";
final total = 95.03;
final available = 44.09;
final savings = 4.16;
final savingsPlan = 30.0;
final content = [
Row(
@@ -49,53 +57,45 @@ class ProfileScreen extends ConsumerWidget {
),
],
),
WalletBalanceBlock(max: total, value: available, savings: savings, savingsPlan: savingsPlan),
LineGraph(),
DepositBlock(max: 150 - total),
Container(
padding: EdgeInsets.all(20),
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: 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),
];
@@ -105,7 +105,7 @@ class ProfileScreen extends ConsumerWidget {
children: [
DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(30)),
borderRadius: const BorderRadius.only(bottomRight: Radius.circular(24), bottomLeft: Radius.circular(24)),
color: Color(0xFF4B4B4B),
),
child: SizedBox(width: double.infinity, height: 200),

View File

@@ -41,7 +41,7 @@ class SettingsScreen extends ConsumerWidget {
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
@@ -57,15 +57,15 @@ class SettingsScreen extends ConsumerWidget {
),
Spacer(),
TextButton(onPressed: () => {}, child: Text("Editar wallet")),
Icon(Icons.attach_money),
Icon(Icons.account_balance, size: 24),
],
),
Text(relation),
Align(alignment: Alignment.centerLeft, child: Text(relation)),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
@@ -73,7 +73,6 @@ class SettingsScreen extends ConsumerWidget {
child: Column(
children: [
Row(
spacing: 10,
children: [
Text(
"Datos personales",
@@ -83,47 +82,56 @@ class SettingsScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Editar")),
],
),
Text.rich(
TextSpan(
text: "Nombre: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: fullName,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Nombre: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: fullName,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
)
),
Text.rich(
TextSpan(
text: "Fecha de nacimiento: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: birthDate,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Fecha de nacimiento: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: birthDate,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
)
),
Text.rich(
TextSpan(
text: "Familiar: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: relation,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Familiar: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: relation,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
)
),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
@@ -131,7 +139,6 @@ class SettingsScreen extends ConsumerWidget {
child: Column(
children: [
Row(
spacing: 10,
children: [
Text(
"Dirección",
@@ -141,47 +148,56 @@ class SettingsScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Editar")),
],
),
Text.rich(
TextSpan(
text: "Dirección: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: address,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Dirección: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: address,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
),
),
Text.rich(
TextSpan(
text: "País: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: country,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "País: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: country,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
),
),
Text.rich(
TextSpan(
text: "Nacionalidad: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: nationality,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Nacionalidad: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: nationality,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
),
),
)
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
@@ -189,7 +205,6 @@ class SettingsScreen extends ConsumerWidget {
child: Column(
children: [
Row(
spacing: 10,
children: [
Text(
"Usuario",
@@ -199,42 +214,46 @@ class SettingsScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Editar")),
],
),
Text.rich(
TextSpan(
text: "Correo: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: email,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Correo: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: email,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
),
),
Text.rich(
TextSpan(
text: "Teléfono: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: phone,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
Align(
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
text: "Teléfono: ",
style: TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: phone,
style: TextStyle(fontWeight: FontWeight.normal),
),
],
),
),
),
)
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
),
child: Row(
spacing: 10,
children: [
Text(
"Cambio de contraseña",
@@ -246,7 +265,7 @@ class SettingsScreen extends ConsumerWidget {
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
@@ -254,7 +273,6 @@ class SettingsScreen extends ConsumerWidget {
child: Column(
children: [
Row(
spacing: 10,
children: [
Text(
"Método de pago",
@@ -269,7 +287,26 @@ class SettingsScreen extends ConsumerWidget {
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundPrimary),
),
child: Column(
spacing: 24,
children: [
Text(
"Retirar y reembolsar dinero del wallet",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Text("Para transferirte el saldo a tu cuenta necesitamos tu IBAN y algunos datos básics. Así nos aseguramos de que la transferencia sea segura y rápida."),
CustomTextField(label: "Nombre y Apellidos", hint: "******"),
CustomTextField(label: "IBAN con número español", hint: "******")
],
),
),
Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: theme.getColorFor(ThemeCode.backgroundTertiary),
@@ -277,7 +314,6 @@ class SettingsScreen extends ConsumerWidget {
child: Column(
children: [
Row(
spacing: 10,
children: [
Text(
"Plan anual",
@@ -287,15 +323,81 @@ class SettingsScreen extends ConsumerWidget {
TextButton(onPressed: () => {}, child: Text("Cambiar Plan")),
],
),
Text("Sin permanencia"),
Text("Llamadas y datos ilimitados"),
Text("2 meses gratis"),
Align(
alignment: Alignment.centerLeft,
child: Row(
spacing: 8,
children: [
Icon(Icons.check, size: 24),
Text(
"Sin permanencia",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
],
)
),
Align(
alignment: Alignment.centerLeft,
child: Row(
spacing: 8,
children: [
Icon(Icons.check, size: 24),
Text(
"Llamadas y datos ilimitados",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
],
)
),
Align(
alignment: Alignment.centerLeft,
child: Row(
spacing: 8,
children: [
Icon(Icons.check, size: 24),
Text(
"2 meses gratis",
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
],
)
),
],
),
),
TextButton(onPressed: () => {}, child: Text("Contáctanos")),
TextButton(onPressed: () => {}, child: Text("Preguntas frecuentes")),
Column(
spacing: 16,
children: [
Align(
alignment: Alignment.topLeft,
child: TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: () => {},
child: Row(
spacing: 4,
children: [
Icon(Icons.contact_support_outlined, size: 24),
Text("Contáctanos")
],
)
)
),
Align(
alignment: Alignment.topLeft,
child: TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))),
onPressed: () => {},
child: Row(
spacing: 4,
children: [
Icon(Icons.contact_support_outlined, size: 24),
Text("Preguntas frecuentes")
],
)
)
),
],
)
];
return Scaffold(
@@ -304,7 +406,7 @@ class SettingsScreen extends ConsumerWidget {
children: [
DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(30)),
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
color: Color(0xFF4B4B4B),
),
child: SizedBox(width: double.infinity, height: 200),
@@ -336,13 +438,10 @@ class SettingsScreen extends ConsumerWidget {
),
child: Column(
children: [
FilledButton(
PrimaryButton(
onPressed: () => {},
child: Container(
width: double.infinity,
padding: EdgeInsets.all(20),
child: Center(child: Text("Guardar cambios")),
),
text: "Guardar cambios",
color: theme.getColorFor(ThemeCode.buttonPrimary)
),
TextButton(
onPressed: () => Navigator.pop(context),

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,3 +1,12 @@
export 'src/theme/theme_port.dart';
export 'src/theme/theme_sf_adapter.dart';
export 'src/icons/sf_icons.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';
export 'src/buttons/primary_button.dart';
export 'src/buttons/secondary_button.dart';
export 'src/buttons/custom_text_button.dart';
export 'src/dropdowns/dropdown.dart';

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class CustomTextButton extends StatelessWidget{
final onPressed;
final String text;
final double size;
final FontWeight weight;
final Color? color;
@override
const CustomTextButton({
super.key,
required this.onPressed,
required this.text,
this.size = 14,
this.weight = FontWeight.normal,
this.color
});
@override
Widget build(BuildContext context) {
return TextButton(
style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
onPressed: onPressed,
child: Text(
text,
style: TextStyle(
fontSize: size,
fontWeight: weight,
letterSpacing: 0,
color: color?? Color(0xFF4B4B4B),
decoration: TextDecoration.underline
),
)
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PrimaryButton extends StatelessWidget{
final onPressed;
final String text;
final Color color;
final double height;
final double? width;
final double size;
final double radius;
final double padding;
PrimaryButton({
required this.onPressed,
required this.text,
required this.color,
this.height = 60,
this.width,
this.size = 18,
this.radius = 18,
this.padding = 0,
});
@override
Widget build(BuildContext context) {
return FilledButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(color),
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: padding)),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
)),
),
onPressed: onPressed,
child: SizedBox(
width: width,
height: height,
child: Center(child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: size,
fontWeight: FontWeight.w500,
letterSpacing: 0,
color: Colors.white//theme.getColorFor(ThemeCode.textSecondary)
)
))
)
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SecondaryButton extends StatelessWidget {
final onPressed;
final String? text;
final IconData? icon;
final String? label;
final Color? color;
final double radius;
final double padding;
final double height;
final double? width;
final double? size;
@override
SecondaryButton({
required this.onPressed,
this.text,
this.icon,
this.label,
this.color,
this.radius = 18,
this.padding = 0,
this.height = 60,
this.width,
this.size,
});
@override
Widget build(BuildContext context) {
return OutlinedButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: padding)),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
side: BorderSide(color: Color(0xFF4B4B4B))
)),
),
onPressed: onPressed,
child: SizedBox(
width: width,
height: height,
child: Center(child: text!=null ? Text(
text!,
textAlign: TextAlign.center,
semanticsLabel: label,
style: TextStyle(
fontSize: size ?? 18,
fontWeight: FontWeight.w500,
letterSpacing: 0,
color: Color(0xFF4B4B4B)
)
) : Icon(
icon,
semanticLabel: label,
size: size ?? 24,
color: color ?? Color(0xFF4B4B4B)
))
)
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CustomDropdown extends StatelessWidget{
final List<Widget> items;
final values;
final onChanged;
final value;
final String? hint;
final String? label;
final double radius;
final double height;
final double width;
final Color? color;
const CustomDropdown({
super.key,
required this.items,
this.values,
required this.onChanged,
this.value,
this.hint,
this.label,
this.radius = 12,
this.width = double.infinity,
this.height = 70,
this.color
});
@override
Widget build(BuildContext context) {
return Column(
spacing: 8,
children: [
if (label != null) Align(
alignment: Alignment.bottomLeft,
child: Text(
label!,
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
SizedBox(
width: width,
height: height,
child: Center(child: DropdownButtonFormField(
dropdownColor: Colors.white,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
borderSide: BorderSide(color: color??Color(0xFF4B4B4B))
)
),
//underline: Container(),
initialValue: value,
onChanged: onChanged,
hint: Text(hint??""),
items: List<DropdownMenuItem>.generate(items.length, (int index){
return DropdownMenuItem(value: (values!=null)?values[index]:index, child: items[index]);
})
)),
)
],
) ;
}
}

View File

@@ -0,0 +1,83 @@
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 StatefulWidget{
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
State<CustomTextField> createState() => CustomTextFieldState();
}
class CustomTextFieldState extends State<CustomTextField>{
@override
Widget build(BuildContext context) {
return Column(
spacing: 8,
children: [
?widget.label == '' ? null : Align(
alignment: Alignment.bottomLeft,
child: Text(
widget.label,
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
),
TextFormField(
keyboardType: widget.numeric? TextInputType.number : TextInputType.text,
obscureText: !(widget.showPassword ?? true),
enableSuggestions: widget.showPassword ?? true,
autocorrect: !(widget.showPassword ?? false),
style: TextStyle(color: Color(0xFF4B4B4B)),
inputFormatters: widget.numeric? [
FilteringTextInputFormatter.digitsOnly
] : [],
decoration: InputDecoration(
counterText: "",
hintText: widget.hint,
//labelText: widget.label,
//floatingLabelBehavior: FloatingLabelBehavior.always,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Color(0xFF4B4B4B)),
gapPadding: 16
),
suffixIcon: widget.showPassword!=null ? IconButton(
icon: Icon(widget.showPassword!
? Icons.visibility_off
: Icons.visibility),
onPressed: () {
setState(() {
widget.showPassword = !widget.showPassword!;
});
},
) : null,
),
minLines: widget.lines ?? 1,
maxLines: widget.lines ?? 1,
maxLength: widget.length,
onChanged: widget.onChanged ?? (_)=>{},
)
],
);
}
}

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({
required this.max,
required this.value,
required this.height,
required this.textSize,
required this.textSecondarySize,
required this.backgroundColor,
required this.foregroundColor,
required this.textColor,}
);
@override
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,64 @@
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 StatelessWidget{
final MessageType? type;
final String message;
const CustomSnackBar({
super.key,
this.type,
required this.message,
});
@override
SnackBar build(BuildContext context) {
late final Color foregroundColor;
late final Color backgroundColor;
late final IconData icon;
switch (type??MessageType.info){
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: Color(0xFF4B4B4B), fontSize: 14)))
],
),
);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
class StepIndicator extends StatelessWidget{
final int total;
final int current;
final Color color;
const StepIndicator({super.key, required this.total, required this.current, required this.color});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List<Widget>.generate(total, (index){
final bool isActive = index < current;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
margin: const EdgeInsets.symmetric(horizontal: 6),
width: 16.0,
height: 16.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? color : Colors.white,
border: Border.all(color: color, width: 2),
),
);
}),
]
);
}
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
class MoneyText extends StatelessWidget {
final String text;
final double size;
final double? secondarySize;
final Color color;
final double height;
const MoneyText({
super.key,
required this.text,
required this.size,
this.secondarySize,
required this.color,
this.height = 1,
});
@override
Widget build(BuildContext context) {
final split = text.contains(".") ? text.split(".") : text.split("");
final mainText = split[0];
var secondaryText = text.contains(".") ? ",${split[1]}" : "${split[1]}";
return Text.rich(TextSpan(
text: mainText,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: size,
color: color,
height: height
),
children: [
TextSpan(
text: secondaryText,
style: TextStyle(
fontWeight: FontWeight.normal,
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

@@ -16,17 +16,21 @@ dependencies:
path: ../utils
flutter_riverpod: ^3.0.3
get_it: ^9.0.5
fonts:
path: ../../packages/fonts
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
golden_toolkit: ^0.15.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:

View File

@@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:design_system/design_system.dart';
void main() {
themePackages();
testGoldens('Step Indicator', (tester) async {
final widget = (int step)=>StepIndicator(total: 3, current: step, color: Colors.blueAccent);
final builder = GoldenBuilder.column()
..addScenario('step -1', widget(-1))
..addScenario('step 0', widget(0))
..addScenario('step 1', widget(1))
..addScenario('step 2', widget(2))
..addScenario('step 3', widget(3))
..addScenario('step 4', widget(4));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'step_indicator');
});
testGoldens('Progress Bar - Small', (tester) async {
final widget = (double value, double max) => ProgressBar(max: max, value: value, height: 24, textSize: 16, textSecondarySize: 12, backgroundColor: Colors.blueGrey, foregroundColor: Colors.blueAccent, textColor: Colors.black87);
final builder = GoldenBuilder.column()
..addScenario('full', widget(100, 100))
..addScenario('empty', widget(0, 100))
..addScenario('half', widget(75, 150))
..addScenario('overflowing', widget(200, 150))
//..addScenario('negative', widget(-20, 83))
..addScenario('tiny value', widget(2.15, 150));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'progress_bar_small');
});
testGoldens('Progress Bar - Large', (tester) async {
final widget = (double value, double max) => ProgressBar(max: max, value: value, height: 83, textSize: 40, textSecondarySize: 24, backgroundColor: Colors.blueGrey, foregroundColor: Colors.blueAccent, textColor: Colors.black87);
final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1)
..addScenario('full', widget(100, 100))
..addScenario('empty', widget(0, 100))
..addScenario('half', widget(75, 150))
..addScenario('overflowing', widget(200, 150))
..addScenario('tiny value', widget(2.15, 150));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'progress_bar_large');
});
testGoldens('Text Field', (tester) async {
final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1)
..addScenario('basic', SizedBox(height: 70, width: 250, child: CustomTextField()))
..addScenario('hint', SizedBox(height: 70, width: 250, child: CustomTextField(hint: "type something")))
..addScenario('label', SizedBox(height: 100, width: 250, child: CustomTextField(hint: "type something", label: "text input")))
..addScenario('numeric', SizedBox(height: 70, width: 250, child: CustomTextField(numeric: true)))
..addScenario('password', SizedBox(height: 70, width: 250, child: CustomTextField(showPassword: false)))
..addScenario('multiline', SizedBox(height: 200, width: 250, child: CustomTextField(lines: 4)));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'textfield');
});
testGoldens('Primary Button', (tester) async {
final widget = ({onPressed, text}) => SizedBox(
height: 70, width: 250,
child:PrimaryButton(onPressed: onPressed, text: text, color: Colors.blueAccent)
);
final builder = GoldenBuilder.grid(columns: 2, widthToHeightRatio: 1)
..addScenario('empty', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, text: "", color: Colors.blueAccent)))
..addScenario('basic', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent)))
..addScenario('round', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, radius: 100, text: "press me", color: Colors.blueAccent)))
..addScenario('small', SizedBox(height: 70, width: 250, child: PrimaryButton(onPressed: ()=>{}, width: 100, text: "press me", color: Colors.blueAccent)));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'primary_button');
});
testGoldens('Text Button', (tester) async {
final tapTarget = Key("tap-target");
final builder = GoldenBuilder.column()
..addScenario('empty', CustomTextButton(onPressed: ()=>{}, text: ""))
..addScenario('basic', CustomTextButton(onPressed: ()=>{}, text: "press me"))
..addScenario('tapped', CustomTextButton(onPressed: ()=>{}, text: "press me", key: tapTarget))
..addScenario('large text', CustomTextButton(onPressed: ()=>{}, text: "press me", size: 40, weight: FontWeight.w500))
..addScenario('small text', CustomTextButton(onPressed: ()=>{}, text: "press me", size: 10))
..addScenario('colored', CustomTextButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent));
await tester.pumpWidgetBuilder(builder.build());
await tester.tap(find.byKey(tapTarget));
await tester.pump();
await screenMatchesGolden(tester, 'text_button');
});
testGoldens('Money Text', (tester) async {
final builder = GoldenBuilder.column()
..addScenario('basic', MoneyText(text: "29.13€", size: 20, color: Colors.blueAccent))
..addScenario('without cents', MoneyText(text: "50€", size: 20, color: Colors.blueAccent))
..addScenario('different sizes', MoneyText(text: "29.13€", size: 30, secondarySize: 15, color: Colors.blueAccent))
..addScenario('different sizes without cents', MoneyText(text: "50€", size: 30, secondarySize: 15, color: Colors.blueAccent));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'money_text');
});
testGoldens('Secondary Button', (tester) async {
final widget = ({onPressed, text}) => SizedBox(
height: 70, width: 250,
child:SecondaryButton(onPressed: onPressed, text: text)
);
final builder = GoldenBuilder.grid(columns: 3, widthToHeightRatio: 1)
..addScenario('empty', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: "")))
..addScenario('text', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: "press me")))
..addScenario('icon', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, icon: Icons.account_circle_outlined)))
..addScenario('colored', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, text: "press me", color: Colors.blueAccent,)))
..addScenario('small', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, width: 100, text: "press me")))
..addScenario('round', SizedBox(height: 70, width: 250, child:SecondaryButton(onPressed: ()=>{}, radius: 100, text: "press me", color: Colors.blueAccent,)));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'secondary_button');
});
final snackbarTest = ({message, type, testName}) {
testGoldens('Snackbar $testName', (tester) async {
const Key tapTarget = Key('tap-target');
final widget = () =>
SizedBox(
width: 800,
height: 600,
child: MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
CustomSnackBar(
message: message,
type: type,
).build(context)
);
},
behavior: HitTestBehavior.opaque,
key: tapTarget,
);
}
),
),
),
);
final builder = DeviceBuilder()
..overrideDevicesForAllScenarios(devices: [
Device(size: Size(750, 550), name: 'base')
])
..addScenario(name: testName, widget: widget());
await tester.pumpWidgetBuilder(builder.build());
await tester.tap(find.byKey(tapTarget));
await screenMatchesGolden(tester, 'snackbar/$testName');
});
};
final shortText = "Mensaje de prueba";
final longText = "Mensaje de prueba largo para comprobar los casos en los que el texto ocupa varias líneas";
snackbarTest(message: "", type: null, testName: "default_empty");
snackbarTest(message: shortText, type: null, testName: "default");
snackbarTest(message: longText, type: null, testName: "default_long_text");
snackbarTest(message: "", type: MessageType.info, testName: "info_empty");
snackbarTest(message: shortText, type: MessageType.info, testName: "info");
snackbarTest(message: longText, type: MessageType.info, testName: "info_long_text");
snackbarTest(message: "", type: MessageType.success, testName: "success_empty");
snackbarTest(message: shortText, type: MessageType.success, testName: "success");
snackbarTest(message: longText, type: MessageType.success, testName: "success_long_text");
snackbarTest(message: "", type: MessageType.warning, testName: "warning_empty");
snackbarTest(message: shortText, type: MessageType.warning, testName: "warning");
snackbarTest(message: longText, type: MessageType.warning, testName: "warning_long_text");
snackbarTest(message: "", type: MessageType.error, testName: "error_empty");
snackbarTest(message: shortText, type: MessageType.error, testName: "error");
snackbarTest(message: longText, type: MessageType.error, testName: "error_long_text");
testGoldens('Dropdown', (tester) async {
final List<Widget> emptyList = [];
final List<Widget> shortList = [Text("A"), Text("B"), Text("C")];
final builder = GoldenBuilder.column()
..addScenario('empty', CustomDropdown(items: emptyList, onChanged: (_)=>{}, width: 200))
..addScenario('initial value', CustomDropdown(items: shortList, value: 1, onChanged: (_)=>{}, width: 300))
..addScenario('hint', CustomDropdown(items: shortList, hint: "choose an option", onChanged: (_)=>{}))
..addScenario('label', CustomDropdown(items: shortList, label: "select", onChanged: (_)=>{}, width: 200));
await tester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(tester, 'dropdown');
});
}

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

@@ -1,8 +1,10 @@
class AppRoutes {
static const login = '/login';
static const signup = '/signup';
static const onboarding = '/onboarding';
static const linkPhone = '/link_phone';
static const phoneCode = '/phone_code';
static const deviceSignup = '/device_signup';
static const recoverPassword = '/recover_password';
static const dashboard = '/dashboard';

View File

@@ -1,6 +1,9 @@
export 'src/models/kid.dart';
export 'src/models/task.dart';
export 'src/models/savings_goal.dart';
export 'src/widgets/line_graph.dart';
export 'src/widgets/deposit_block.dart';
export 'src/screens/connection_error_screen.dart';
export 'src/screens/server_error_screen.dart';
export 'src/screens/no_plan_error_screen.dart';
export 'src/widgets/wallet_balance_block.dart';

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

@@ -0,0 +1,12 @@
class SavingsGoal{
final String name;
final int goal;
final double saved;
const SavingsGoal({
required this.name,
required this.goal,
required this.saved
});
}

View File

@@ -0,0 +1,21 @@
class Task{
final double rewardAmount;
final List<Subtask> subtasks;
const Task({
required this.rewardAmount,
this.subtasks = const []
});
}
class Subtask{
final String name;
final bool completed;
const Subtask({
required this.name,
required this.completed
});
}

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,51 @@ 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: 16,
children: [
Expanded(
child: CustomTextField(
label: "Cantidad",
hint: "0€",
numeric: true,
),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
),
Align(
alignment: Alignment.bottomRight,
child: PrimaryButton(
onPressed: () => {},
text: "Ingresar",
color: theme.getColorFor(ThemeCode.buttonPrimary),
padding: 24,
),
)
],
),
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

@@ -1,13 +1,13 @@
import 'dart:math';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LineGraph extends ConsumerStatefulWidget {
final lines = [
[0, 1, 0, 1, 0, 1, 0],
[1, 0, 1, 0, 1, 0, 1],
];
final lines = [[0,1,0,3,0,1,0],[1,0,1,0,4,0,1]];
late final maxValue = lines.map((x)=>x.reduce(max)).reduce(max);
LineGraph({super.key});
@@ -16,7 +16,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 +32,136 @@ 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(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: theme.getColorFor(ThemeCode.backgroundSecondary),
),
child:
CustomDropdown(
value: timeSpan,
items: [
Text("Hoy", style: TextStyle(fontSize: 14, letterSpacing: 0)),
Text("Esta semana", style: TextStyle(fontSize: 14, letterSpacing: 0)),
Text("Este mes", style: TextStyle(fontSize: 14, letterSpacing: 0))
],
values: ["day", "week", "month"],
onChanged: (value)=>{},
color: Colors.transparent,
width: 151,
),
)
]),
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: List<LineChartBarData>.generate(widget.lines.length, (int i)=>
LineChartBarData(
isCurved: true,
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: theme.getCardColorFor(i)
),
barWidth: 5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
spots: List<FlSpot>.generate(widget.lines[i].length, (int j){
return FlSpot(j.toDouble(), widget.lines[i][j].toDouble());
})
),
),
minX: 0,
maxX: days.length-1,
maxY: widget.maxValue.toDouble(),
minY: 0,
))
)
],
),
);
}
}
}

View File

@@ -0,0 +1,77 @@
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(
max: savingsPlan,
value: savings,
height: 24,
textSize: 16,
textSecondarySize: 12,
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
foregroundColor: theme.getColorFor(ThemeCode.backgroundTertiary),
textColor: theme.getColorFor(ThemeCode.textPrimary)
),
ProgressBar(
max: max,
value: value,
height: 83,
textSize: 40,
textSecondarySize: 24,
backgroundColor: theme.getColorFor(ThemeCode.backgroundTertiary),
foregroundColor: theme.getColorFor(ThemeCode.buttonPrimary),
textColor: theme.getColorFor(ThemeCode.textSecondary)
),
Center(child: Text("Disponible")),
],
),
);
}
}

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: