- created custom text block, dropdown,

- created extract, goals and block card screen.
- generated tests for design system components.
- updated restore password and home screens to 17/11 design.
This commit is contained in:
2025-12-03 15:28:10 +01:00
parent 8201bff0a7
commit 62ffc9ef7c
53 changed files with 3070 additions and 882 deletions

View File

@@ -4,4 +4,8 @@ export 'src/steps/step_indicator.dart';
export 'src/texts/money_text.dart';
export 'src/progress_bars/progress_bar.dart';
export 'src/inputs/textfields.dart';
export 'src/snackbars/snackbar.dart';
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

@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CustomTextField extends ConsumerStatefulWidget{
class CustomTextField extends StatefulWidget{
bool? showPassword;
final bool numeric;
final String hint;
@@ -24,49 +24,60 @@ class CustomTextField extends ConsumerStatefulWidget{
});
@override
ConsumerState<CustomTextField> createState() => CustomTextFieldState();
State<CustomTextField> createState() => CustomTextFieldState();
}
class CustomTextFieldState extends ConsumerState<CustomTextField>{
class CustomTextFieldState extends State<CustomTextField>{
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
return TextFormField(
keyboardType: widget.numeric? TextInputType.number : TextInputType.text,
obscureText: !(widget.showPassword ?? true),
enableSuggestions: widget.showPassword ?? true,
autocorrect: !(widget.showPassword ?? false),
style: TextStyle(color: theme.getColorFor(ThemeCode.buttonSecondary)),
inputFormatters: widget.numeric? [
FilteringTextInputFormatter.digitsOnly
] : [],
decoration: InputDecoration(
counterText: "",
hintText: widget.hint,
labelText: widget.label,
floatingLabelBehavior: FloatingLabelBehavior.always,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: theme.getColorFor(ThemeCode.textPrimary)),
gapPadding: 16
return Column(
spacing: 8,
children: [
?widget.label == '' ? null : Align(
alignment: Alignment.bottomLeft,
child: Text(
widget.label,
style: TextStyle(fontSize: 14, letterSpacing: 0),
)
),
suffixIcon: widget.showPassword!=null ? IconButton(
icon: Icon(widget.showPassword!
? Icons.visibility_off
: Icons.visibility),
onPressed: () {
setState(() {
widget.showPassword = !widget.showPassword!;
});
},
) : null,
),
minLines: widget.lines ?? 1,
maxLines: widget.lines ?? 1,
maxLength: widget.length,
onChanged: widget.onChanged ?? (_)=>{},
TextFormField(
keyboardType: widget.numeric? TextInputType.number : TextInputType.text,
obscureText: !(widget.showPassword ?? true),
enableSuggestions: widget.showPassword ?? true,
autocorrect: !(widget.showPassword ?? false),
style: TextStyle(color: Color(0xFF4B4B4B)),
inputFormatters: widget.numeric? [
FilteringTextInputFormatter.digitsOnly
] : [],
decoration: InputDecoration(
counterText: "",
hintText: widget.hint,
//labelText: widget.label,
//floatingLabelBehavior: FloatingLabelBehavior.always,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Color(0xFF4B4B4B)),
gapPadding: 16
),
suffixIcon: widget.showPassword!=null ? IconButton(
icon: Icon(widget.showPassword!
? Icons.visibility_off
: Icons.visibility),
onPressed: () {
setState(() {
widget.showPassword = !widget.showPassword!;
});
},
) : null,
),
minLines: widget.lines ?? 1,
maxLines: widget.lines ?? 1,
maxLength: widget.length,
onChanged: widget.onChanged ?? (_)=>{},
)
],
);
}
}

View File

