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:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_domain/krow_domain.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/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.
|
/// Full-screen page displaying paginated benefit history.
|
||||||
///
|
///
|
||||||
@@ -79,43 +79,29 @@ class _BenefitHistoryPageState extends State<BenefitHistoryPage> {
|
|||||||
final bool isLoaded =
|
final bool isLoaded =
|
||||||
state.loadedHistoryIds.contains(widget.benefitId);
|
state.loadedHistoryIds.contains(widget.benefitId);
|
||||||
final List<BenefitHistory> history =
|
final List<BenefitHistory> history =
|
||||||
state.historyByBenefitId[widget.benefitId] ?? <BenefitHistory>[];
|
state.historyByBenefitId[widget.benefitId] ??
|
||||||
final bool hasMore = state.hasMoreHistory[widget.benefitId] ?? true;
|
<BenefitHistory>[];
|
||||||
|
final bool hasMore =
|
||||||
|
state.hasMoreHistory[widget.benefitId] ?? true;
|
||||||
|
|
||||||
// Initial loading state
|
// Initial loading state
|
||||||
if (isLoading && !isLoaded) {
|
if (isLoading && !isLoaded) {
|
||||||
return _buildLoadingSkeleton();
|
return const BenefitHistorySkeleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty state
|
// Empty state
|
||||||
if (isLoaded && history.isEmpty) {
|
if (isLoaded && history.isEmpty) {
|
||||||
return UiEmptyState(
|
return BenefitHistoryEmptyState(
|
||||||
icon: UiIcons.clock,
|
message: i18n.no_history as String,
|
||||||
title: i18n.no_history as String,
|
|
||||||
description: '',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loaded list with infinite scroll
|
// Loaded list with infinite scroll
|
||||||
return ListView.builder(
|
return BenefitHistoryList(
|
||||||
controller: _scrollController,
|
history: history,
|
||||||
padding: const EdgeInsets.symmetric(
|
hasMore: hasMore,
|
||||||
horizontal: UiConstants.space4,
|
isLoading: isLoading,
|
||||||
vertical: UiConstants.space4,
|
scrollController: _scrollController,
|
||||||
),
|
|
||||||
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]);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -134,29 +120,4 @@ class _BenefitHistoryPageState extends State<BenefitHistoryPage> {
|
|||||||
cubit.loadMoreBenefitHistory(widget.benefitId);
|
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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(height: 1, color: UiColors.border),
|
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _toggleExpanded,
|
onTap: _toggleExpanded,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
|
padding: const EdgeInsets.only(top: UiConstants.space4),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
i18n.history_header as String,
|
i18n.history_header as String,
|
||||||
style: UiTypography.footnote2b.textSecondary,
|
style: UiTypography.footnote2b.textSecondary,
|
||||||
),
|
),
|
||||||
Icon(
|
Icon(
|
||||||
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
||||||
size: UiConstants.iconSm,
|
size: UiConstants.iconSm,
|
||||||
color: UiColors.iconSecondary,
|
color: UiColors.iconSecondary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedSize(
|
AnimatedSize(
|
||||||
@@ -136,7 +135,6 @@ class _BenefitHistoryPreviewState extends State<BenefitHistoryPreview> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user