From 8ad31dad58fb4e824b91ccf6d00e982fc9821807 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 16 Jan 2026 00:08:56 +0800 Subject: [PATCH] :sparkles: Notification stacks on the mobile --- lib/widgets/notification_item.dart | 8 +- lib/widgets/notification_overlay.dart | 125 +++++++++++++++++--------- 2 files changed, 89 insertions(+), 44 deletions(-) diff --git a/lib/widgets/notification_item.dart b/lib/widgets/notification_item.dart index 8fcebd77..f577dd16 100644 --- a/lib/widgets/notification_item.dart +++ b/lib/widgets/notification_item.dart @@ -12,6 +12,8 @@ class NotificationItemWidget extends HookConsumerWidget { final NotificationItem item; final VoidCallback onDismiss; final bool isDesktop; + final int index; + final int totalNotifications; final Animation progress; const NotificationItemWidget({ @@ -19,6 +21,8 @@ class NotificationItemWidget extends HookConsumerWidget { required this.item, required this.onDismiss, required this.isDesktop, + required this.index, + required this.totalNotifications, required this.progress, }); @@ -48,7 +52,7 @@ class NotificationItemWidget extends HookConsumerWidget { } : null, child: Card( - elevation: 4, + elevation: (index == 0 && !isDesktop && totalNotifications > 1) ? 8 : 4, margin: EdgeInsets.zero, color: Theme.of(context).colorScheme.surfaceContainerHigh, shape: const RoundedRectangleBorder( @@ -117,4 +121,4 @@ class NotificationItemWidget extends HookConsumerWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/notification_overlay.dart b/lib/widgets/notification_overlay.dart index 02a2a7ac..bd3ce20a 100644 --- a/lib/widgets/notification_overlay.dart +++ b/lib/widgets/notification_overlay.dart @@ -29,39 +29,79 @@ class NotificationOverlay extends HookConsumerWidget { return const SizedBox.shrink(); } - final itemWidth = isDesktop ? 420.0 : MediaQuery.sizeOf(context).width - 40; + final itemWidth = isDesktop ? 420.0 : MediaQuery.sizeOf(context).width; - return Positioned( - top: topOffset, - left: 0, - right: 0, - child: - Material( - color: Colors.transparent, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: notifications.asMap().entries.map((entry) { - final item = entry.value; - return AnimatedNotificationItem( - key: Key(item.id), - item: item, - isDesktop: isDesktop, - onDismiss: () { - ref - .read(notificationStateProvider.notifier) - .remove(item.id); - }, - ); - }).toList(), - ), - ) - .width(itemWidth) - .alignment(isDesktop ? Alignment.topRight : Alignment.topCenter), - ); + if (isDesktop) { + return Positioned( + top: topOffset, + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: notifications.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + return AnimatedNotificationItem( + key: Key(item.id), + item: item, + isDesktop: true, + index: index, + totalNotifications: notifications.length, + onDismiss: () { + ref.read(notificationStateProvider.notifier).remove(item.id); + }, + ); + }).toList(), + ), + ).width(itemWidth).alignment(Alignment.topRight), + ); + } else { + // Non-desktop: use Stack with overlapping + const double notificationHeight = 80.0; + const double overlap = 20.0; + final stackHeight = + notificationHeight + (notifications.length - 1) * overlap; + + return Positioned( + top: topOffset, + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: SizedBox( + height: stackHeight, + child: Stack( + alignment: Alignment.topCenter, + children: notifications.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + return Positioned( + top: index * overlap, + left: 16, + right: 16, + child: AnimatedNotificationItem( + key: Key(item.id), + item: item, + isDesktop: false, + index: index, + totalNotifications: notifications.length, + onDismiss: () { + ref + .read(notificationStateProvider.notifier) + .remove(item.id); + }, + ).clipRRect(all: 8), + ); + }).toList(), + ), + ), + ).width(itemWidth).alignment(Alignment.topCenter), + ); + } } } @@ -69,12 +109,16 @@ class AnimatedNotificationItem extends HookConsumerWidget { final NotificationItem item; final VoidCallback onDismiss; final bool isDesktop; + final int index; + final int totalNotifications; const AnimatedNotificationItem({ super.key, required this.item, required this.onDismiss, required this.isDesktop, + required this.index, + required this.totalNotifications, }); @override @@ -124,16 +168,13 @@ class AnimatedNotificationItem extends HookConsumerWidget { child: SizeTransition( sizeFactor: curvedAnimation, axis: Axis.vertical, - child: Padding( - padding: isDesktop - ? const EdgeInsets.only(bottom: 12, right: 16) - : const EdgeInsets.only(bottom: 12, left: 16, right: 16), - child: NotificationItemWidget( - item: item, - isDesktop: isDesktop, - onDismiss: () {}, - progress: progressAnimation, - ), + child: NotificationItemWidget( + item: item, + isDesktop: isDesktop, + index: index, + totalNotifications: totalNotifications, + onDismiss: () {}, + progress: progressAnimation, ), ), );