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:
Achintha Isuru
2026-03-10 15:20:24 -04:00
parent ccf1a75a4d
commit bd98a112a0
60 changed files with 1718 additions and 31 deletions

View File

@@ -10,6 +10,7 @@ import '../blocs/profile_cubit.dart';
import '../blocs/profile_state.dart';
import '../widgets/logout_button.dart';
import '../widgets/header/profile_header.dart';
import '../widgets/profile_page_skeleton/profile_page_skeleton.dart';
import '../widgets/reliability_score_bar.dart';
import '../widgets/reliability_stats_card.dart';
import '../widgets/sections/index.dart';
@@ -63,9 +64,9 @@ class StaffProfilePage extends StatelessWidget {
}
},
builder: (BuildContext context, ProfileState state) {
// Show loading spinner if status is loading
// Show shimmer skeleton while profile data loads
if (state.status == ProfileStatus.loading) {
return const Center(child: CircularProgressIndicator());
return const ProfilePageSkeleton();
}
if (state.status == ProfileStatus.error) {
@@ -87,7 +88,7 @@ class StaffProfilePage extends StatelessWidget {
final Staff? profile = state.profile;
if (profile == null) {
return const Center(child: CircularProgressIndicator());
return const ProfilePageSkeleton();
}
return SingleChildScrollView(

View File

@@ -0,0 +1,5 @@
export 'menu_section_skeleton.dart';
export 'profile_header_skeleton.dart';
export 'profile_page_skeleton.dart';
export 'reliability_score_skeleton.dart';
export 'reliability_stats_skeleton.dart';

View File

@@ -0,0 +1,87 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Shimmer placeholder for a profile menu section.
///
/// Mirrors the section layout: a section title line followed by a grid of
/// square menu item placeholders. Reused for onboarding, compliance, finance,
/// and support sections.
class MenuSectionSkeleton extends StatelessWidget {
/// Creates a [MenuSectionSkeleton].
const MenuSectionSkeleton({
super.key,
this.itemCount = 4,
this.crossAxisCount = 3,
});
/// Number of menu item placeholders to display.
final int itemCount;
/// Number of columns in the grid.
final int crossAxisCount;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Section title placeholder
Padding(
padding: const EdgeInsets.only(left: UiConstants.space1),
child: const UiShimmerLine(width: 100, height: 12),
),
const SizedBox(height: UiConstants.space3),
// Menu items grid
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
const double spacing = UiConstants.space3;
final double totalWidth = constraints.maxWidth;
final double totalSpacingWidth = spacing * (crossAxisCount - 1);
final double itemWidth =
(totalWidth - totalSpacingWidth) / crossAxisCount;
return Wrap(
spacing: spacing,
runSpacing: spacing,
children: List<Widget>.generate(itemCount, (int index) {
return SizedBox(
width: itemWidth,
child: const _MenuItemSkeleton(),
);
}),
);
},
),
],
);
}
}
/// Single menu item shimmer: a bordered square with an icon circle and label
/// line.
class _MenuItemSkeleton extends StatelessWidget {
const _MenuItemSkeleton();
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
padding: const EdgeInsets.all(UiConstants.space2),
child: const AspectRatio(
aspectRatio: 1.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
UiShimmerBox(width: 36, height: 36),
SizedBox(height: UiConstants.space1),
UiShimmerLine(width: 48, height: 10),
],
),
),
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Shimmer placeholder for the profile header section.
///
/// Mirrors [ProfileHeader] layout: circle avatar, name line, and level badge
/// on the primary-colored background with rounded bottom corners.
class ProfileHeaderSkeleton extends StatelessWidget {
/// Creates a [ProfileHeaderSkeleton].
const ProfileHeaderSkeleton({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(
UiConstants.space5,
UiConstants.space5,
UiConstants.space5,
UiConstants.space16,
),
decoration: const BoxDecoration(
color: UiColors.primary,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(UiConstants.space6),
),
),
child: SafeArea(
bottom: false,
child: Column(
children: <Widget>[
// Avatar placeholder
const UiShimmerCircle(size: 112),
const SizedBox(height: UiConstants.space4),
// Name placeholder
const UiShimmerLine(width: 160, height: 20),
const SizedBox(height: UiConstants.space2),
// Level badge placeholder
const UiShimmerBox(width: 100, height: 24),
],
),
),
);
}
}

