feat: Refactor client home widgets to use SectionLayout and add titles

- Updated ActionsWidget, CoverageWidget, SpendingWidget, ReorderWidget, and LiveActivityWidget to utilize SectionLayout for consistent layout structure.
- Introduced SectionHeader for displaying titles and optional actions in sections.
- Added ClientHomeBody, ClientHomeEditModeBody, ClientHomeNormalModeBody, and ClientHomeErrorState for improved state management and UI separation.
- Enhanced dashboard widget builder to support edit mode and error handling.
This commit is contained in:
Achintha Isuru
2026-03-03 22:00:42 -05:00
parent 2d20254ce3
commit 6f2a195724
13 changed files with 721 additions and 529 deletions

View File

@@ -1,15 +1,12 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import '../blocs/client_home_bloc.dart';
import '../blocs/client_home_event.dart';
import '../blocs/client_home_state.dart';
import '../widgets/client_home_body.dart';
import '../widgets/client_home_edit_banner.dart';
import '../widgets/client_home_header.dart';
import '../widgets/dashboard_widget_builder.dart';
/// The main Home page for client users.
///
@@ -21,133 +18,23 @@ class ClientHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TranslationsClientHomeEn i18n = t.client_home;
return BlocProvider<ClientHomeBloc>(
create: (BuildContext context) => Modular.get<ClientHomeBloc>(),
child: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
ClientHomeHeader(i18n: i18n),
ClientHomeEditBanner(i18n: i18n),
Flexible(
child: BlocConsumer<ClientHomeBloc, ClientHomeState>(
listener: (BuildContext context, ClientHomeState state) {
if (state.status == ClientHomeStatus.error &&
state.errorMessage != null) {
UiSnackbar.show(
context,
message: translateErrorKey(state.errorMessage!),
type: UiSnackbarType.error,
);
}
},
builder: (BuildContext context, ClientHomeState state) {
if (state.status == ClientHomeStatus.error) {
return _buildErrorState(context, state);
}
if (state.isEditMode) {
return _buildEditModeList(context, state);
}
return _buildNormalModeList(state);
},
ClientHomeHeader(
i18n: t.client_home,
),
ClientHomeEditBanner(
i18n: t.client_home,
),
const ClientHomeBody(),
],
),
),
),
);
}
/// Builds the widget list in edit mode with drag-and-drop support.
Widget _buildEditModeList(BuildContext context, ClientHomeState state) {
return ReorderableListView(
padding: const EdgeInsets.fromLTRB(
UiConstants.space4,
0,
UiConstants.space4,
100,
),
onReorder: (int oldIndex, int newIndex) {
BlocProvider.of<ClientHomeBloc>(
context,
).add(ClientHomeWidgetReordered(oldIndex, newIndex));
},
children: state.widgetOrder.map((String id) {
return Container(
key: ValueKey<String>(id),
margin: const EdgeInsets.only(bottom: UiConstants.space4),
child: DashboardWidgetBuilder(id: id, state: state, isEditMode: true),
);
}).toList(),
);
}
/// Builds the widget list in normal mode with visibility filters.
Widget _buildNormalModeList(ClientHomeState state) {
final List<String> visibleWidgets = state.widgetOrder.where((String id) {
if (id == 'reorder' && state.reorderItems.isEmpty) {
return false;
}
return state.widgetVisibility[id] ?? true;
}).toList();
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return const Divider(color: UiColors.border, height: 0.1);
},
itemCount: visibleWidgets.length,
itemBuilder: (BuildContext context, int index) {
final String id = visibleWidgets[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (index != 0) const SizedBox(height: UiConstants.space8),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
),
child: DashboardWidgetBuilder(
id: id,
state: state,
isEditMode: false,
),
),
const SizedBox(height: UiConstants.space8),
],
);
},
);
}
Widget _buildErrorState(BuildContext context, ClientHomeState state) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(
UiIcons.error,
size: 48,
color: UiColors.error,
),
const SizedBox(height: UiConstants.space4),
Text(
state.errorMessage != null
? translateErrorKey(state.errorMessage!)
: 'An error occurred',
style: UiTypography.body1m.textError,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space4),
UiButton.secondary(
text: 'Retry',
onPressed: () =>
BlocProvider.of<ClientHomeBloc>(context).add(ClientHomeStarted()),
),
],
),
);
}
}

