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:
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user