View File

@@ -0,0 +1,62 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'menu_section_skeleton.dart';
import 'profile_header_skeleton.dart';
import 'reliability_score_skeleton.dart';
import 'reliability_stats_skeleton.dart';
/// Full-page shimmer skeleton for [StaffProfilePage].
///
/// Mimics the loaded profile layout: header, reliability stats, score bar,
/// and four menu sections. Displayed while [ProfileCubit] fetches data.
class ProfilePageSkeleton extends StatelessWidget {
/// Creates a [ProfilePageSkeleton].
const ProfilePageSkeleton({super.key});
@override
Widget build(BuildContext context) {
return UiShimmer(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
// Header with avatar, name, and badge
const ProfileHeaderSkeleton(),
// Content offset to overlap the header bottom radius
Transform.translate(
offset: const Offset(0, -UiConstants.space6),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column(
spacing: UiConstants.space6,
children: const <Widget>[
// Reliability stats row (5 items)
ReliabilityStatsSkeleton(),
// Reliability score bar
ReliabilityScoreSkeleton(),
// Onboarding section (4 items, 3 columns)
MenuSectionSkeleton(itemCount: 4, crossAxisCount: 3),
// Compliance section (3 items, 3 columns)
MenuSectionSkeleton(itemCount: 3, crossAxisCount: 3),
// Finance section (3 items, 3 columns)
MenuSectionSkeleton(itemCount: 3, crossAxisCount: 3),
// Support section (2 items, 3 columns)
MenuSectionSkeleton(itemCount: 2, crossAxisCount: 3),
],
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Shimmer placeholder for the reliability score bar section.
///
/// Mirrors [ReliabilityScoreBar] layout: a tinted container with a title line,
/// percentage line, progress bar placeholder, and description line.
class ReliabilityScoreSkeleton extends StatelessWidget {
/// Creates a [ReliabilityScoreSkeleton].
const ReliabilityScoreSkeleton({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: UiConstants.radiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Title row with label and percentage
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
UiShimmerLine(width: 120, height: 14),
UiShimmerLine(width: 40, height: 18),
],
),
const SizedBox(height: UiConstants.space2),
// Progress bar placeholder
const UiShimmerBox(
width: double.infinity,
height: 8,
borderRadius: UiConstants.radiusSm,
),
const SizedBox(height: UiConstants.space2),
// Description line
const UiShimmerLine(width: 200, height: 10),
],
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Shimmer placeholder for the reliability stats card.
///
/// Mirrors [ReliabilityStatsCard] layout: a bordered card containing five
/// evenly-spaced stat columns, each with an icon circle, value line, and
/// label line.
class ReliabilityStatsSkeleton extends StatelessWidget {
/// Creates a [ReliabilityStatsSkeleton].
const ReliabilityStatsSkeleton({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration(
color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_StatItemSkeleton(),
_StatItemSkeleton(),
_StatItemSkeleton(),
_StatItemSkeleton(),
_StatItemSkeleton(),
],
),
);
}
}
/// Single stat column shimmer: icon circle, value line, label line.
class _StatItemSkeleton extends StatelessWidget {
const _StatItemSkeleton();
@override
Widget build(BuildContext context) {
return const Expanded(
child: Column(
children: <Widget>[
UiShimmerBox(width: UiConstants.space10, height: UiConstants.space10),
SizedBox(height: UiConstants.space1),
UiShimmerLine(width: 28, height: 14),
SizedBox(height: UiConstants.space1),
UiShimmerLine(width: 36, height: 10),
],
),
);
}
}