View File

@@ -4,10 +4,19 @@ import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'section_layout.dart';
/// A widget that displays quick actions for the client.
class ActionsWidget extends StatelessWidget {
/// Creates an [ActionsWidget].
const ActionsWidget({super.key, this.subtitle});
const ActionsWidget({
super.key,
this.title,
this.subtitle,
});
/// Optional title for the section.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
@@ -17,7 +26,9 @@ class ActionsWidget extends StatelessWidget {
// Check if client_home exists in t
final TranslationsClientHomeActionsEn i18n = t.client_home.actions;
return Row(
return SectionLayout(
title: title,
child: Row(
spacing: UiConstants.space4,
children: <Widget>[
Expanded(
@@ -49,6 +60,7 @@ class ActionsWidget extends StatelessWidget {
),
),
],
),
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/client_home_bloc.dart';
import '../blocs/client_home_state.dart';
import 'client_home_edit_mode_body.dart';
import 'client_home_error_state.dart';
import 'client_home_normal_mode_body.dart';
/// Main body widget for the client home page.
///
/// Manages the state transitions between error, edit mode, and normal mode views.
class ClientHomeBody extends StatelessWidget {
/// Creates a [ClientHomeBody].
const ClientHomeBody({super.key});
@override
Widget build(BuildContext context) {
return Flexible(
child: BlocConsumer<ClientHomeBloc, ClientHomeState>(
listener: (BuildContext context, ClientHomeState state) {
if (state.status == ClientHomeStatus.error &&
state.errorMessage != null) {
UiSnackbar.show(
context,
message: translateErrorKey(state.errorMessage!),
type: UiSnackbarType.error,
);
}
},
builder: (BuildContext context, ClientHomeState state) {
if (state.status == ClientHomeStatus.error) {
return ClientHomeErrorState(state: state);
}
if (state.isEditMode) {
return ClientHomeEditModeBody(state: state);
}
return ClientHomeNormalModeBody(state: state);
},
),
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/client_home_bloc.dart';
import '../blocs/client_home_event.dart';
import '../blocs/client_home_state.dart';
import 'dashboard_widget_builder.dart';
/// Widget that displays the home dashboard in edit mode with drag-and-drop support.
///
/// Allows users to reorder and rearrange dashboard widgets.
class ClientHomeEditModeBody extends StatelessWidget {
/// The current home state.
final ClientHomeState state;
/// Creates a [ClientHomeEditModeBody].
const ClientHomeEditModeBody({
required this.state,
super.key,
});
@override
Widget build(BuildContext context) {
return ReorderableListView(
padding: const EdgeInsets.fromLTRB(
UiConstants.space4,
0,
UiConstants.space4,
100,
),
onReorder: (int oldIndex, int newIndex) {
BlocProvider.of<ClientHomeBloc>(context)
.add(ClientHomeWidgetReordered(oldIndex, newIndex));
},
children: state.widgetOrder.map((String id) {
return Container(
key: ValueKey<String>(id),
margin: const EdgeInsets.only(bottom: UiConstants.space4),
child: DashboardWidgetBuilder(
id: id,
state: state,
isEditMode: true,
),
);
}).toList(),
);
}
}

View File

@@ -0,0 +1,52 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/client_home_bloc.dart';
import '../blocs/client_home_event.dart';
import '../blocs/client_home_state.dart';
/// Widget that displays an error state for the client home page.
///
/// Shows an error message with a retry button when data fails to load.
class ClientHomeErrorState extends StatelessWidget {
/// The current home state containing error information.
final ClientHomeState state;
/// Creates a [ClientHomeErrorState].
const ClientHomeErrorState({
required this.state,
super.key,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(
UiIcons.error,
size: 48,
color: UiColors.error,
),
const SizedBox(height: UiConstants.space4),
Text(
state.errorMessage != null
? translateErrorKey(state.errorMessage!)
: 'An error occurred',
style: UiTypography.body1m.textError,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space4),
UiButton.secondary(
text: 'Retry',
onPressed: () =>
BlocProvider.of<ClientHomeBloc>(context).add(ClientHomeStarted()),
),
],
),
);
}
}

View File

@@ -0,0 +1,56 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import '../blocs/client_home_state.dart';
import 'dashboard_widget_builder.dart';
/// Widget that displays the home dashboard in normal mode.
///
/// Shows visible dashboard widgets in a vertical scrollable list with dividers.
class ClientHomeNormalModeBody extends StatelessWidget {
/// The current home state.
final ClientHomeState state;
/// Creates a [ClientHomeNormalModeBody].
const ClientHomeNormalModeBody({
required this.state,
super.key,
});
@override
Widget build(BuildContext context) {
final List<String> visibleWidgets = state.widgetOrder.where((String id) {
if (id == 'reorder' && state.reorderItems.isEmpty) {
return false;
}
return state.widgetVisibility[id] ?? true;
}).toList();
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return const Divider(color: UiColors.border, height: 0.1);
},
itemCount: visibleWidgets.length,
itemBuilder: (BuildContext context, int index) {
final String id = visibleWidgets[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (index != 0) const SizedBox(height: UiConstants.space8),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
),
child: DashboardWidgetBuilder(
id: id,
state: state,
isEditMode: false,
),
),
const SizedBox(height: UiConstants.space8),
],
);
},
);
}
}

