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';
|
export 'billing_page_skeleton/index.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),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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';
|
export 'coverage_page_skeleton/index.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(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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) {
|
builder: (BuildContext context, ClientHomeState state) {
|
||||||
return const ClientHomePageSkeleton();
|
|
||||||
if (state.status == ClientHomeStatus.initial ||
|
if (state.status == ClientHomeStatus.initial ||
|
||||||
state.status == ClientHomeStatus.loading) {
|
state.status == ClientHomeStatus.loading) {
|
||||||
return const ClientHomePageSkeleton();
|
return const ClientHomePageSkeleton();
|
||||||
|
|||||||
@@ -1,329 +1,10 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
export 'client_home_page_skeleton/action_card_skeleton.dart';
|
||||||
import 'package:flutter/material.dart';
|
export 'client_home_page_skeleton/actions_section_skeleton.dart';
|
||||||
|
export 'client_home_page_skeleton/client_home_page_skeleton.dart';
|
||||||
/// Shimmer loading skeleton for the client home page.
|
export 'client_home_page_skeleton/coverage_section_skeleton.dart';
|
||||||
///
|
export 'client_home_page_skeleton/live_activity_section_skeleton.dart';
|
||||||
/// Mimics the loaded dashboard layout with action cards, reorder cards,
|
export 'client_home_page_skeleton/metric_card_skeleton.dart';
|
||||||
/// coverage metrics, spending card, and live activity sections.
|
export 'client_home_page_skeleton/reorder_card_skeleton.dart';
|
||||||
class ClientHomePageSkeleton extends StatelessWidget {
|
export 'client_home_page_skeleton/reorder_section_skeleton.dart';
|
||||||
/// Creates a [ClientHomePageSkeleton].
|
export 'client_home_page_skeleton/spending_card_skeleton.dart';
|
||||||
const ClientHomePageSkeleton({super.key});
|
export 'client_home_page_skeleton/spending_section_skeleton.dart';
|
||||||
|
|
||||||
@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(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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';
|
export 'view_orders_page_skeleton/index.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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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';
|
export 'metrics_grid_skeleton/index.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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,201 +1 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
export 'home_page_skeleton/index.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Shimmer loading skeleton for the staff home page.
|
|
||||||
///
|
|
||||||
/// Mimics the loaded layout with quick actions, today's shifts, tomorrow's
|
|
||||||
/// shifts, recommended shifts, and benefits sections. Displayed while
|
|
||||||
/// [HomeCubit] is fetching initial data.
|
|
||||||
class HomePageSkeleton extends StatelessWidget {
|
|
||||||
/// Creates a [HomePageSkeleton].
|
|
||||||
const HomePageSkeleton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return UiShimmer(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Quick actions row (3 circular icons + labels)
|
|
||||||
const _QuickActionsSkeleton(),
|
|
||||||
|
|
||||||
const _SkeletonDivider(),
|
|
||||||
|
|
||||||
// Today's Shifts section
|
|
||||||
const _ShiftSectionSkeleton(),
|
|
||||||
|
|
||||||
const _SkeletonDivider(),
|
|
||||||
|
|
||||||
// Tomorrow's Shifts section
|
|
||||||
const _ShiftSectionSkeleton(),
|
|
||||||
|
|
||||||
const _SkeletonDivider(),
|
|
||||||
|
|
||||||
// Recommended Shifts (horizontal cards)
|
|
||||||
const _RecommendedSectionSkeleton(),
|
|
||||||
|
|
||||||
const _SkeletonDivider(),
|
|
||||||
|
|
||||||
// Benefits section
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
vertical: UiConstants.space3,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const UiShimmerSectionHeader(),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerList(
|
|
||||||
itemCount: 2,
|
|
||||||
itemBuilder: (index) => const UiShimmerListItem(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for the quick actions row (3 circular placeholders with labels).
|
|
||||||
class _QuickActionsSkeleton extends StatelessWidget {
|
|
||||||
const _QuickActionsSkeleton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
vertical: UiConstants.space3,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: List.generate(3, (index) {
|
|
||||||
return const Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
UiShimmerCircle(size: 48),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 60, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for a shift section (section header + 2 shift card placeholders).
|
|
||||||
class _ShiftSectionSkeleton extends StatelessWidget {
|
|
||||||
const _ShiftSectionSkeleton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
vertical: UiConstants.space3,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const UiShimmerSectionHeader(),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerList(
|
|
||||||
itemCount: 2,
|
|
||||||
itemBuilder: (index) => const _ShiftCardSkeleton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for a single compact shift card on the home page.
|
|
||||||
class _ShiftCardSkeleton extends StatelessWidget {
|
|
||||||
const _ShiftCardSkeleton();
|
|
||||||
|
|
||||||
@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: [
|
|
||||||
UiShimmerBox(width: 48, height: 48),
|
|
||||||
SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
UiShimmerLine(width: 160, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 120, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: UiConstants.space3),
|
|
||||||
UiShimmerBox(width: 56, height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for the recommended shifts horizontal scroll section.
|
|
||||||
class _RecommendedSectionSkeleton extends StatelessWidget {
|
|
||||||
const _RecommendedSectionSkeleton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space3),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
|
||||||
child: UiShimmerSectionHeader(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
SizedBox(
|
|
||||||
height: 120,
|
|
||||||
child: ListView.builder(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
),
|
|
||||||
itemCount: 3,
|
|
||||||
itemBuilder: (context, index) => Padding(
|
|
||||||
padding: const EdgeInsets.only(right: UiConstants.space3),
|
|
||||||
child: UiShimmerBox(
|
|
||||||
width: 200,
|
|
||||||
height: 120,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A thin full-width divider placeholder matching the home page layout.
|
|
||||||
class _SkeletonDivider extends StatelessWidget {
|
|
||||||
const _SkeletonDivider();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const Divider(height: 1, thickness: 0.5, color: UiColors.border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'quick_actions_skeleton.dart';
|
||||||
|
import 'recommended_section_skeleton.dart';
|
||||||
|
import 'shift_section_skeleton.dart';
|
||||||
|
import 'skeleton_divider.dart';
|
||||||
|
|
||||||
|
/// Shimmer loading skeleton for the staff home page.
|
||||||
|
///
|
||||||
|
/// Mimics the loaded layout with quick actions, today's shifts, tomorrow's
|
||||||
|
/// shifts, recommended shifts, and benefits sections. Displayed while
|
||||||
|
/// [HomeCubit] is fetching initial data.
|
||||||
|
class HomePageSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [HomePageSkeleton].
|
||||||
|
const HomePageSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return UiShimmer(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Quick actions row (3 circular icons + labels)
|
||||||
|
const QuickActionsSkeleton(),
|
||||||
|
|
||||||
|
const SkeletonDivider(),
|
||||||
|
|
||||||
|
// Today's Shifts section
|
||||||
|
const ShiftSectionSkeleton(),
|
||||||
|
|
||||||
|
const SkeletonDivider(),
|
||||||
|
|
||||||
|
// Tomorrow's Shifts section
|
||||||
|
const ShiftSectionSkeleton(),
|
||||||
|
|
||||||
|
const SkeletonDivider(),
|
||||||
|
|
||||||
|
// Recommended Shifts (horizontal cards)
|
||||||
|
const RecommendedSectionSkeleton(),
|
||||||
|
|
||||||
|
const SkeletonDivider(),
|
||||||
|
|
||||||
|
// Benefits section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const UiShimmerSectionHeader(),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerList(
|
||||||
|
itemCount: 2,
|
||||||
|
itemBuilder: (index) => const UiShimmerListItem(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export 'home_page_skeleton.dart';
|
||||||
|
export 'quick_actions_skeleton.dart';
|
||||||
|
export 'recommended_section_skeleton.dart';
|
||||||
|
export 'shift_card_skeleton.dart';
|
||||||
|
export 'shift_section_skeleton.dart';
|
||||||
|
export 'skeleton_divider.dart';
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for the quick actions row (3 circular placeholders with labels).
|
||||||
|
class QuickActionsSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [QuickActionsSkeleton].
|
||||||
|
const QuickActionsSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: List.generate(3, (index) {
|
||||||
|
return const Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
UiShimmerCircle(size: 48),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 60, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for the recommended shifts horizontal scroll section.
|
||||||
|
class RecommendedSectionSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [RecommendedSectionSkeleton].
|
||||||
|
const RecommendedSectionSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: UiConstants.space3),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||||
|
child: UiShimmerSectionHeader(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
),
|
||||||
|
itemCount: 3,
|
||||||
|
itemBuilder: (context, index) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: UiConstants.space3),
|
||||||
|
child: UiShimmerBox(
|
||||||
|
width: 200,
|
||||||
|
height: 120,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for a single compact shift card on the home page.
|
||||||
|
class ShiftCardSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [ShiftCardSkeleton].
|
||||||
|
const ShiftCardSkeleton({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: [
|
||||||
|
UiShimmerBox(width: 48, height: 48),
|
||||||
|
SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
UiShimmerLine(width: 160, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 120, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: UiConstants.space3),
|
||||||
|
UiShimmerBox(width: 56, height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'shift_card_skeleton.dart';
|
||||||
|
|
||||||
|
/// Skeleton for a shift section (section header + 2 shift card placeholders).
|
||||||
|
class ShiftSectionSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [ShiftSectionSkeleton].
|
||||||
|
const ShiftSectionSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const UiShimmerSectionHeader(),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerList(
|
||||||
|
itemCount: 2,
|
||||||
|
itemBuilder: (index) => const ShiftCardSkeleton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A thin full-width divider placeholder matching the home page layout.
|
||||||
|
class SkeletonDivider extends StatelessWidget {
|
||||||
|
/// Creates a [SkeletonDivider].
|
||||||
|
const SkeletonDivider({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Divider(height: 1, thickness: 0.5, color: UiColors.border);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,148 +1 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
export 'payments_page_skeleton/index.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Shimmer loading skeleton for the payments page.
|
|
||||||
///
|
|
||||||
/// Mimics the loaded layout: a gradient header with balance and period tabs,
|
|
||||||
/// an earnings graph placeholder, stat cards, and a recent payments list.
|
|
||||||
class PaymentsPageSkeleton extends StatelessWidget {
|
|
||||||
/// Creates a [PaymentsPageSkeleton].
|
|
||||||
const PaymentsPageSkeleton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return UiShimmer(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// Header section with gradient
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
UiColors.primary,
|
|
||||||
UiColors.primary.withValues(alpha: 0.8),
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
UiConstants.space5,
|
|
||||||
MediaQuery.of(context).padding.top + UiConstants.space6,
|
|
||||||
UiConstants.space5,
|
|
||||||
UiConstants.space8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Title placeholder
|
|
||||||
const UiShimmerLine(width: 120, height: 24),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
|
|
||||||
// Balance center
|
|
||||||
const Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
UiShimmerLine(width: 100, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space1),
|
|
||||||
UiShimmerLine(width: 160, height: 36),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
// Period tabs placeholder
|
|
||||||
UiShimmerBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 40,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Main content offset upwards
|
|
||||||
Transform.translate(
|
|
||||||
offset: const Offset(0, -UiConstants.space4),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space5,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Earnings graph placeholder
|
|
||||||
UiShimmerBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 180,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
|
|
||||||
// Quick stats row
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: UiShimmerStatsCard()),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(child: UiShimmerStatsCard()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space8),
|
|
||||||
|
|
||||||
// Recent Payments header
|
|
||||||
const UiShimmerSectionHeader(),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
|
|
||||||
// Payment history items
|
|
||||||
UiShimmerList(
|
|
||||||
itemCount: 4,
|
|
||||||
itemBuilder: (index) => const _PaymentItemSkeleton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for a single payment history item.
|
|
||||||
///
|
|
||||||
/// Matches the [PaymentHistoryItem] layout with a leading icon, title/subtitle
|
|
||||||
/// lines, and trailing amount text.
|
|
||||||
class _PaymentItemSkeleton extends StatelessWidget {
|
|
||||||
const _PaymentItemSkeleton();
|
|
||||||
|
|
||||||
@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: [
|
|
||||||
UiShimmerCircle(size: 40),
|
|
||||||
SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
UiShimmerLine(width: 140, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 100, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: UiConstants.space3),
|
|
||||||
UiShimmerLine(width: 60, height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export 'payment_item_skeleton.dart';
|
||||||
|
export 'payments_page_skeleton.dart';
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for a single payment history item.
|
||||||
|
///
|
||||||
|
/// Matches the [PaymentHistoryItem] layout with a leading icon, title/subtitle
|
||||||
|
/// lines, and trailing amount text.
|
||||||
|
class PaymentItemSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [PaymentItemSkeleton].
|
||||||
|
const PaymentItemSkeleton({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: [
|
||||||
|
UiShimmerCircle(size: 40),
|
||||||
|
SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
UiShimmerLine(width: 140, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 100, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: UiConstants.space3),
|
||||||
|
UiShimmerLine(width: 60, height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'payment_item_skeleton.dart';
|
||||||
|
|
||||||
|
/// Shimmer loading skeleton for the payments page.
|
||||||
|
///
|
||||||
|
/// Mimics the loaded layout: a gradient header with balance and period tabs,
|
||||||
|
/// an earnings graph placeholder, stat cards, and a recent payments list.
|
||||||
|
class PaymentsPageSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [PaymentsPageSkeleton].
|
||||||
|
const PaymentsPageSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return UiShimmer(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header section with gradient
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
UiColors.primary,
|
||||||
|
UiColors.primary.withValues(alpha: 0.8),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
UiConstants.space5,
|
||||||
|
MediaQuery.of(context).padding.top + UiConstants.space6,
|
||||||
|
UiConstants.space5,
|
||||||
|
UiConstants.space8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Title placeholder
|
||||||
|
const UiShimmerLine(width: 120, height: 24),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
// Balance center
|
||||||
|
const Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
UiShimmerLine(width: 100, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space1),
|
||||||
|
UiShimmerLine(width: 160, height: 36),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// Period tabs placeholder
|
||||||
|
UiShimmerBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main content offset upwards
|
||||||
|
Transform.translate(
|
||||||
|
offset: const Offset(0, -UiConstants.space4),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space5,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Earnings graph placeholder
|
||||||
|
UiShimmerBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 180,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
// Quick stats row
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: UiShimmerStatsCard()),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(child: UiShimmerStatsCard()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
|
||||||
|
// Recent Payments header
|
||||||
|
const UiShimmerSectionHeader(),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
|
||||||
|
// Payment history items
|
||||||
|
UiShimmerList(
|
||||||
|
itemCount: 4,
|
||||||
|
itemBuilder: (index) => const PaymentItemSkeleton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,173 +1 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
export 'shift_details_page_skeleton/index.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Shimmer loading skeleton for the shift details page.
|
|
||||||
///
|
|
||||||
/// Mimics the loaded layout: a header with icon + text lines, a stats row
|
|
||||||
/// with three stat cards, and content sections with date/time and location
|
|
||||||
/// placeholders.
|
|
||||||
class ShiftDetailsPageSkeleton extends StatelessWidget {
|
|
||||||
/// Creates a [ShiftDetailsPageSkeleton].
|
|
||||||
const ShiftDetailsPageSkeleton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: const UiAppBar(centerTitle: false),
|
|
||||||
body: UiShimmer(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Header: icon box + title/subtitle lines
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
UiShimmerBox(
|
|
||||||
width: 114,
|
|
||||||
height: 100,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
const Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
UiShimmerLine(width: 180, height: 20),
|
|
||||||
SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerLine(width: 140, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space1),
|
|
||||||
UiShimmerLine(width: 200, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
|
|
||||||
// Stats row: three stat cards
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Row(
|
|
||||||
children: List.generate(3, (index) {
|
|
||||||
return Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: index > 0 ? UiConstants.space2 : 0,
|
|
||||||
),
|
|
||||||
child: const _StatCardSkeleton(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
|
|
||||||
// Date / time section
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const UiShimmerLine(width: 100, height: 14),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
UiShimmerLine(width: 80, height: 12),
|
|
||||||
SizedBox(height: UiConstants.space1),
|
|
||||||
UiShimmerLine(width: 120, height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
UiShimmerLine(width: 80, height: 12),
|
|
||||||
SizedBox(height: UiConstants.space1),
|
|
||||||
UiShimmerLine(width: 120, height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
|
|
||||||
// Location section
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
UiShimmerLine(width: 80, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerLine(height: 14),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 240, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
|
|
||||||
// Description section
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
UiShimmerLine(width: 120, height: 14),
|
|
||||||
SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerLine(height: 12),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(height: 12),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 200, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for a single stat card in the stats row.
|
|
||||||
class _StatCardSkeleton extends StatelessWidget {
|
|
||||||
const _StatCardSkeleton();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgThird,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
child: const Column(
|
|
||||||
children: [
|
|
||||||
UiShimmerCircle(size: 40),
|
|
||||||
SizedBox(height: UiConstants.space2),
|
|
||||||
UiShimmerLine(width: 50, height: 16),
|
|
||||||
SizedBox(height: UiConstants.space1),
|
|
||||||
UiShimmerLine(width: 60, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export 'shift_details_page_skeleton.dart';
|
||||||
|
export 'stat_card_skeleton.dart';
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'stat_card_skeleton.dart';
|
||||||
|
|
||||||
|
/// Shimmer loading skeleton for the shift details page.
|
||||||
|
///
|
||||||
|
/// Mimics the loaded layout: a header with icon + text lines, a stats row
|
||||||
|
/// with three stat cards, and content sections with date/time and location
|
||||||
|
/// placeholders.
|
||||||
|
class ShiftDetailsPageSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [ShiftDetailsPageSkeleton].
|
||||||
|
const ShiftDetailsPageSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: const UiAppBar(centerTitle: false),
|
||||||
|
body: UiShimmer(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Header: icon box + title/subtitle lines
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
UiShimmerBox(
|
||||||
|
width: 114,
|
||||||
|
height: 100,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
const Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
UiShimmerLine(width: 180, height: 20),
|
||||||
|
SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerLine(width: 140, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space1),
|
||||||
|
UiShimmerLine(width: 200, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
|
// Stats row: three stat cards
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Row(
|
||||||
|
children: List.generate(3, (index) {
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: index > 0 ? UiConstants.space2 : 0,
|
||||||
|
),
|
||||||
|
child: const StatCardSkeleton(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
|
// Date / time section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const UiShimmerLine(width: 100, height: 14),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
UiShimmerLine(width: 80, height: 12),
|
||||||
|
SizedBox(height: UiConstants.space1),
|
||||||
|
UiShimmerLine(width: 120, height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
UiShimmerLine(width: 80, height: 12),
|
||||||
|
SizedBox(height: UiConstants.space1),
|
||||||
|
UiShimmerLine(width: 120, height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
|
// Location section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
UiShimmerLine(width: 80, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerLine(height: 14),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 240, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
|
// Description section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
UiShimmerLine(width: 120, height: 14),
|
||||||
|
SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerLine(height: 12),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(height: 12),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 200, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for a single stat card in the stats row.
|
||||||
|
class StatCardSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [StatCardSkeleton].
|
||||||
|
const StatCardSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: UiConstants.space3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.bgThird,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
child: const Column(
|
||||||
|
children: [
|
||||||
|
UiShimmerCircle(size: 40),
|
||||||
|
SizedBox(height: UiConstants.space2),
|
||||||
|
UiShimmerLine(width: 50, height: 16),
|
||||||
|
SizedBox(height: UiConstants.space1),
|
||||||
|
UiShimmerLine(width: 60, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +1 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
export 'shifts_page_skeleton/index.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Shimmer loading skeleton for the shifts page body content.
|
|
||||||
///
|
|
||||||
/// Mimics the loaded layout with a section header and a list of shift card
|
|
||||||
/// placeholders. Used while the initial shifts data is being fetched.
|
|
||||||
class ShiftsPageSkeleton extends StatelessWidget {
|
|
||||||
/// Creates a [ShiftsPageSkeleton].
|
|
||||||
const ShiftsPageSkeleton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return UiShimmer(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const UiShimmerSectionHeader(),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
UiShimmerList(
|
|
||||||
itemCount: 5,
|
|
||||||
itemBuilder: (index) => const _ShiftCardSkeleton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skeleton for a single shift card matching the shift list item layout.
|
|
||||||
///
|
|
||||||
/// Shows a rounded container with placeholder lines for the shift title,
|
|
||||||
/// time, location, and a trailing status badge.
|
|
||||||
class _ShiftCardSkeleton extends StatelessWidget {
|
|
||||||
const _ShiftCardSkeleton();
|
|
||||||
|
|
||||||
@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(
|
|
||||||
children: [
|
|
||||||
const Expanded(
|
|
||||||
child: UiShimmerLine(width: 180, height: 16),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
UiShimmerBox(
|
|
||||||
width: 64,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: UiConstants.radiusFull,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
const UiShimmerLine(width: 140, height: 12),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
const UiShimmerLine(width: 200, height: 12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export 'shift_card_skeleton.dart';
|
||||||
|
export 'shifts_page_skeleton.dart';
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Skeleton for a single shift card matching the shift list item layout.
|
||||||
|
///
|
||||||
|
/// Shows a rounded container with placeholder lines for the shift title,
|
||||||
|
/// time, location, and a trailing status badge.
|
||||||
|
class ShiftCardSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [ShiftCardSkeleton].
|
||||||
|
const ShiftCardSkeleton({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(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: UiShimmerLine(width: 180, height: 16),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
UiShimmerBox(
|
||||||
|
width: 64,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: UiConstants.radiusFull,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
const UiShimmerLine(width: 140, height: 12),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
const UiShimmerLine(width: 200, height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'shift_card_skeleton.dart';
|
||||||
|
|
||||||
|
/// Shimmer loading skeleton for the shifts page body content.
|
||||||
|
///
|
||||||
|
/// Mimics the loaded layout with a section header and a list of shift card
|
||||||
|
/// placeholders. Used while the initial shifts data is being fetched.
|
||||||
|
class ShiftsPageSkeleton extends StatelessWidget {
|
||||||
|
/// Creates a [ShiftsPageSkeleton].
|
||||||
|
const ShiftsPageSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return UiShimmer(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const UiShimmerSectionHeader(),
|
||||||
|
const SizedBox(height: UiConstants.space3),
|
||||||
|
UiShimmerList(
|
||||||
|
itemCount: 5,
|
||||||
|
itemBuilder: (index) => const ShiftCardSkeleton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user