refactor: modularize view orders page by extracting list, empty, and error states into dedicated widgets.

This commit is contained in:
Achintha Isuru
2026-02-21 21:22:53 -05:00
parent 83cf5db390
commit 5865e3e596
5 changed files with 239 additions and 157 deletions

View File

@@ -2,15 +2,15 @@ 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 'package:intl/intl.dart';
import 'package:core_localization/core_localization.dart';
import 'package:krow_core/core.dart';
import '../blocs/view_orders_cubit.dart';
import '../blocs/view_orders_state.dart';
import 'package:krow_domain/krow_domain.dart';
import '../widgets/view_order_card.dart';
import '../widgets/view_orders_header.dart';
import '../widgets/view_orders_empty_state.dart';
import '../widgets/view_orders_error_state.dart';
import '../widgets/view_orders_list.dart';
/// The main page for viewing client orders.
///
@@ -38,7 +38,7 @@ class ViewOrdersPage extends StatelessWidget {
class ViewOrdersView extends StatefulWidget {
/// Creates a [ViewOrdersView].
const ViewOrdersView({super.key, this.initialDate});
/// The initial date to display orders for.
final DateTime? initialDate;
@@ -69,7 +69,8 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
@override
void didUpdateWidget(ViewOrdersView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialDate != oldWidget.initialDate && widget.initialDate != null) {
if (widget.initialDate != oldWidget.initialDate &&
widget.initialDate != null) {
_cubit?.jumpToDate(widget.initialDate!);
}
}
@@ -91,94 +92,29 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
final List<DateTime> calendarDays = state.calendarDays;
final List<OrderItem> filteredOrders = state.filteredOrders;
// Header Colors logic from prototype
String sectionTitle = '';
Color dotColor = UiColors.transparent;
if (state.filterTab == 'all') {
sectionTitle = t.client_view_orders.tabs.up_next;
dotColor = UiColors.primary;
} else if (state.filterTab == 'active') {
sectionTitle = t.client_view_orders.tabs.active;
dotColor = UiColors.textWarning;
} else if (state.filterTab == 'completed') {
sectionTitle = t.client_view_orders.tabs.completed;
dotColor =
UiColors.primary; // Reverting to primary blue for consistency
}
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
// Header + Filter + Calendar (Sticky behavior)
ViewOrdersHeader(
state: state,
calendarDays: calendarDays,
),
ViewOrdersHeader(state: state, calendarDays: calendarDays),
// Content List
Expanded(
child: state.status == ViewOrdersStatus.failure
? _buildErrorState(context: context, state: state)
? ViewOrdersErrorState(
errorMessage: state.errorMessage,
selectedDate: state.selectedDate,
onRetry: () => BlocProvider.of<ViewOrdersCubit>(
context,
).jumpToDate(state.selectedDate ?? DateTime.now()),
)
: filteredOrders.isEmpty
? _buildEmptyState(context: context, state: state)
: ListView(
padding: const EdgeInsets.fromLTRB(
UiConstants.space5,
UiConstants.space4,
UiConstants.space5,
100,
),
children: <Widget>[
if (filteredOrders.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space3,
),
child: Row(
children: <Widget>[
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: dotColor,
shape: BoxShape.circle,
),
),
const SizedBox(
width: UiConstants.space2,
),
Text(
sectionTitle.toUpperCase(),
style: UiTypography.titleUppercase2m
.copyWith(
color: UiColors.textPrimary,
),
),
const SizedBox(
width: UiConstants.space1,
),
Text(
'(${filteredOrders.length})',
style: UiTypography.footnote1r
.copyWith(
color: UiColors.textSecondary,
),
),
],
),
),
...filteredOrders.map(
(OrderItem order) => Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space3,
),
child: ViewOrderCard(order: order),
),
),
],
),
? ViewOrdersEmptyState(selectedDate: state.selectedDate)
: ViewOrdersList(
orders: filteredOrders,
filterTab: state.filterTab,
),
),
],
),
@@ -187,77 +123,4 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
},
);
}
/// Builds the empty state view.
Widget _buildEmptyState({
required BuildContext context,
required ViewOrdersState state,
}) {
final String dateStr = state.selectedDate != null
? _formatDateHeader(state.selectedDate!)
: 'this date';
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(UiIcons.calendar, size: 48, color: UiColors.iconInactive),
const SizedBox(height: UiConstants.space3),
Text(
t.client_view_orders.no_orders(date: dateStr),
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
),
const SizedBox(height: UiConstants.space4),
UiButton.primary(
text: t.client_view_orders.post_order,
leadingIcon: UiIcons.add,
onPressed: () => Modular.to.toCreateOrder(),
),
],
),
);
}
static String _formatDateHeader(DateTime date) {
final DateTime now = DateTime.now();
final DateTime today = DateTime(now.year, now.month, now.day);
final DateTime tomorrow = today.add(const Duration(days: 1));
final DateTime checkDate = DateTime(date.year, date.month, date.day);
if (checkDate == today) return 'Today';
if (checkDate == tomorrow) return 'Tomorrow';
return DateFormat('EEE, MMM d').format(date);
}
Widget _buildErrorState({
required BuildContext context,
required ViewOrdersState 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<ViewOrdersCubit>(context)
.jumpToDate(state.selectedDate ?? DateTime.now()),
),
],
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
/// A widget that displays an empty state when no orders are found for a specific date.
class ViewOrdersEmptyState extends StatelessWidget {
/// Creates a [ViewOrdersEmptyState].
const ViewOrdersEmptyState({super.key, required this.selectedDate});
/// The currently selected date to display in the empty state message.
final DateTime? selectedDate;
@override
Widget build(BuildContext context) {
final String dateStr = selectedDate != null
? _formatDateHeader(selectedDate!)
: 'this date';
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(UiIcons.calendar, size: 48, color: UiColors.iconInactive),
const SizedBox(height: UiConstants.space3),
Text(
t.client_view_orders.no_orders(date: dateStr),
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
),
const SizedBox(height: UiConstants.space4),
UiButton.primary(
text: t.client_view_orders.post_order,
leadingIcon: UiIcons.add,
onPressed: () => Modular.to.toCreateOrder(),
),
],
),
);
}
static String _formatDateHeader(DateTime date) {
final DateTime now = DateTime.now();
final DateTime today = DateTime(now.year, now.month, now.day);
final DateTime tomorrow = today.add(const Duration(days: 1));
final DateTime checkDate = DateTime(date.year, date.month, date.day);
if (checkDate == today) return 'Today';
if (checkDate == tomorrow) return 'Tomorrow';
return DateFormat('EEE, MMM d').format(date);
}
}

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';
/// A widget that displays an error state when orders fail to load.
class ViewOrdersErrorState extends StatelessWidget {
/// Creates a [ViewOrdersErrorState].
const ViewOrdersErrorState({
super.key,
required this.errorMessage,
required this.selectedDate,
required this.onRetry,
});
/// The error message to display.
final String? errorMessage;
/// The selected date to retry loading for.
final DateTime? selectedDate;
/// Callback to trigger a retry.
final VoidCallback onRetry;
@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(
errorMessage != null
? translateErrorKey(errorMessage!)
: 'An error occurred',
style: UiTypography.body1m.textError,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space4),
UiButton.secondary(text: 'Retry', onPressed: onRetry),
],
),
);
}
}

