feat(benefit_history): refactor benefit history page with new components and improved loading states
This commit is contained in:
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export 'benefit_history_empty_state.dart';
|
||||
export 'benefit_history_list.dart';
|
||||
export 'benefit_history_skeleton.dart';
|
||||
@@ -41,25 +41,24 @@ 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),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.history_header as String,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
Icon(
|
||||
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
||||
size: UiConstants.iconSm,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.only(top: UiConstants.space4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.history_header as String,
|
||||
style: UiTypography.footnote2b.textSecondary,
|
||||
),
|
||||
Icon(
|
||||
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
||||
size: UiConstants.iconSm,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
@@ -136,7 +135,6 @@ class _BenefitHistoryPreviewState extends State<BenefitHistoryPreview> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user