From d1ee2e5160d878b4585fbd5c77da000e9ee1a06b Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 16 Jan 2026 00:56:57 +0800 Subject: [PATCH] :lipstick: Optimize the notification overlay styling --- lib/widgets/notification_item.dart | 173 +++++++++++++------------- lib/widgets/notification_overlay.dart | 60 ++++----- 2 files changed, 118 insertions(+), 115 deletions(-) diff --git a/lib/widgets/notification_item.dart b/lib/widgets/notification_item.dart index 6881e382..3ccaea09 100644 --- a/lib/widgets/notification_item.dart +++ b/lib/widgets/notification_item.dart @@ -8,12 +8,12 @@ import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; +const double kNotificationBorderRadius = 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({ @@ -21,8 +21,6 @@ class NotificationItemWidget extends HookConsumerWidget { required this.item, required this.onDismiss, required this.isDesktop, - required this.index, - required this.totalNotifications, required this.progress, }); @@ -56,92 +54,97 @@ class NotificationItemWidget extends HookConsumerWidget { } } : null, - child: Stack( - children: [ - Card( - elevation: (index == 0 && !isDesktop && totalNotifications > 1) ? 8 : 4, - margin: EdgeInsets.zero, - color: Theme.of(context).colorScheme.surfaceContainerHigh, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (item.notification.meta['pfp'] != null) - ProfilePictureWidget( - fileId: item.notification.meta['pfp'], - radius: 12, - ).padding(right: 12, top: 2) - else - Icon( - Symbols.info, - color: Theme.of(context).colorScheme.primary, - size: 24, - ).padding(right: 12), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - item.notification.title, - style: Theme.of(context).textTheme.titleMedium - ?.copyWith(fontWeight: FontWeight.bold), - ), - if (item.notification.content.isNotEmpty) - Text( - item.notification.content, - style: Theme.of(context).textTheme.bodyMedium, - ), - if (item.notification.subtitle.isNotEmpty) - Text( - item.notification.subtitle, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - ], - ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: double.infinity), + child: Stack( + children: [ + Card( + elevation: 4, + margin: EdgeInsets.zero, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all( + Radius.circular(kNotificationBorderRadius), ), - AnimatedBuilder( - animation: progress, - builder: (context, child) => LinearProgressIndicator( - value: progress.value, - minHeight: 2, - backgroundColor: Colors.transparent, - valueColor: AlwaysStoppedAnimation( - Theme.of(context).colorScheme.primary.withOpacity(0.5), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (item.notification.meta['pfp'] != null) + ProfilePictureWidget( + fileId: item.notification.meta['pfp'], + radius: 12, + ).padding(right: 12, top: 2) + else + Icon( + Symbols.info, + color: Theme.of(context).colorScheme.primary, + size: 24, + ).padding(right: 12), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + item.notification.title, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.bold), + ), + if (item.notification.content.isNotEmpty) + Text( + item.notification.content, + style: Theme.of(context).textTheme.bodyMedium, + ), + if (item.notification.subtitle.isNotEmpty) + Text( + item.notification.subtitle, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ], ), ), - ), - ], - ).clipRRect(all: 8), - ), - Positioned( - top: 4, - right: 4, - child: IconButton( - icon: Icon( - Symbols.close, - size: 20, - color: Theme.of(context).colorScheme.onSurfaceVariant, + AnimatedBuilder( + animation: progress, + builder: (context, child) => LinearProgressIndicator( + value: progress.value, + minHeight: 2, + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary.withOpacity(0.5), + ), + ), + ), + ], ), - onPressed: onDismiss, - padding: const EdgeInsets.all(4), - constraints: const BoxConstraints(), ), - ), - ], + Positioned( + top: 4, + right: 4, + child: IconButton( + icon: Icon( + Symbols.close, + size: 20, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + onPressed: onDismiss, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), + ), + ), + ], + ).clipRRect(all: kNotificationBorderRadius), ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/notification_overlay.dart b/lib/widgets/notification_overlay.dart index a623c546..91367580 100644 --- a/lib/widgets/notification_overlay.dart +++ b/lib/widgets/notification_overlay.dart @@ -38,18 +38,17 @@ class NotificationOverlay extends HookConsumerWidget { child: Material( color: Colors.transparent, child: Column( + spacing: 8, 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, + margin: EdgeInsets.symmetric(horizontal: 16), onDismiss: () { ref.read(notificationStateProvider.notifier).dismiss(item.id); }, @@ -60,10 +59,7 @@ class NotificationOverlay extends HookConsumerWidget { ); } 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, @@ -72,7 +68,7 @@ class NotificationOverlay extends HookConsumerWidget { child: Material( color: Colors.transparent, child: SizedBox( - height: stackHeight, + height: MediaQuery.sizeOf(context).height, child: Stack( alignment: Alignment.topCenter, children: notifications.asMap().entries.map((entry) { @@ -82,18 +78,23 @@ class NotificationOverlay extends HookConsumerWidget { 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) - .dismiss(item.id); - }, - ).clipRRect(all: 8), + child: Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow(color: Colors.black54, blurRadius: 4.0 + index * 2.0), + ], + ), + child: AnimatedNotificationItem( + key: Key(item.id), + item: item, + isDesktop: false, + onDismiss: () { + ref + .read(notificationStateProvider.notifier) + .dismiss(item.id); + }, + ), + ), ); }).toList(), ), @@ -108,16 +109,14 @@ class AnimatedNotificationItem extends HookConsumerWidget { final NotificationItem item; final VoidCallback onDismiss; final bool isDesktop; - final int index; - final int totalNotifications; + final EdgeInsets? margin; const AnimatedNotificationItem({ super.key, required this.item, required this.onDismiss, required this.isDesktop, - required this.index, - required this.totalNotifications, + this.margin, }); @override @@ -163,13 +162,14 @@ class AnimatedNotificationItem extends HookConsumerWidget { child: SizeTransition( sizeFactor: curvedAnimation, axis: Axis.vertical, - child: NotificationItemWidget( - item: item, - isDesktop: isDesktop, - index: index, - totalNotifications: totalNotifications, - onDismiss: onDismiss, - progress: progressAnimation, + child: Padding( + padding: margin ?? EdgeInsets.zero, + child: NotificationItemWidget( + item: item, + isDesktop: isDesktop, + onDismiss: onDismiss, + progress: progressAnimation, + ), ), ), );