View File

@@ -0,0 +1,66 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
import 'view_order_card.dart';
import 'view_orders_list_section_header.dart';
import 'package:core_localization/core_localization.dart';
/// A widget that displays the list of filtered orders.
class ViewOrdersList extends StatelessWidget {
/// Creates a [ViewOrdersList].
const ViewOrdersList({
super.key,
required this.orders,
required this.filterTab,
});
/// The list of orders to display.
final List<OrderItem> orders;
/// The currently selected filter tab to determine the section title and dot color.
final String filterTab;
@override
Widget build(BuildContext context) {
if (orders.isEmpty) {
return const SizedBox.shrink();
}
String sectionTitle = '';
Color dotColor = UiColors.transparent;
if (filterTab == 'all') {
sectionTitle = t.client_view_orders.tabs.up_next;
dotColor = UiColors.primary;
} else if (filterTab == 'active') {
sectionTitle = t.client_view_orders.tabs.active;
dotColor = UiColors.textWarning;
} else if (filterTab == 'completed') {
sectionTitle = t.client_view_orders.tabs.completed;
dotColor = UiColors.primary;
}
return ListView(
padding: const EdgeInsets.fromLTRB(
UiConstants.space5,
UiConstants.space4,
UiConstants.space5,
100,
),
children: <Widget>[
ViewOrdersListSectionHeader(
title: sectionTitle,
dotColor: dotColor,
count: orders.length,
),
...orders.map(
(OrderItem order) => Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: ViewOrderCard(order: order),
),
),
],
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A widget that displays the section header for the orders list.
///
/// Includes a status indicator dot, the section title, and the count of orders.
class ViewOrdersListSectionHeader extends StatelessWidget {
/// Creates a [ViewOrdersListSectionHeader].
const ViewOrdersListSectionHeader({
super.key,
required this.title,
required this.dotColor,
required this.count,
});
/// The title of the section (e.g., UP NEXT, ACTIVE, COMPLETED).
final String title;
/// The color of the status indicator dot.
final Color dotColor;
/// The number of orders in this section.
final int count;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: Row(
children: <Widget>[
Container(
width: 8,
height: 8,
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
),
const SizedBox(width: UiConstants.space2),
Text(
title.toUpperCase(),
style: UiTypography.titleUppercase2m.copyWith(
color: UiColors.textPrimary,
),
),
const SizedBox(width: UiConstants.space1),
Text(
'($count)',
style: UiTypography.footnote1r.copyWith(
color: UiColors.textSecondary,
),
),
],
),
);
}
}