View File

@@ -2,6 +2,8 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'section_layout.dart';
/// A widget that displays the daily coverage metrics.
class CoverageWidget extends StatelessWidget {
/// Creates a [CoverageWidget].
@@ -10,6 +12,7 @@ class CoverageWidget extends StatelessWidget {
this.totalNeeded = 0,
this.totalConfirmed = 0,
this.coveragePercent = 0,
this.title,
this.subtitle,
});
@@ -22,63 +25,20 @@ class CoverageWidget extends StatelessWidget {
/// The percentage of coverage (0-100).
final int coveragePercent;
/// Optional title for the section.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
@override
Widget build(BuildContext context) {
Color backgroundColor;
Color textColor;
if (coveragePercent == 100) {
backgroundColor = UiColors.tagActive;
textColor = UiColors.textSuccess;
} else if (coveragePercent >= 40) {
backgroundColor = UiColors.tagPending;
textColor = UiColors.textWarning;
} else {
backgroundColor = UiColors.tagError;
textColor = UiColors.textError;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
t.client_home.dashboard.todays_coverage,
style: UiTypography.footnote1b.copyWith(
color: UiColors.textPrimary,
letterSpacing: 0.5,
),
),
if (totalNeeded > 0 || totalConfirmed > 0 || coveragePercent > 0)
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical:
2, // 2px is not in metrics, using hardcoded for small tweaks or space0/space1
),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: UiConstants.radiusLg,
),
child: Text(
t.client_home.dashboard.percent_covered(
percent: coveragePercent,
),
style: UiTypography.footnote2b.copyWith(color: textColor),
),
),
],
),
if (subtitle != null) ...<Widget>[
Text(subtitle!, style: UiTypography.body2r.textSecondary),
],
const SizedBox(height: UiConstants.space6),
Row(
return SectionLayout(
title: title,
action: totalNeeded > 0 || totalConfirmed > 0 || coveragePercent > 0
? t.client_home.dashboard.percent_covered(percent: coveragePercent)
: null,
child: Row(
children: <Widget>[
Expanded(
child: _MetricCard(
@@ -100,6 +60,7 @@ class CoverageWidget extends StatelessWidget {
),
),
const SizedBox(width: UiConstants.space2),
if (totalConfirmed != 0)
Expanded(
child: _MetricCard(
icon: UiIcons.error,
@@ -111,7 +72,6 @@ class CoverageWidget extends StatelessWidget {
),
],
),
],
);
}
}

View File

@@ -35,7 +35,7 @@ class DashboardWidgetBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TranslationsClientHomeWidgetsEn i18n = t.client_home.widgets;
final Widget widgetContent = _buildWidgetContent(context);
final Widget widgetContent = _buildWidgetContent(context, i18n);
if (isEditMode) {
return DraggableWidgetWrapper(
@@ -55,21 +55,27 @@ class DashboardWidgetBuilder extends StatelessWidget {
}
/// Builds the actual widget content based on the widget ID.
Widget _buildWidgetContent(BuildContext context) {
Widget _buildWidgetContent(BuildContext context, TranslationsClientHomeWidgetsEn i18n) {
final String title = _getWidgetTitle(i18n);
// Only show subtitle in normal mode
final String? subtitle = !isEditMode ? _getWidgetSubtitle(id) : null;
switch (id) {
case 'actions':
return ActionsWidget(subtitle: subtitle);
return ActionsWidget(title: title, subtitle: subtitle);
case 'reorder':
return ReorderWidget(orders: state.reorderItems, subtitle: subtitle);
return ReorderWidget(
orders: state.reorderItems,
title: title,
subtitle: subtitle,
);
case 'spending':
return SpendingWidget(
weeklySpending: state.dashboardData.weeklySpending,
next7DaysSpending: state.dashboardData.next7DaysSpending,
weeklyShifts: state.dashboardData.weeklyShifts,
next7DaysScheduled: state.dashboardData.next7DaysScheduled,
title: title,
subtitle: subtitle,
);
case 'coverage':
@@ -82,11 +88,13 @@ class DashboardWidgetBuilder extends StatelessWidget {
100)
.toInt()
: 0,
title: title,
subtitle: subtitle,
);
case 'liveActivity':
return LiveActivityWidget(
onViewAllPressed: () => Modular.to.toClientCoverage(),
title: title,
subtitle: subtitle,
);
default:

View File

@@ -1,9 +1,10 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'coverage_dashboard.dart';
import 'section_layout.dart';
/// A widget that displays live activity information.
class LiveActivityWidget extends StatefulWidget {
@@ -12,11 +13,15 @@ class LiveActivityWidget extends StatefulWidget {
const LiveActivityWidget({
super.key,
required this.onViewAllPressed,
this.title,
this.subtitle
});
/// Callback when "View all" is pressed.
final VoidCallback onViewAllPressed;
/// Optional title for the section.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
@@ -106,37 +111,11 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
Widget build(BuildContext context) {
final TranslationsClientHomeEn i18n = t.client_home;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
i18n.widgets.live_activity.toUpperCase(),
style: UiTypography.footnote1b.textSecondary.copyWith(
letterSpacing: 0.5,
),
),
GestureDetector(
onTap: widget.onViewAllPressed,
child: Text(
i18n.dashboard.view_all,
style: UiTypography.footnote1m.copyWith(
color: UiColors.primary,
),
),
),
],
),
if (widget.subtitle != null) ...<Widget>[
Text(
widget.subtitle!,
style: UiTypography.body2r.textSecondary,
),
],
const SizedBox(height: UiConstants.space6),
FutureBuilder<_LiveActivityData>(
return SectionLayout(
title: widget.title,
action: i18n.dashboard.view_all,
onAction: widget.onViewAllPressed,
child: FutureBuilder<_LiveActivityData>(
future: _liveActivityFuture,
builder: (BuildContext context,
AsyncSnapshot<_LiveActivityData> snapshot) {
@@ -172,7 +151,6 @@ class _LiveActivityWidgetState extends State<LiveActivityWidget> {
);
},
),
],
);
}
}

View File

@@ -5,14 +5,24 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'section_layout.dart';
/// A widget that allows clients to reorder recent shifts.
class ReorderWidget extends StatelessWidget {
/// Creates a [ReorderWidget].
const ReorderWidget({super.key, required this.orders, this.subtitle});
const ReorderWidget({
super.key,
required this.orders,
this.title,
this.subtitle,
});
/// Recent completed orders for reorder.
final List<ReorderItem> orders;
/// Optional title for the section.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
@@ -26,21 +36,9 @@ class ReorderWidget extends StatelessWidget {
final List<ReorderItem> recentOrders = orders;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
i18n.title,
style: UiTypography.footnote1b.textSecondary.copyWith(
letterSpacing: 0.5,
),
),
if (subtitle != null) ...<Widget>[
const SizedBox(height: UiConstants.space1),
Text(subtitle!, style: UiTypography.body2r.textSecondary),
],
const SizedBox(height: UiConstants.space2),
SizedBox(
return SectionLayout(
title: title,
child: SizedBox(
height: 164,
child: ListView.separated(
scrollDirection: Axis.horizontal,
@@ -166,7 +164,6 @@ class ReorderWidget extends StatelessWidget {
},
),
),
],
);
}

View File

@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart';
/// Section header widget for home page sections, using design system tokens.
class SectionHeader extends StatelessWidget {
/// Section title
final String title;
/// Optional subtitle
final String? subtitle;
/// Optional action label
final String? action;
/// Optional action callback
final VoidCallback? onAction;
/// Creates a [SectionHeader].
const SectionHeader({
super.key,
required this.title,
this.subtitle,
this.action,
this.onAction,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: action != null
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(title, style: UiTypography.body1b),
if (onAction != null)
GestureDetector(
onTap: onAction,
child: Row(
children: <Widget>[
Text(
action ?? '',
style: UiTypography.body3r.textSecondary,
),
const Icon(
UiIcons.chevronRight,
size: UiConstants.space4,
color: UiColors.iconSecondary,
),
],
),
)
else
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical: 2,
),
decoration: BoxDecoration(
color: UiColors.primary.withValues(alpha: 0.08),
borderRadius: UiConstants.radiusMd,
border: Border.all(
color: UiColors.primary,
width: 0.5,
),
),
child: Text(
action!,
style: UiTypography.body3r.primary,
),
),
],
)
: Text(title, style: UiTypography.body1b),
),
],
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: Text(
subtitle!,
style: UiTypography.body2r.textSecondary,
),
),
],
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'section_header.dart';
/// A common layout widget for home page sections.
///
/// Provides consistent structure with optional header and content area.
/// Use this to ensure all sections follow the same layout pattern.
class SectionLayout extends StatelessWidget {
/// Creates a [SectionLayout].
const SectionLayout({
this.title,
this.subtitle,
this.action,
this.onAction,
required this.child,
this.contentPadding,
super.key,
});
/// The title of the section, displayed in the header.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
/// Optional action text/widget to display on the right side of the header.
final String? action;
/// Optional callback when action is tapped.
final VoidCallback? onAction;
/// The main content of the section.
final Widget child;
/// Optional padding for the content area.
/// Defaults to no padding.
final EdgeInsetsGeometry? contentPadding;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (title != null)
Padding(
padding: contentPadding ?? EdgeInsets.zero,
child: SectionHeader(
title: title!,
subtitle: subtitle,
action: action,
onAction: onAction,
),
),
const SizedBox(height: UiConstants.space2),
child,
],
);
}
}

