feat: add shimmer loading skeletons for various pages and components
- Implemented ReorderCardSkeleton and ReorderSectionSkeleton for the client home page. - Added SpendingCardSkeleton and SpendingSectionSkeleton for spending-related UI. - Created OrderCardSkeleton and associated skeletons for the view orders page. - Developed MetricCardSkeleton and MetricsGridSkeleton for reports page metrics. - Introduced HomePageSkeleton and its components for staff home page. - Added PaymentItemSkeleton and PaymentsPageSkeleton for payments page. - Created ShiftDetailsPageSkeleton and related components for shift details. - Implemented ShiftsPageSkeleton and ShiftCardSkeleton for shifts page.
This commit is contained in:
@@ -1,135 +1 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the billing page content area.
|
||||
///
|
||||
/// Mimics the loaded layout with a pending invoices section,
|
||||
/// a spending breakdown card, and an invoice history list.
|
||||
class BillingPageSkeleton extends StatelessWidget {
|
||||
/// Creates a [BillingPageSkeleton].
|
||||
const BillingPageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Pending invoices section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Pending invoice cards
|
||||
const _InvoiceCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const _InvoiceCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Spending breakdown card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UiShimmerLine(width: 160, height: 16),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
// Breakdown rows
|
||||
_BreakdownRowSkeleton(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
_BreakdownRowSkeleton(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
_BreakdownRowSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Invoice history section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const UiShimmerListItem(),
|
||||
const UiShimmerListItem(),
|
||||
const UiShimmerListItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a single pending invoice card.
|
||||
class _InvoiceCardSkeleton extends StatelessWidget {
|
||||
const _InvoiceCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
UiShimmerBox(
|
||||
width: 72,
|
||||
height: 24,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
const UiShimmerLine(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const UiShimmerLine(width: 200, height: 16),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
const UiShimmerLine(width: 160, height: 12),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UiShimmerLine(width: 80, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 100, height: 18),
|
||||
],
|
||||
),
|
||||
UiShimmerBox(
|
||||
width: 100,
|
||||
height: 36,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a spending breakdown row.
|
||||
class _BreakdownRowSkeleton extends StatelessWidget {
|
||||
const _BreakdownRowSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
UiShimmerLine(width: 60, height: 14),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'billing_page_skeleton/index.dart';
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'breakdown_row_skeleton.dart';
|
||||
import 'invoice_card_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the billing page content area.
|
||||
///
|
||||
/// Mimics the loaded layout with a pending invoices section,
|
||||
/// a spending breakdown card, and an invoice history list.
|
||||
class BillingPageSkeleton extends StatelessWidget {
|
||||
/// Creates a [BillingPageSkeleton].
|
||||
const BillingPageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Pending invoices section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Pending invoice cards
|
||||
const InvoiceCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const InvoiceCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Spending breakdown card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UiShimmerLine(width: 160, height: 16),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
// Breakdown rows
|
||||
BreakdownRowSkeleton(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
BreakdownRowSkeleton(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
BreakdownRowSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Invoice history section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const UiShimmerListItem(),
|
||||
const UiShimmerListItem(),
|
||||
const UiShimmerListItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a spending breakdown row.
|
||||
class BreakdownRowSkeleton extends StatelessWidget {
|
||||
/// Creates a [BreakdownRowSkeleton].
|
||||
const BreakdownRowSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
UiShimmerLine(width: 60, height: 14),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export 'billing_page_skeleton.dart';
|
||||
export 'breakdown_row_skeleton.dart';
|
||||
export 'invoice_card_skeleton.dart';
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single pending invoice card.
|
||||
class InvoiceCardSkeleton extends StatelessWidget {
|
||||
/// Creates an [InvoiceCardSkeleton].
|
||||
const InvoiceCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
UiShimmerBox(
|
||||
width: 72,
|
||||
height: 24,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
const UiShimmerLine(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const UiShimmerLine(width: 200, height: 16),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
const UiShimmerLine(width: 160, height: 12),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UiShimmerLine(width: 80, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 100, height: 18),
|
||||
],
|
||||
),
|
||||
UiShimmerBox(
|
||||
width: 100,
|
||||
height: 36,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer loading skeleton that mimics the coverage page loaded layout.
|
||||
///
|
||||
/// Shows placeholder shapes for the quick stats row, shift section header,
|
||||
/// and a list of shift cards with worker rows.
|
||||
class CoveragePageSkeleton extends StatelessWidget {
|
||||
/// Creates a [CoveragePageSkeleton].
|
||||
const CoveragePageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Quick stats row (2 stat cards)
|
||||
const Row(
|
||||
children: [
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Shifts section header
|
||||
const UiShimmerLine(width: 140, height: 18),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Shift cards with worker rows
|
||||
const _ShiftCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const _ShiftCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const _ShiftCardSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a single shift card with header and worker rows.
|
||||
class _ShiftCardSkeleton extends StatelessWidget {
|
||||
const _ShiftCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
// Shift header
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const UiShimmerLine(width: 180, height: 16),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
const UiShimmerLine(width: 120, height: 12),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: [
|
||||
const UiShimmerLine(width: 80, height: 12),
|
||||
const Spacer(),
|
||||
UiShimmerBox(
|
||||
width: 60,
|
||||
height: 24,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Worker rows
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space3,
|
||||
).copyWith(bottom: UiConstants.space3),
|
||||
child: const Column(
|
||||
children: [
|
||||
UiShimmerListItem(),
|
||||
UiShimmerListItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'coverage_page_skeleton/index.dart';
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'shift_card_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton that mimics the coverage page loaded layout.
|
||||
///
|
||||
/// Shows placeholder shapes for the quick stats row, shift section header,
|
||||
/// and a list of shift cards with worker rows.
|
||||
class CoveragePageSkeleton extends StatelessWidget {
|
||||
/// Creates a [CoveragePageSkeleton].
|
||||
const CoveragePageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Quick stats row (2 stat cards)
|
||||
const Row(
|
||||
children: [
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Shifts section header
|
||||
const UiShimmerLine(width: 140, height: 18),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Shift cards with worker rows
|
||||
const ShiftCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const ShiftCardSkeleton(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const ShiftCardSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'coverage_page_skeleton.dart';
|
||||
export 'shift_card_skeleton.dart';
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single shift card with header and worker rows.
|
||||
class ShiftCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [ShiftCardSkeleton].
|
||||
const ShiftCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
// Shift header
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const UiShimmerLine(width: 180, height: 16),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
const UiShimmerLine(width: 120, height: 12),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: [
|
||||
const UiShimmerLine(width: 80, height: 12),
|
||||
const Spacer(),
|
||||
UiShimmerBox(
|
||||
width: 60,
|
||||
height: 24,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Worker rows
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space3,
|
||||
).copyWith(bottom: UiConstants.space3),
|
||||
child: const Column(
|
||||
children: [
|
||||
UiShimmerListItem(),
|
||||
UiShimmerListItem(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ class ClientHomeBody extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, ClientHomeState state) {
|
||||
return const ClientHomePageSkeleton();
|
||||
if (state.status == ClientHomeStatus.initial ||
|
||||
state.status == ClientHomeStatus.loading) {
|
||||
return const ClientHomePageSkeleton();
|
||||
|
||||
@@ -1,329 +1,10 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the client home page.
|
||||
///
|
||||
/// Mimics the loaded dashboard layout with action cards, reorder cards,
|
||||
/// coverage metrics, spending card, and live activity sections.
|
||||
class ClientHomePageSkeleton extends StatelessWidget {
|
||||
/// Creates a [ClientHomePageSkeleton].
|
||||
const ClientHomePageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: ListView(
|
||||
children: const <Widget>[
|
||||
// Actions section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: _ActionsSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Reorder section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: _ReorderSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Coverage section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: _CoverageSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Spending section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: _SpendingSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Live activity section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: _LiveActivitySectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for the two side-by-side action cards.
|
||||
class _ActionsSectionSkeleton extends StatelessWidget {
|
||||
const _ActionsSectionSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: _ActionCardSkeleton()),
|
||||
const SizedBox(width: UiConstants.space4),
|
||||
Expanded(child: _ActionCardSkeleton()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for a single action card with icon, title, and subtitle.
|
||||
class _ActionCardSkeleton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 36, height: 36),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 60, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 100, height: 10),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for the horizontal reorder cards list.
|
||||
class _ReorderSectionSkeleton extends StatelessWidget {
|
||||
const _ReorderSectionSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
SizedBox(
|
||||
height: 164,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_ReorderCardSkeleton(),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
_ReorderCardSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for a single reorder card.
|
||||
class _ReorderCardSkeleton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 260,
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.6),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 36, height: 36),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 80, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 40, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 60, height: 22),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
UiShimmerBox(width: 36, height: 22),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
UiShimmerBox(
|
||||
width: double.infinity,
|
||||
height: 32,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for the coverage metric cards row.
|
||||
class _CoverageSectionSkeleton extends StatelessWidget {
|
||||
const _CoverageSectionSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: _MetricCardSkeleton()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: _MetricCardSkeleton()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: _MetricCardSkeleton()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for a single coverage metric card.
|
||||
class _MetricCardSkeleton extends StatelessWidget {
|
||||
const _MetricCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space2),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
SizedBox(width: UiConstants.space1),
|
||||
UiShimmerLine(width: 40, height: 10),
|
||||
],
|
||||
),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 32, height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for the spending gradient card.
|
||||
class _SpendingSectionSkeleton extends StatelessWidget {
|
||||
const _SpendingSectionSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
_SpendingCardSkeleton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton mimicking the spending card layout.
|
||||
class _SpendingCardSkeleton extends StatelessWidget {
|
||||
const _SpendingCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 80, height: 22),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 50, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 70, height: 18),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 50, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Skeleton for the live activity section.
|
||||
class _LiveActivitySectionSkeleton extends StatelessWidget {
|
||||
const _LiveActivitySectionSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerStatsCard(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
UiShimmerListItem(),
|
||||
UiShimmerListItem(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'client_home_page_skeleton/action_card_skeleton.dart';
|
||||
export 'client_home_page_skeleton/actions_section_skeleton.dart';
|
||||
export 'client_home_page_skeleton/client_home_page_skeleton.dart';
|
||||
export 'client_home_page_skeleton/coverage_section_skeleton.dart';
|
||||
export 'client_home_page_skeleton/live_activity_section_skeleton.dart';
|
||||
export 'client_home_page_skeleton/metric_card_skeleton.dart';
|
||||
export 'client_home_page_skeleton/reorder_card_skeleton.dart';
|
||||
export 'client_home_page_skeleton/reorder_section_skeleton.dart';
|
||||
export 'client_home_page_skeleton/spending_card_skeleton.dart';
|
||||
export 'client_home_page_skeleton/spending_section_skeleton.dart';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Skeleton for a single action card with icon, title, and subtitle.
|
||||
class ActionCardSkeleton extends StatelessWidget {
|
||||
/// Creates an [ActionCardSkeleton].
|
||||
const ActionCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 36, height: 36),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 60, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 100, height: 10),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'action_card_skeleton.dart';
|
||||
|
||||
/// Skeleton for the two side-by-side action cards.
|
||||
class ActionsSectionSkeleton extends StatelessWidget {
|
||||
/// Creates an [ActionsSectionSkeleton].
|
||||
const ActionsSectionSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: ActionCardSkeleton()),
|
||||
SizedBox(width: UiConstants.space4),
|
||||
Expanded(child: ActionCardSkeleton()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'actions_section_skeleton.dart';
|
||||
import 'coverage_section_skeleton.dart';
|
||||
import 'live_activity_section_skeleton.dart';
|
||||
import 'reorder_section_skeleton.dart';
|
||||
import 'spending_section_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the client home page.
|
||||
///
|
||||
/// Mimics the loaded dashboard layout with action cards, reorder cards,
|
||||
/// coverage metrics, spending card, and live activity sections.
|
||||
class ClientHomePageSkeleton extends StatelessWidget {
|
||||
/// Creates a [ClientHomePageSkeleton].
|
||||
const ClientHomePageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: ListView(
|
||||
children: const <Widget>[
|
||||
// Actions section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: ActionsSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Reorder section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: ReorderSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Coverage section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: CoverageSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Spending section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: SpendingSectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
Divider(color: UiColors.border, height: 0.1),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Live activity section
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
child: LiveActivitySectionSkeleton(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space8),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'metric_card_skeleton.dart';
|
||||
|
||||
/// Skeleton for the coverage metric cards row.
|
||||
class CoverageSectionSkeleton extends StatelessWidget {
|
||||
/// Creates a [CoverageSectionSkeleton].
|
||||
const CoverageSectionSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: MetricCardSkeleton()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: MetricCardSkeleton()),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(child: MetricCardSkeleton()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Skeleton for the live activity section.
|
||||
class LiveActivitySectionSkeleton extends StatelessWidget {
|
||||
/// Creates a [LiveActivitySectionSkeleton].
|
||||
const LiveActivitySectionSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerStatsCard(),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
UiShimmerListItem(),
|
||||
UiShimmerListItem(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Skeleton for a single coverage metric card.
|
||||
class MetricCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [MetricCardSkeleton].
|
||||
const MetricCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space2),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
SizedBox(width: UiConstants.space1),
|
||||
UiShimmerLine(width: 40, height: 10),
|
||||
],
|
||||
),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 32, height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Skeleton for a single reorder card.
|
||||
class ReorderCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [ReorderCardSkeleton].
|
||||
const ReorderCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 260,
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.6),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 36, height: 36),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 80, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 40, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 60, height: 22),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
UiShimmerBox(width: 36, height: 22),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
UiShimmerBox(
|
||||
width: double.infinity,
|
||||
height: 32,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'reorder_card_skeleton.dart';
|
||||
|
||||
/// Skeleton for the horizontal reorder cards list.
|
||||
class ReorderSectionSkeleton extends StatelessWidget {
|
||||
/// Creates a [ReorderSectionSkeleton].
|
||||
const ReorderSectionSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
SizedBox(
|
||||
height: 164,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
ReorderCardSkeleton(),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
ReorderCardSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Skeleton mimicking the spending card layout.
|
||||
class SpendingCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [SpendingCardSkeleton].
|
||||
const SpendingCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 80, height: 22),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 50, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 70, height: 18),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 50, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'spending_card_skeleton.dart';
|
||||
|
||||
/// Skeleton for the spending gradient card.
|
||||
class SpendingSectionSkeleton extends StatelessWidget {
|
||||
/// Creates a [SpendingSectionSkeleton].
|
||||
const SpendingSectionSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerSectionHeader(),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
SpendingCardSkeleton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,211 +1 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the View Orders page.
|
||||
///
|
||||
/// Mimics the loaded layout: a section header followed by a list of order
|
||||
/// card placeholders, each containing badge, title, location, stats, time
|
||||
/// boxes, and a coverage progress bar.
|
||||
class ViewOrdersPageSkeleton extends StatelessWidget {
|
||||
/// Creates a [ViewOrdersPageSkeleton].
|
||||
const ViewOrdersPageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space4,
|
||||
UiConstants.space5,
|
||||
// Extra bottom padding for bottom navigation clearance.
|
||||
UiConstants.space24,
|
||||
),
|
||||
children: <Widget>[
|
||||
// Section header placeholder (dot + title + count)
|
||||
const _SectionHeaderSkeleton(),
|
||||
// Order card placeholders
|
||||
...List<Widget>.generate(3, (int index) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: _OrderCardSkeleton(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for the section header row.
|
||||
class _SectionHeaderSkeleton extends StatelessWidget {
|
||||
const _SectionHeaderSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 8),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
SizedBox(width: UiConstants.space1),
|
||||
UiShimmerLine(width: 24, height: 14),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a single order card.
|
||||
class _OrderCardSkeleton extends StatelessWidget {
|
||||
const _OrderCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Status and type badges
|
||||
Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(
|
||||
width: 80,
|
||||
height: 22,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
UiShimmerBox(
|
||||
width: 72,
|
||||
height: 22,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Title line
|
||||
const UiShimmerLine(width: 200, height: 18),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
|
||||
// Event name line
|
||||
const UiShimmerLine(width: 160, height: 14),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Location lines
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 180, height: 12),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 140, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const Divider(height: 1, color: UiColors.border),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Stats row (cost / hours / workers)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
_StatItemSkeleton(),
|
||||
_StatDividerSkeleton(),
|
||||
_StatItemSkeleton(),
|
||||
_StatDividerSkeleton(),
|
||||
_StatItemSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// Time boxes (clock in / clock out)
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: _timeBoxSkeleton()),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(child: _timeBoxSkeleton()),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Coverage progress bar
|
||||
const UiShimmerLine(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a placeholder for a time display box (clock-in / clock-out).
|
||||
Widget _timeBoxSkeleton() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 80, height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a single stat item (icon + value + label).
|
||||
class _StatItemSkeleton extends StatelessWidget {
|
||||
const _StatItemSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
spacing: UiConstants.space1,
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
UiShimmerLine(width: 32, height: 16),
|
||||
UiShimmerLine(width: 40, height: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for the vertical stat divider.
|
||||
class _StatDividerSkeleton extends StatelessWidget {
|
||||
const _StatDividerSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const UiShimmerBox(
|
||||
width: 1,
|
||||
height: 24,
|
||||
borderRadius: BorderRadius.zero,
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'view_orders_page_skeleton/index.dart';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export 'order_card_skeleton.dart';
|
||||
export 'section_header_skeleton.dart';
|
||||
export 'stat_divider_skeleton.dart';
|
||||
export 'stat_item_skeleton.dart';
|
||||
export 'view_orders_page_skeleton.dart';
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'stat_divider_skeleton.dart';
|
||||
import 'stat_item_skeleton.dart';
|
||||
|
||||
/// Shimmer placeholder for a single order card.
|
||||
class OrderCardSkeleton extends StatelessWidget {
|
||||
/// Creates an [OrderCardSkeleton].
|
||||
const OrderCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Status and type badges
|
||||
Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(
|
||||
width: 80,
|
||||
height: 22,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
UiShimmerBox(
|
||||
width: 72,
|
||||
height: 22,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Title line
|
||||
const UiShimmerLine(width: 200, height: 18),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
|
||||
// Event name line
|
||||
const UiShimmerLine(width: 160, height: 14),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Location lines
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 180, height: 12),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 140, height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const Divider(height: 1, color: UiColors.border),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Stats row (cost / hours / workers)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
StatItemSkeleton(),
|
||||
StatDividerSkeleton(),
|
||||
StatItemSkeleton(),
|
||||
StatDividerSkeleton(),
|
||||
StatItemSkeleton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// Time boxes (clock in / clock out)
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: _timeBoxSkeleton()),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(child: _timeBoxSkeleton()),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Coverage progress bar
|
||||
const UiShimmerLine(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a placeholder for a time display box (clock-in / clock-out).
|
||||
Widget _timeBoxSkeleton() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, width: 0.5),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 10),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 80, height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the section header row (dot + title + count).
|
||||
class SectionHeaderSkeleton extends StatelessWidget {
|
||||
/// Creates a [SectionHeaderSkeleton].
|
||||
const SectionHeaderSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 8),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 14),
|
||||
SizedBox(width: UiConstants.space1),
|
||||
UiShimmerLine(width: 24, height: 14),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the vertical stat divider.
|
||||
class StatDividerSkeleton extends StatelessWidget {
|
||||
/// Creates a [StatDividerSkeleton].
|
||||
const StatDividerSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const UiShimmerBox(
|
||||
width: 1,
|
||||
height: 24,
|
||||
borderRadius: BorderRadius.zero,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single stat item (icon + value + label).
|
||||
class StatItemSkeleton extends StatelessWidget {
|
||||
/// Creates a [StatItemSkeleton].
|
||||
const StatItemSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
spacing: UiConstants.space1,
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 14),
|
||||
UiShimmerLine(width: 32, height: 16),
|
||||
UiShimmerLine(width: 40, height: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'order_card_skeleton.dart';
|
||||
import 'section_header_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the View Orders page.
|
||||
///
|
||||
/// Mimics the loaded layout: a section header followed by a list of order
|
||||
/// card placeholders, each containing badge, title, location, stats, time
|
||||
/// boxes, and a coverage progress bar.
|
||||
class ViewOrdersPageSkeleton extends StatelessWidget {
|
||||
/// Creates a [ViewOrdersPageSkeleton].
|
||||
const ViewOrdersPageSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space4,
|
||||
UiConstants.space5,
|
||||
// Extra bottom padding for bottom navigation clearance.
|
||||
UiConstants.space24,
|
||||
),
|
||||
children: <Widget>[
|
||||
// Section header placeholder (dot + title + count)
|
||||
const SectionHeaderSkeleton(),
|
||||
// Order card placeholders
|
||||
...List<Widget>.generate(3, (int index) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: OrderCardSkeleton(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the reports metrics grid.
|
||||
///
|
||||
/// Shows a 2-column grid of 6 placeholder cards matching the [MetricsGrid]
|
||||
/// loaded layout.
|
||||
class MetricsGridSkeleton extends StatelessWidget {
|
||||
/// Creates a [MetricsGridSkeleton].
|
||||
const MetricsGridSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: GridView.count(
|
||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space6),
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: UiConstants.space3,
|
||||
crossAxisSpacing: UiConstants.space3,
|
||||
childAspectRatio: 1.32,
|
||||
children: List.generate(6, (int index) {
|
||||
return const _MetricCardSkeleton();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shimmer placeholder for a single metric card.
|
||||
class _MetricCardSkeleton extends StatelessWidget {
|
||||
const _MetricCardSkeleton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.cardViewBackground,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon + label row
|
||||
Row(
|
||||
children: [
|
||||
const UiShimmerCircle(size: UiConstants.space6),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
const Expanded(
|
||||
child: UiShimmerLine(width: 60, height: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
// Value
|
||||
const UiShimmerLine(width: 80, height: 22),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
// Badge
|
||||
UiShimmerBox(
|
||||
width: 60,
|
||||
height: 20,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'metrics_grid_skeleton/index.dart';
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'metric_card_skeleton.dart';
|
||||
export 'metrics_grid_skeleton.dart';
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single metric card.
|
||||
class MetricCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [MetricCardSkeleton].
|
||||
const MetricCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.cardViewBackground,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon + label row
|
||||
Row(
|
||||
children: [
|
||||
const UiShimmerCircle(size: UiConstants.space6),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
const Expanded(
|
||||
child: UiShimmerLine(width: 60, height: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
// Value
|
||||
const UiShimmerLine(width: 80, height: 22),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
// Badge
|
||||
UiShimmerBox(
|
||||
width: 60,
|
||||
height: 20,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'metric_card_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton for the reports metrics grid.
|
||||
///
|
||||
/// Shows a 2-column grid of 6 placeholder cards matching the [MetricsGrid]
|
||||
/// loaded layout.
|
||||
class MetricsGridSkeleton extends StatelessWidget {
|
||||
/// Creates a [MetricsGridSkeleton].
|
||||
const MetricsGridSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: GridView.count(
|
||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space6),
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: UiConstants.space3,
|
||||
crossAxisSpacing: UiConstants.space3,
|
||||
childAspectRatio: 1.32,
|
||||
children: List.generate(6, (int index) {
|
||||
return const MetricCardSkeleton();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user