feat(benefit_history): refactor benefit history page with new components and improved loading states

This commit is contained in:
Achintha Isuru
2026-03-18 17:37:38 -04:00
parent 9039aa63d6
commit 8d39ebbced
6 changed files with 143 additions and 72 deletions

View File

@@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:staff_home/src/presentation/blocs/benefits_overview/benefits_overview_cubit.dart';
import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_history_row.dart';
import 'package:staff_home/src/presentation/widgets/benefit_history_page/index.dart';
/// Full-screen page displaying paginated benefit history.
///
@@ -79,43 +79,29 @@ class _BenefitHistoryPageState extends State<BenefitHistoryPage> {
final bool isLoaded =
state.loadedHistoryIds.contains(widget.benefitId);
final List<BenefitHistory> history =
state.historyByBenefitId[widget.benefitId] ?? <BenefitHistory>[];
final bool hasMore = state.hasMoreHistory[widget.benefitId] ?? true;
state.historyByBenefitId[widget.benefitId] ??
<BenefitHistory>[];
final bool hasMore =
state.hasMoreHistory[widget.benefitId] ?? true;
// Initial loading state
if (isLoading && !isLoaded) {
return _buildLoadingSkeleton();
return const BenefitHistorySkeleton();
}
// Empty state
if (isLoaded && history.isEmpty) {
return UiEmptyState(
icon: UiIcons.clock,
title: i18n.no_history as String,
description: '',
return BenefitHistoryEmptyState(
message: i18n.no_history as String,
);
}
// Loaded list with infinite scroll
return ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space4,
),
itemCount: history.length + (hasMore ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
if (index >= history.length) {
// Bottom loading indicator
return isLoading
? const Padding(
padding: EdgeInsets.all(UiConstants.space4),
child: Center(child: CircularProgressIndicator()),
)
: const SizedBox.shrink();
}
return BenefitHistoryRow(history: history[index]);
},
return BenefitHistoryList(
history: history,
hasMore: hasMore,
isLoading: isLoading,
scrollController: _scrollController,
);
},
),
@@ -134,29 +120,4 @@ class _BenefitHistoryPageState extends State<BenefitHistoryPage> {
cubit.loadMoreBenefitHistory(widget.benefitId);
}
}
/// Builds a shimmer skeleton for the initial loading state.
Widget _buildLoadingSkeleton() {
return UiShimmer(
child: Padding(
padding: const EdgeInsets.all(UiConstants.space4),
child: Column(
children: <Widget>[
for (int i = 0; i < 8; i++)
Padding(
padding:
const EdgeInsets.symmetric(vertical: UiConstants.space2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
UiShimmerLine(width: 100, height: 14),
UiShimmerLine(width: 80, height: 14),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Empty state shown when a benefit has no history entries.
class BenefitHistoryEmptyState extends StatelessWidget {
/// Creates a [BenefitHistoryEmptyState].
const BenefitHistoryEmptyState({
required this.message,
super.key,
});
/// The localized message displayed as the empty-state title.
final String message;
@override
Widget build(BuildContext context) {
return UiEmptyState(
icon: UiIcons.clock,
title: message,
description: '',
);
}
}

View File

@@ -0,0 +1,53 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_history_row.dart';
/// Scrollable list of [BenefitHistoryRow] items with a bottom loading
/// indicator for infinite-scroll pagination.
class BenefitHistoryList extends StatelessWidget {
/// Creates a [BenefitHistoryList].
const BenefitHistoryList({
required this.history,
required this.hasMore,
required this.isLoading,
required this.scrollController,
super.key,
});
/// The benefit history entries to display.
final List<BenefitHistory> history;
/// Whether additional pages are available to fetch.
final bool hasMore;
/// Whether a page load is currently in progress.
final bool isLoading;
/// Controller shared with the parent for infinite-scroll detection.
final ScrollController scrollController;
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: scrollController,
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space4,
),
itemCount: history.length + (hasMore ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
if (index >= history.length) {
// Bottom loading indicator
return isLoading
? const Padding(
padding: EdgeInsets.all(UiConstants.space4),
child: Center(child: CircularProgressIndicator()),
)
: const SizedBox.shrink();
}
return BenefitHistoryRow(history: history[index]);
},
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Shimmer skeleton shown while the initial benefit history page loads.
class BenefitHistorySkeleton extends StatelessWidget {
/// Creates a [BenefitHistorySkeleton].
const BenefitHistorySkeleton({super.key});
@override
Widget build(BuildContext context) {
return UiShimmer(
child: Padding(
padding: const EdgeInsets.all(UiConstants.space4),
child: Column(
children: <Widget>[
for (int i = 0; i < 8; i++)
Padding(
padding:
const EdgeInsets.symmetric(vertical: UiConstants.space2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
UiShimmerLine(width: 100, height: 14),
UiShimmerLine(width: 80, height: 14),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,3 @@
export 'benefit_history_empty_state.dart';
export 'benefit_history_list.dart';
export 'benefit_history_skeleton.dart';

View File

@@ -41,11 +41,10 @@ class _BenefitHistoryPreviewState extends State<BenefitHistoryPreview> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Divider(height: 1, color: UiColors.border),
InkWell(
onTap: _toggleExpanded,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
padding: const EdgeInsets.only(top: UiConstants.space4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
@@ -136,7 +135,6 @@ class _BenefitHistoryPreviewState extends State<BenefitHistoryPreview> {
),
),
),
const SizedBox(height: UiConstants.space1),
],
);
},