From e60413f45c07ca288736ae753aa37390c1f052ae Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Tue, 10 Mar 2026 17:02:16 -0400 Subject: [PATCH] feat: add shimmer loading skeletons for emergency contact section --- .../pages/emergency_contact_screen.dart | 3 +- .../contact_card_skeleton.dart | 39 +++++++++++++ .../contact_field_skeleton.dart | 20 +++++++ .../emergency_contact_skeleton.dart | 57 +++++++++++++++++++ .../info_banner_skeleton.dart | 37 ++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_card_skeleton.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_field_skeleton.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/emergency_contact_skeleton.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/info_banner_skeleton.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart index ab377812..dd85406f 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/pages/emergency_contact_screen.dart @@ -9,6 +9,7 @@ import '../widgets/emergency_contact_add_button.dart'; import '../widgets/emergency_contact_form_item.dart'; import '../widgets/emergency_contact_info_banner.dart'; import '../widgets/emergency_contact_save_button.dart'; +import '../widgets/emergency_contact_skeleton/emergency_contact_skeleton.dart'; /// The Staff Emergency Contact screen. /// @@ -43,7 +44,7 @@ class EmergencyContactScreen extends StatelessWidget { }, builder: (context, state) { if (state.status == EmergencyContactStatus.loading) { - return const Center(child: CircularProgressIndicator()); + return const EmergencyContactSkeleton(); } return Column( children: [ diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_card_skeleton.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_card_skeleton.dart new file mode 100644 index 00000000..9109a538 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_card_skeleton.dart @@ -0,0 +1,39 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +import 'contact_field_skeleton.dart'; + +/// Shimmer placeholder for a single emergency contact card. +class ContactCardSkeleton extends StatelessWidget { + /// Creates a [ContactCardSkeleton]. + const ContactCardSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: UiConstants.space4), + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.bgPopup, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header ("Contact 1") + UiShimmerLine(width: 90, height: 16), + SizedBox(height: UiConstants.space4), + // Full Name field + ContactFieldSkeleton(), + SizedBox(height: UiConstants.space4), + // Phone Number field + ContactFieldSkeleton(), + SizedBox(height: UiConstants.space4), + // Relationship field + ContactFieldSkeleton(), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_field_skeleton.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_field_skeleton.dart new file mode 100644 index 00000000..b376b11e --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/contact_field_skeleton.dart @@ -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 ContactFieldSkeleton extends StatelessWidget { + /// Creates a [ContactFieldSkeleton]. + const ContactFieldSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + UiShimmerLine(width: 80, height: 12), + SizedBox(height: UiConstants.space2), + UiShimmerBox(width: double.infinity, height: 48), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/emergency_contact_skeleton.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/emergency_contact_skeleton.dart new file mode 100644 index 00000000..280e599e --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/emergency_contact_skeleton.dart @@ -0,0 +1,57 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +import 'contact_card_skeleton.dart'; +import 'info_banner_skeleton.dart'; + +/// Full-page shimmer skeleton shown while emergency contacts are loading. +class EmergencyContactSkeleton extends StatelessWidget { + /// Creates an [EmergencyContactSkeleton]. + const EmergencyContactSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return UiShimmer( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + children: [ + // Info banner + const InfoBannerSkeleton(), + const SizedBox(height: UiConstants.space6), + // Contact card + const ContactCardSkeleton(), + const SizedBox(height: UiConstants.space4), + // Add contact button placeholder + UiShimmerBox( + width: 180, + height: 40, + borderRadius: UiConstants.radiusFull, + ), + const SizedBox(height: UiConstants.space16), + ], + ), + ), + ), + // Save button placeholder + Container( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: UiColors.border)), + ), + child: SafeArea( + child: UiShimmerBox( + width: double.infinity, + height: 48, + borderRadius: UiConstants.radiusLg, + ), + ), + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/info_banner_skeleton.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/info_banner_skeleton.dart new file mode 100644 index 00000000..dd1462b9 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/emergency_contact/lib/src/presentation/widgets/emergency_contact_skeleton/info_banner_skeleton.dart @@ -0,0 +1,37 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// Shimmer placeholder for the emergency contact info banner. +class InfoBannerSkeleton extends StatelessWidget { + /// Creates an [InfoBannerSkeleton]. + const InfoBannerSkeleton({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( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + UiShimmerCircle(size: 24), + SizedBox(width: UiConstants.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + UiShimmerLine(height: 12), + SizedBox(height: UiConstants.space2), + UiShimmerLine(width: 200, height: 12), + ], + ), + ), + ], + ), + ); + } +}