View File

@@ -2,6 +2,8 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'section_layout.dart';
/// A widget that displays spending insights for the client.
class SpendingWidget extends StatelessWidget {
@@ -12,6 +14,7 @@ class SpendingWidget extends StatelessWidget {
required this.next7DaysSpending,
required this.weeklyShifts,
required this.next7DaysScheduled,
this.title,
this.subtitle,
});
/// The spending this week.
@@ -26,30 +29,17 @@ class SpendingWidget extends StatelessWidget {
/// The number of scheduled shifts for next 7 days.
final int next7DaysScheduled;
/// Optional title for the section.
final String? title;
/// Optional subtitle for the section.
final String? subtitle;
@override
Widget build(BuildContext context) {
final TranslationsClientHomeEn i18n = t.client_home;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
i18n.widgets.spending.toUpperCase(),
style: UiTypography.footnote1b.textSecondary.copyWith(
letterSpacing: 0.5,
),
),
if (subtitle != null) ...<Widget>[
Text(
subtitle!,
style: UiTypography.body2r.textSecondary,
),
],
const SizedBox(height: UiConstants.space6),
Container(
return SectionLayout(
title: title,
child: Container(
padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration(
gradient: LinearGradient(
@@ -136,7 +126,6 @@ class SpendingWidget extends StatelessWidget {
],
),
),
],
);
}
}