Files
sf-app-platform/modules/home/lib/src/features/goals/goals_screen.dart

914 lines
33 KiB
Dart

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:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import '../../card_colors.dart';
import '../child_wallet/child_data_provider.dart';
import 'goals_view_model.dart';
class GoalsScreen extends ConsumerWidget {
final String childId;
final NavigationContract navigation;
const GoalsScreen({
super.key,
required this.childId,
required this.navigation,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final viewState = ref.watch(goalsViewModelProvider(childId));
final cardStatus = ref.watch(childDataProvider(childId)).cardStatus;
if (viewState.isLoading) {
return const Scaffold(body: Center(child: AppLoadingIndicator()));
}
if (viewState.errorMessage.isNotEmpty) {
return Scaffold(
body: Center(child: Text('Error: ${viewState.errorMessage}')),
);
}
final childProfile = viewState.childProfile!;
final childName = childProfile.firstName;
final availableBalance = viewState.childWallet?.authorizedBalance ?? 0;
return WalletManagementLayout(
childName: childName,
balance: availableBalance,
cardColors: cardColorsFor(theme: theme, carrierGenre: viewState.device?.carrierGenre, cardStatus: cardStatus),
navigation: navigation,
children: [
SectionContainer(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
spacing: 24,
children: [
Row(
spacing: 8,
children: [
Text(
context.translate(I18n.goalsTitle),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
Icon(Icons.workspace_premium),
Spacer(),
Text(
context.translate(I18n.goalsOnlyFullPlan),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
],
),
Text(
context.translate(I18n.goalsTeachSavings),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
],
),
SavingsBlock(
fullPlan: viewState.fullPlan,
savings: viewState.savingsGoals,
),
TasksBlock(
fullPlan: viewState.fullPlan,
tasks: viewState.tasks,
childId: childId,
),
],
footer: Container(),
);
}
}
class SavingsBlock extends ConsumerStatefulWidget {
final bool fullPlan;
final List<SavingsGoal> 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(
context.translate(I18n.goalsSavings),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200,
child: Text(
context.translate(I18n.goalsTeachSavings),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
),
if (fullPlan)
Column(
spacing: 24,
children: [
Align(
alignment: Alignment.topLeft,
child: SecondaryButton(
onPressed: () => setState(() {
showNew = true;
}),
text: context.translate(I18n.goalsCreateSavingsGoal),
radius: 8,
height: 44,
size: 14,
width: 230,
),
),
Align(
alignment: Alignment.topLeft,
child: TextButton(
onPressed: () => {},
style: ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
),
child: Text(
context.translate(I18n.goalsViewSavingsStatus),
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(context.translate(I18n.goalsSubscribeFullPlan)),
],
),
),
],
),
],
),
);
var editBlock = ({create, index}) => Column(
spacing: 24,
children: [
CustomTextField(
hint: create
? context.translate(I18n.goalsSavingsNameHint)
: widget.savings[index].name,
label: context.translate(I18n.goalsSavingsReasonLabel),
lines: create ? 2 : 1,
),
CustomTextField(
hint: "30\u20AC",
label: context.translate(I18n.goalsSavingsAmountLabel),
keyboardType: TextInputType.number,
),
CheckboxListTile(
value: false,
onChanged: (_) => {},
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
title: Text(
context.translate(I18n.goalsAutoSavingsFromAllowance),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
CustomTextField(
hint: "2\u20AC",
label: context.translate(I18n.goalsSelectAmount),
keyboardType: TextInputType.number,
),
CheckboxListTile(
value: false,
onChanged: (_) => {},
checkboxScaleFactor: 2,
controlAffinity: ListTileControlAffinity.leading,
activeColor: theme.getColorFor(ThemeCode.buttonPrimary),
contentPadding: EdgeInsets.zero,
title: Text(
context.translate(I18n.goalsAutoSendOnGoal),
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
),
Column(
spacing: 10,
children: [
Text(
context.translate(I18n.goalsDefaultMessagePrefix),
style: TextStyle(fontSize: 16, letterSpacing: 0),
),
Text(
context.translate(I18n.goalsSavingsDefaultMessage),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
],
),
Column(
spacing: 8,
children: [
CustomTextField(
hint: context.translate(I18n.allowanceMessageHint),
label: context.translate(I18n.goalsWriteOtherMessage),
lines: 4,
length: 150,
),
Row(
spacing: 4,
children: [
Icon(Icons.info_outline, size: 16),
Text(
context.translate(I18n.allowanceMaxChars, args: {'count': '150'}),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
],
),
],
),
Column(
spacing: 24,
children: [
PrimaryButton(
onPressed: () => {},
text: context.translate(I18n.goalsSaveChanges),
color: theme.getColorFor(ThemeCode.buttonPrimary),
),
TextButton(
onPressed: () {
if (create) {
setState(() {
showNew = false;
});
} else {
setState(() {
showEdit[index] = false;
});
}
},
child: Text(
context.translate(I18n.cancel),
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(
context.translate(I18n.goalsSavings),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
Text(
context.translate(I18n.goalsTeachSavings),
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),
),
child: Column(
spacing: 24,
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
context.translate(I18n.goalsSavings),
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(
context.translate(I18n.goalsSavingsFor),
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}\u20AC",
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(context.translate(I18n.goalsSaved))),
if (!showAdd[index])
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.zero,
),
),
onPressed: () => setState(() {
showAdd[index] = true;
}),
child: Text(
context.translate(I18n.goalsAddExtraMoney),
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
letterSpacing: 0,
),
),
),
if (showAdd[index])
CustomTextField(
hint: "5\u20AC",
keyboardType: TextInputType.number,
),
],
),
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(
context.translate(I18n.homeEdit),
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(
context.translate(I18n.goalsDelete),
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(
context.translate(I18n.goalsCreateAnotherSavings),
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;
final String childId;
@override
const TasksBlock({
super.key,
required this.fullPlan,
required this.tasks,
required this.childId,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => TasksBlockState();
}
class TasksBlockState extends ConsumerState<TasksBlock> {
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
final viewModel = ref.read(goalsViewModelProvider(widget.childId).notifier);
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(
context.translate(I18n.goalsTasks),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200,
child: Text(
context.translate(I18n.goalsTasksDescription),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
),
if (fullPlan)
Align(
alignment: Alignment.topLeft,
child: SecondaryButton(
onPressed: () => {},
text: context.translate(I18n.goalsCreateTaskList),
size: 14,
width: 190,
height: 44,
),
),
if (!fullPlan)
TextButton(
onPressed: () => {},
child: Row(
spacing: 4,
children: [
Icon(Icons.workspace_premium, size: 16),
Text(context.translate(I18n.goalsSubscribeFullPlan)),
],
),
),
],
),
],
),
);
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(
context.translate(I18n.goalsTasks),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
),
Text(
context.translate(I18n.goalsTasksDescription),
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(
context.translate(I18n.goalsTaskList),
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: (_) =>
viewModel.toggleSubtaskCompleted(i, j),
),
),
TextButton(
style: ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
),
onPressed: () => {},
child: Align(
alignment: Alignment.topLeft,
child: Text(
context.translate(I18n.goalsAddTask),
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(
context.translate(I18n.goalsRewardForCompletion),
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}\u20AC",
size: 40,
secondarySize: 24,
color: theme.getColorFor(
ThemeCode.buttonPrimary,
),
),
],
),
],
),
),
Column(
spacing: 8,
children: [
Align(
alignment: Alignment.topLeft,
child: Text(
context.translate(I18n.goalsTasksCompletedMessagePrefix),
style: TextStyle(fontSize: 14, letterSpacing: 0),
),
),
Align(
alignment: Alignment.topLeft,
child: Text(
context.translate(I18n.goalsTasksCompletedMessage),
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(
context.translate(I18n.goalsDeleteTaskList),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
),
],
),
),
),
],
),
),
),
SecondaryButton(
onPressed: () => {},
text: context.translate(I18n.goalsCreateNewTaskList),
size: 14,
),
],
),
);
} else {
return emptyBlock(fullPlan: true);
}
} else {
return emptyBlock(fullPlan: false);
}
}
}