From abb498c94669422b3fea44fd4860ff85e34c6980 Mon Sep 17 00:00:00 2001 From: Roscoe Rubin-Rottenberg Date: Sun, 24 May 2026 20:19:17 -0400 Subject: [PATCH] feat: notifications skeleton loading --- .../widgets/notification_list_skeleton.dart | 143 ++++++++++++++++++ .../ui/widgets/notifications_list.dart | 5 +- 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 lib/src/features/notifications/ui/widgets/notification_list_skeleton.dart diff --git a/lib/src/features/notifications/ui/widgets/notification_list_skeleton.dart b/lib/src/features/notifications/ui/widgets/notification_list_skeleton.dart new file mode 100644 index 0000000..9ef4a35 --- /dev/null +++ b/lib/src/features/notifications/ui/widgets/notification_list_skeleton.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class NotificationListSkeleton extends StatelessWidget { + const NotificationListSkeleton({super.key, this.itemCount = 8}); + + final int itemCount; + + @override + Widget build(BuildContext context) { + return Skeletonizer( + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: itemCount, + itemBuilder: (context, index) { + return NotificationItemSkeleton(showMedia: index % 3 == 1); + }, + ), + ); + } +} + +class NotificationItemSkeleton extends StatelessWidget { + const NotificationItemSkeleton({super.key, this.showMedia = false}); + + final bool showMedia; + + @override + Widget build(BuildContext context) { + final skeletonColor = Theme.of(context).colorScheme.surfaceContainerHighest; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 24, + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: Skeleton.leaf( + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: skeletonColor, + shape: BoxShape.circle, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + Skeleton.leaf( + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: skeletonColor, + shape: BoxShape.circle, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Skeleton.leaf( + child: Container( + height: 15, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ), + const SizedBox(width: 42), + Skeleton.leaf( + child: Container( + width: 30, + height: 12, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ], + ), + const SizedBox(height: 10), + FractionallySizedBox( + widthFactor: 0.78, + alignment: Alignment.centerLeft, + child: Skeleton.leaf( + child: Container( + height: 14, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ), + const SizedBox(height: 8), + FractionallySizedBox( + widthFactor: 0.54, + alignment: Alignment.centerLeft, + child: Skeleton.leaf( + child: Container( + height: 14, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ), + ], + ), + ), + if (showMedia) ...[ + const SizedBox(width: 12), + Skeleton.leaf( + child: Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: skeletonColor, + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ], + ], + ), + ); + } +} diff --git a/lib/src/features/notifications/ui/widgets/notifications_list.dart b/lib/src/features/notifications/ui/widgets/notifications_list.dart index 8de90e9..7e799b5 100644 --- a/lib/src/features/notifications/ui/widgets/notifications_list.dart +++ b/lib/src/features/notifications/ui/widgets/notifications_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spark/src/features/notifications/providers/notification_provider.dart'; import 'package:spark/src/core/l10n/app_localizations.dart'; +import 'package:spark/src/features/notifications/providers/notification_provider.dart'; import 'package:spark/src/features/notifications/ui/widgets/notification_item.dart'; +import 'package:spark/src/features/notifications/ui/widgets/notification_list_skeleton.dart'; class NotificationsList extends ConsumerStatefulWidget { const NotificationsList({this.priority, this.reasons, super.key}); @@ -153,7 +154,7 @@ class _NotificationsListState extends ConsumerState { final hasError = notificationState.hasError; if (isLoading && isEmpty) { - return const Center(child: CircularProgressIndicator()); + return const NotificationListSkeleton(); } if (hasError && isEmpty) { -- 2.51.2