feat: add shimmer skeletons for various sections in the staff profile and onboarding features
- Implemented ProfilePageSkeleton for loading state in staff profile. - Added ReliabilityScoreSkeleton and ReliabilityStatsSkeleton for reliability metrics. - Created CertificatesSkeleton and related components for loading certificates. - Developed DocumentsSkeleton and associated document card skeletons. - Introduced TaxFormsSkeleton for loading tax forms. - Added BankAccountSkeleton and its components for bank account loading state. - Created TimeCardSkeleton for displaying time card loading state. - Implemented AttireSkeleton for loading attire items. - Added PersonalInfoSkeleton for loading personal information. - Developed FaqsSkeleton for loading FAQ sections. - Created PrivacySecuritySkeleton for loading privacy settings.
This commit is contained in:
@@ -11,6 +11,7 @@ import '../blocs/certificates/certificates_state.dart';
|
||||
import '../widgets/add_certificate_card.dart';
|
||||
import '../widgets/certificate_card.dart';
|
||||
import '../widgets/certificates_header.dart';
|
||||
import '../widgets/certificates_skeleton/certificates_skeleton.dart';
|
||||
|
||||
/// Page for viewing and managing staff certificates.
|
||||
///
|
||||
@@ -28,9 +29,7 @@ class CertificatesPage extends StatelessWidget {
|
||||
builder: (BuildContext context, CertificatesState state) {
|
||||
if (state.status == CertificatesStatus.loading ||
|
||||
state.status == CertificatesStatus.initial) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
return const Scaffold(body: CertificatesSkeleton());
|
||||
}
|
||||
|
||||
if (state.status == CertificatesStatus.failure) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single certificate card.
|
||||
class CertificateCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [CertificateCardSkeleton].
|
||||
const CertificateCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 40),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 140, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiShimmerBox(width: 60, height: 28),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the certificates progress header.
|
||||
class CertificatesHeaderSkeleton extends StatelessWidget {
|
||||
/// Creates a [CertificatesHeaderSkeleton].
|
||||
const CertificatesHeaderSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: const BoxDecoration(color: UiColors.primary),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const UiShimmerCircle(size: 64),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
UiShimmerLine(
|
||||
width: 120,
|
||||
height: 14,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(
|
||||
width: 80,
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'certificate_card_skeleton.dart';
|
||||
import 'certificates_header_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while certificates are loading.
|
||||
class CertificatesSkeleton extends StatelessWidget {
|
||||
/// Creates a [CertificatesSkeleton].
|
||||
const CertificatesSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const CertificatesHeaderSkeleton(),
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -UiConstants.space12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
),
|
||||
child: UiShimmerList(
|
||||
itemCount: 4,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) =>
|
||||
const CertificateCardSkeleton(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import '../blocs/documents/documents_cubit.dart';
|
||||
import '../blocs/documents/documents_state.dart';
|
||||
import '../widgets/document_card.dart';
|
||||
import '../widgets/documents_progress_card.dart';
|
||||
import '../widgets/documents_skeleton/documents_skeleton.dart';
|
||||
|
||||
class DocumentsPage extends StatelessWidget {
|
||||
const DocumentsPage({super.key});
|
||||
@@ -28,11 +29,7 @@ class DocumentsPage extends StatelessWidget {
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (BuildContext context, DocumentsState state) {
|
||||
if (state.status == DocumentsStatus.loading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(UiColors.primary),
|
||||
),
|
||||
);
|
||||
return const DocumentsSkeleton();
|
||||
}
|
||||
if (state.status == DocumentsStatus.failure) {
|
||||
return Center(
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single document card row.
|
||||
class DocumentCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [DocumentCardSkeleton].
|
||||
const DocumentCardSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 40),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 160, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiShimmerBox(width: 24, height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the documents progress card.
|
||||
class DocumentsProgressSkeleton extends StatelessWidget {
|
||||
/// Creates a [DocumentsProgressSkeleton].
|
||||
const DocumentsProgressSkeleton({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: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 140, height: 14),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
UiShimmerBox(width: double.infinity, height: 8),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'document_card_skeleton.dart';
|
||||
import 'documents_progress_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while documents are loading.
|
||||
class DocumentsSkeleton extends StatelessWidget {
|
||||
/// Creates a [DocumentsSkeleton].
|
||||
const DocumentsSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space6,
|
||||
),
|
||||
children: <Widget>[
|
||||
const DocumentsProgressSkeleton(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiShimmerList(
|
||||
itemCount: 5,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) => const DocumentCardSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
import '../blocs/tax_forms/tax_forms_cubit.dart';
|
||||
import '../blocs/tax_forms/tax_forms_state.dart';
|
||||
import '../widgets/tax_forms_page/index.dart';
|
||||
import '../widgets/tax_forms_skeleton/tax_forms_skeleton.dart';
|
||||
|
||||
class TaxFormsPage extends StatelessWidget {
|
||||
const TaxFormsPage({super.key});
|
||||
@@ -31,7 +32,7 @@ class TaxFormsPage extends StatelessWidget {
|
||||
child: BlocBuilder<TaxFormsCubit, TaxFormsState>(
|
||||
builder: (BuildContext context, TaxFormsState state) {
|
||||
if (state.status == TaxFormsStatus.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const TaxFormsSkeleton();
|
||||
}
|
||||
|
||||
if (state.status == TaxFormsStatus.failure) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single tax form card.
|
||||
class TaxFormCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [TaxFormCardSkeleton].
|
||||
const TaxFormCardSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 40),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 120, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiShimmerBox(width: 60, height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'tax_form_card_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while tax forms are loading.
|
||||
class TaxFormsSkeleton extends StatelessWidget {
|
||||
/// Creates a [TaxFormsSkeleton].
|
||||
const TaxFormsSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space6,
|
||||
),
|
||||
child: Column(
|
||||
spacing: UiConstants.space4,
|
||||
children: <Widget>[
|
||||
// Info card placeholder
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.cardViewBackground,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 180, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(height: 12),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 200, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Progress overview placeholder
|
||||
const UiShimmerStatsCard(),
|
||||
// Form card placeholders
|
||||
UiShimmerList(
|
||||
itemCount: 3,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => const TaxFormCardSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import '../blocs/bank_account_cubit.dart';
|
||||
import '../blocs/bank_account_state.dart';
|
||||
import '../widgets/account_card.dart';
|
||||
import '../widgets/add_account_form.dart';
|
||||
import '../widgets/bank_account_skeleton/bank_account_skeleton.dart';
|
||||
import '../widgets/security_notice.dart';
|
||||
|
||||
class BankAccountPage extends StatelessWidget {
|
||||
@@ -49,7 +50,7 @@ class BankAccountPage extends StatelessWidget {
|
||||
builder: (BuildContext context, BankAccountState state) {
|
||||
if (state.status == BankAccountStatus.loading &&
|
||||
state.accounts.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const BankAccountSkeleton();
|
||||
}
|
||||
|
||||
if (state.status == BankAccountStatus.error) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single bank account card.
|
||||
class AccountCardSkeleton extends StatelessWidget {
|
||||
/// Creates an [AccountCardSkeleton].
|
||||
const AccountCardSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 40),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 140, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiShimmerBox(width: 48, height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'account_card_skeleton.dart';
|
||||
import 'security_notice_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while bank accounts are loading.
|
||||
class BankAccountSkeleton extends StatelessWidget {
|
||||
/// Creates a [BankAccountSkeleton].
|
||||
const BankAccountSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SecurityNoticeSkeleton(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiShimmerList(
|
||||
itemCount: 2,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) => const AccountCardSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the security notice banner.
|
||||
class SecurityNoticeSkeleton extends StatelessWidget {
|
||||
/// Creates a [SecurityNoticeSkeleton].
|
||||
const SecurityNoticeSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 24),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 160, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:krow_core/core.dart';
|
||||
import '../blocs/time_card_bloc.dart';
|
||||
import '../widgets/month_selector.dart';
|
||||
import '../widgets/shift_history_list.dart';
|
||||
import '../widgets/time_card_skeleton/time_card_skeleton.dart';
|
||||
import '../widgets/time_card_summary.dart';
|
||||
|
||||
/// The main page for displaying the staff time card.
|
||||
@@ -50,7 +51,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
|
||||
},
|
||||
builder: (BuildContext context, TimeCardState state) {
|
||||
if (state is TimeCardLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const TimeCardSkeleton();
|
||||
} else if (state is TimeCardError) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the month selector row.
|
||||
class MonthSelectorSkeleton extends StatelessWidget {
|
||||
/// Creates a [MonthSelectorSkeleton].
|
||||
const MonthSelectorSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
UiShimmerCircle(size: 32),
|
||||
UiShimmerLine(width: 120, height: 16),
|
||||
UiShimmerCircle(size: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single shift history row.
|
||||
class ShiftHistorySkeleton extends StatelessWidget {
|
||||
/// Creates a [ShiftHistorySkeleton].
|
||||
const ShiftHistorySkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 140, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 100, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 60, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 40, height: 12),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'month_selector_skeleton.dart';
|
||||
import 'shift_history_skeleton.dart';
|
||||
import 'time_card_summary_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while time card data is loading.
|
||||
class TimeCardSkeleton extends StatelessWidget {
|
||||
/// Creates a [TimeCardSkeleton].
|
||||
const TimeCardSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space6,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const MonthSelectorSkeleton(),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
const TimeCardSummarySkeleton(),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
UiShimmerList(
|
||||
itemCount: 5,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) => const ShiftHistorySkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for the time card summary (hours + earnings).
|
||||
class TimeCardSummarySkeleton extends StatelessWidget {
|
||||
/// Creates a [TimeCardSummarySkeleton].
|
||||
const TimeCardSummarySkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(child: UiShimmerStatsCard()),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import '../widgets/attire_info_card.dart';
|
||||
import '../widgets/attire_item_card.dart';
|
||||
import '../widgets/attire_section_header.dart';
|
||||
import '../widgets/attire_section_tab.dart';
|
||||
import '../widgets/attire_skeleton/attire_skeleton.dart';
|
||||
|
||||
class AttirePage extends StatefulWidget {
|
||||
const AttirePage({super.key});
|
||||
@@ -49,7 +50,7 @@ class _AttirePageState extends State<AttirePage> {
|
||||
},
|
||||
builder: (BuildContext context, AttireState state) {
|
||||
if (state.status == AttireStatus.loading && state.options.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const AttireSkeleton();
|
||||
}
|
||||
|
||||
final List<AttireItem> requiredItems = state.options
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single attire item card.
|
||||
class AttireItemSkeleton extends StatelessWidget {
|
||||
/// Creates an [AttireItemSkeleton].
|
||||
const AttireItemSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 56, height: 56),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 120, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
UiShimmerBox(width: 60, height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'attire_item_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while attire items are loading.
|
||||
class AttireSkeleton extends StatelessWidget {
|
||||
/// Creates an [AttireSkeleton].
|
||||
const AttireSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Info card placeholder
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.cardViewBackground,
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 160, height: 14),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(height: 12),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 200, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Section toggle chips placeholder
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
UiShimmerBox(width: 80, height: 32),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
UiShimmerBox(width: 100, height: 32),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Section header placeholder
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
// Attire item cards
|
||||
UiShimmerList(
|
||||
itemCount: 4,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) => const AttireItemSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:krow_core/core.dart';
|
||||
import 'package:staff_profile_info/src/presentation/blocs/personal_info_bloc.dart';
|
||||
import 'package:staff_profile_info/src/presentation/blocs/personal_info_state.dart';
|
||||
import 'package:staff_profile_info/src/presentation/widgets/personal_info_page/personal_info_content.dart';
|
||||
import 'package:staff_profile_info/src/presentation/widgets/personal_info_skeleton/personal_info_skeleton.dart';
|
||||
|
||||
|
||||
/// The Personal Info page for staff onboarding.
|
||||
@@ -56,7 +57,7 @@ class PersonalInfoPage extends StatelessWidget {
|
||||
builder: (BuildContext context, PersonalInfoState state) {
|
||||
if (state.status == PersonalInfoStatus.loading ||
|
||||
state.status == PersonalInfoStatus.initial) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const PersonalInfoSkeleton();
|
||||
}
|
||||
|
||||
if (state.staff == null) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single form field (label + input).
|
||||
class FormFieldSkeleton extends StatelessWidget {
|
||||
/// Creates a [FormFieldSkeleton].
|
||||
const FormFieldSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 80, height: 12),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerBox(width: double.infinity, height: 48),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'form_field_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while personal info is loading.
|
||||
class PersonalInfoSkeleton extends StatelessWidget {
|
||||
/// Creates a [PersonalInfoSkeleton].
|
||||
const PersonalInfoSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Avatar placeholder
|
||||
const Center(child: UiShimmerCircle(size: 80)),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Form fields
|
||||
UiShimmerList(
|
||||
itemCount: 5,
|
||||
spacing: UiConstants.space5,
|
||||
itemBuilder: (int index) => const FormFieldSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single FAQ accordion item.
|
||||
class FaqItemSkeleton extends StatelessWidget {
|
||||
/// Creates a [FaqItemSkeleton].
|
||||
const FaqItemSkeleton({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: const Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiShimmerLine(height: 14),
|
||||
),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
UiShimmerCircle(size: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'faq_item_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while FAQs are loading.
|
||||
class FaqsSkeleton extends StatelessWidget {
|
||||
/// Creates a [FaqsSkeleton].
|
||||
const FaqsSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space24,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Search bar placeholder
|
||||
UiShimmerBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Category header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
// FAQ items
|
||||
UiShimmerList(
|
||||
itemCount: 3,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => const FaqItemSkeleton(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Second category
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
UiShimmerList(
|
||||
itemCount: 3,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => const FaqItemSkeleton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:staff_faqs/src/presentation/blocs/faqs_bloc.dart';
|
||||
import 'faqs_skeleton/faqs_skeleton.dart';
|
||||
|
||||
/// Widget displaying FAQs with search functionality and accordion items
|
||||
class FaqsWidget extends StatefulWidget {
|
||||
@@ -76,10 +77,7 @@ class _FaqsWidgetState extends State<FaqsWidget> {
|
||||
|
||||
// FAQ List or Empty State
|
||||
if (state.isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 48),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
const FaqsSkeleton()
|
||||
else if (state.categories.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 48),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
import '../../blocs/legal/privacy_policy_cubit.dart';
|
||||
import '../../widgets/skeletons/legal_document_skeleton.dart';
|
||||
|
||||
/// Page displaying the Privacy Policy document
|
||||
class PrivacyPolicyPage extends StatelessWidget {
|
||||
@@ -24,9 +25,7 @@ class PrivacyPolicyPage extends StatelessWidget {
|
||||
child: BlocBuilder<PrivacyPolicyCubit, PrivacyPolicyState>(
|
||||
builder: (BuildContext context, PrivacyPolicyState state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
return const LegalDocumentSkeleton();
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
import '../../blocs/legal/terms_cubit.dart';
|
||||
import '../../widgets/skeletons/legal_document_skeleton.dart';
|
||||
|
||||
/// Page displaying the Terms of Service document
|
||||
class TermsOfServicePage extends StatelessWidget {
|
||||
@@ -24,9 +25,7 @@ class TermsOfServicePage extends StatelessWidget {
|
||||
child: BlocBuilder<TermsCubit, TermsState>(
|
||||
builder: (BuildContext context, TermsState state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
return const LegalDocumentSkeleton();
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../blocs/privacy_security_bloc.dart';
|
||||
import '../widgets/legal/legal_section_widget.dart';
|
||||
import '../widgets/privacy/privacy_section_widget.dart';
|
||||
import '../widgets/skeletons/privacy_security_skeleton.dart';
|
||||
|
||||
/// Page displaying privacy & security settings for staff
|
||||
class PrivacySecurityPage extends StatelessWidget {
|
||||
@@ -25,7 +26,7 @@ class PrivacySecurityPage extends StatelessWidget {
|
||||
child: BlocBuilder<PrivacySecurityBloc, PrivacySecurityState>(
|
||||
builder: (BuildContext context, PrivacySecurityState state) {
|
||||
if (state.isLoading) {
|
||||
return const UiLoadingPage();
|
||||
return const PrivacySecuritySkeleton();
|
||||
}
|
||||
|
||||
return const SingleChildScrollView(
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shared shimmer skeleton for legal document pages (Privacy Policy, Terms).
|
||||
///
|
||||
/// Simulates a long-form text document with varied line widths.
|
||||
class LegalDocumentSkeleton extends StatelessWidget {
|
||||
/// Creates a [LegalDocumentSkeleton].
|
||||
const LegalDocumentSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Title line
|
||||
const UiShimmerLine(width: 200, height: 18),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
// Body text lines with varied widths
|
||||
UiShimmerList(
|
||||
itemCount: 4,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => const UiShimmerLine(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
const UiShimmerLine(width: 180, height: 16),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
UiShimmerList(
|
||||
itemCount: 5,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => UiShimmerLine(
|
||||
width: index == 4 ? 200 : double.infinity,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
const UiShimmerLine(width: 160, height: 16),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
UiShimmerList(
|
||||
itemCount: 3,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => const UiShimmerLine(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
const UiShimmerLine(width: 140, height: 16),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
UiShimmerList(
|
||||
itemCount: 4,
|
||||
spacing: UiConstants.space2,
|
||||
itemBuilder: (int index) => UiShimmerLine(
|
||||
width: index == 3 ? 160 : double.infinity,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'settings_toggle_skeleton.dart';
|
||||
|
||||
/// Full-page shimmer skeleton shown while privacy settings are loading.
|
||||
class PrivacySecuritySkeleton extends StatelessWidget {
|
||||
/// Creates a [PrivacySecuritySkeleton].
|
||||
const PrivacySecuritySkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiShimmer(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Privacy section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiShimmerList(
|
||||
itemCount: 3,
|
||||
spacing: UiConstants.space4,
|
||||
itemBuilder: (int index) => const SettingsToggleSkeleton(),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Legal section header
|
||||
const UiShimmerSectionHeader(),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
// Legal links
|
||||
UiShimmerList(
|
||||
itemCount: 2,
|
||||
spacing: UiConstants.space3,
|
||||
itemBuilder: (int index) => const UiShimmerListItem(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shimmer placeholder for a single settings toggle row.
|
||||
class SettingsToggleSkeleton extends StatelessWidget {
|
||||
/// Creates a [SettingsToggleSkeleton].
|
||||
const SettingsToggleSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: UiConstants.space2),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
UiShimmerLine(width: 140, height: 14),
|
||||
SizedBox(height: UiConstants.space1),
|
||||
UiShimmerLine(width: 200, height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: UiConstants.space3),
|
||||
UiShimmerBox(width: 48, height: 28),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user