@@ -12,15 +12,15 @@ class ProgressBar extends ConsumerWidget{
final Color foregroundColor;
final Color textColor;
const ProgressBar(
this.max,
this.value,
this.height,
this.textSize,
this.textSecondarySize,
this.backgroundColor,
this.foregroundColor,
this.textColor,
const ProgressBar({
required this.max,
required this.value,
required this.height,
required this.textSize,
required this.textSecondarySize,
required this.backgroundColor,
required this.foregroundColor,
required this.textColor,}
);
@override

View File

@@ -9,25 +9,24 @@ enum MessageType {
success
}
class CustomSnackBar extends ConsumerWidget{
final MessageType type;
class CustomSnackBar extends StatelessWidget{
final MessageType? type;
final String message;
CustomSnackBar({
const CustomSnackBar({
super.key,
this.type = MessageType.info,
this.type,
required this.message,
});
@override
SnackBar build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
SnackBar build(BuildContext context) {
late final Color foregroundColor;
late final Color backgroundColor;
late final IconData icon;
switch (type){
switch (type??MessageType.info){
case MessageType.info:
backgroundColor = Color(0xFFE3EFFD);
foregroundColor = Color(0xFF1F4ECF);
@@ -57,7 +56,7 @@ class CustomSnackBar extends ConsumerWidget{
spacing: 8,
children: [
Icon(icon, color: foregroundColor),
Expanded(child: Text(message, style: TextStyle(color: theme.getColorFor(ThemeCode.textPrimary), fontSize: 14)))
Expanded(child: Text(message, style: TextStyle(color: Color(0xFF4B4B4B), fontSize: 14)))
],
),
);

View File

@@ -18,11 +18,12 @@ class MoneyText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final units = text.split(".")[0];
final cents = ",${text.split(".")[1]}";
final split = text.contains(".") ? text.split(".") : text.split("");
final mainText = split[0];
var secondaryText = text.contains(".") ? ",${split[1]}" : "${split[1]}";
return Text.rich(TextSpan(
text: units,
text: mainText,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: size,
@@ -31,7 +32,7 @@ class MoneyText extends StatelessWidget {
),
children: [
TextSpan(
text: cents,
text: secondaryText,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: secondarySize ?? size,

View File

@@ -20,12 +20,14 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
golden_toolkit: ^0.15.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:

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

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

View File

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

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

@@ -32,7 +32,7 @@ class DepositBlock extends ConsumerWidget {
spacing: 16,
children: [
Row(
spacing: 10,
spacing: 16,
children: [
Expanded(
child: CustomTextField(
@@ -41,20 +41,15 @@ class DepositBlock extends ConsumerWidget {
numeric: true,
),
),
FilledButton(
onPressed: () => {},
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll<Color>(
theme.getColorFor(ThemeCode.buttonPrimary),
),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(18))
))
Align(
alignment: Alignment.bottomRight,
child: PrimaryButton(
onPressed: () => {},
text: "Ingresar",
color: theme.getColorFor(ThemeCode.buttonPrimary),
padding: 24,
),
child: SizedBox(
height: 60,
child: Center(child: Text("Ingresar", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)))),
),
)
],
),
Align(

View File

@@ -1,10 +1,13 @@
import 'dart:math';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LineGraph extends ConsumerStatefulWidget {
final lines = [[0,1,0,1,0,1,0],[1,0,1,0,1,0,1]];
final lines = [[0,1,0,3,0,1,0],[1,0,1,0,4,0,1]];
late final maxValue = lines.map((x)=>x.reduce(max)).reduce(max);
LineGraph({super.key});
@@ -42,25 +45,22 @@ class LineGraphState extends ConsumerState<LineGraph> {
Text("Gastos", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 18)),
Spacer(),
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: theme.getColorFor(ThemeCode.backgroundSecondary),
),
child: DropdownButton(
underline: Container(),
child:
CustomDropdown(
value: timeSpan,
onChanged: (String? value) {
setState(() {
timeSpan = value;
});
},
dropdownColor: theme.getColorFor(ThemeCode.backgroundPrimary),
items: [
DropdownMenuItem(value: "day", child: Text("Hoy", style: TextStyle(fontSize: 14, letterSpacing: 0))),
DropdownMenuItem(value: "week", child: Text("Esta semana", style: TextStyle(fontSize: 14, letterSpacing: 0))),
DropdownMenuItem(value: "month", child: Text("Este mes", style: TextStyle(fontSize: 14, letterSpacing: 0))),
]
Text("Hoy", style: TextStyle(fontSize: 14, letterSpacing: 0)),
Text("Esta semana", style: TextStyle(fontSize: 14, letterSpacing: 0)),
Text("Este mes", style: TextStyle(fontSize: 14, letterSpacing: 0))
],
values: ["day", "week", "month"],
onChanged: (value)=>{},
color: Colors.transparent,
width: 151,
),
)
]),
@@ -136,33 +136,26 @@ class LineGraphState extends ConsumerState<LineGraph> {
top: const BorderSide(color: Colors.transparent),
),
),
lineBarsData: [
lineBarsData: List<LineChartBarData>.generate(widget.lines.length, (int i)=>
LineChartBarData(
isCurved: true,
color: Colors.pink,
barWidth: 5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
spots: List<FlSpot>.generate(days.length, (int index){
return FlSpot(index.toDouble(), (index+1)%2);
})
isCurved: true,
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: theme.getCardColorFor(i)
),
barWidth: 5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
spots: List<FlSpot>.generate(widget.lines[i].length, (int j){
return FlSpot(j.toDouble(), widget.lines[i][j].toDouble());
})
),
LineChartBarData(
isCurved: true,
color: Colors.cyan,
barWidth: 5,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
spots: List<FlSpot>.generate(days.length, (int index){
return FlSpot(index.toDouble(), index%2);
})
),
],
),
minX: 0,
maxX: days.length-1,
maxY: 1,
maxY: widget.maxValue.toDouble(),
minY: 0,
))
)

View File

@@ -49,8 +49,26 @@ class WalletBalanceBlock extends ConsumerWidget {
Spacer(),
Text("$savingsPlan")
]),
ProgressBar(savingsPlan, savings, 24, 16, 12, theme.getColorFor(ThemeCode.backgroundSecondary), theme.getColorFor(ThemeCode.backgroundTertiary), theme.getColorFor(ThemeCode.textPrimary)),
ProgressBar(max, value, 83, 40, 24, theme.getColorFor(ThemeCode.backgroundTertiary), theme.getColorFor(ThemeCode.buttonPrimary), theme.getColorFor(ThemeCode.textSecondary)),
ProgressBar(
max: savingsPlan,
value: savings,
height: 24,
textSize: 16,
textSecondarySize: 12,
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
foregroundColor: theme.getColorFor(ThemeCode.backgroundTertiary),
textColor: theme.getColorFor(ThemeCode.textPrimary)
),
ProgressBar(
max: max,
value: value,
height: 83,
textSize: 40,
textSecondarySize: 24,
backgroundColor: theme.getColorFor(ThemeCode.backgroundTertiary),
foregroundColor: theme.getColorFor(ThemeCode.buttonPrimary),
textColor: theme.getColorFor(ThemeCode.textSecondary)
),
Center(child: Text("Disponible")),
],
),