Enhanced material design 3 implementation

This commit is contained in:
2026-03-20 15:15:38 +08:00
parent 27ebb89052
commit 74197c525d
26 changed files with 1345 additions and 515 deletions
@@ -9,6 +9,8 @@ import '../../providers/notifications_provider.dart';
import '../../providers/profile_provider.dart';
import '../../providers/tasks_provider.dart';
import '../../providers/tickets_provider.dart';
import '../../widgets/app_page_header.dart';
import '../../widgets/app_state_view.dart';
import '../../widgets/mono_text.dart';
import '../../widgets/responsive_body.dart';
import '../../theme/app_surfaces.dart';
@@ -58,48 +60,44 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
};
return ResponsiveBody(
child: notificationsAsync.when(
data: (items) {
if (items.isEmpty) {
return const Center(child: Text('No notifications yet.'));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
child: Text(
'Notifications',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const AppPageHeader(
title: 'Notifications',
subtitle: 'Updates and mentions across tasks and tickets',
),
if (_showBanner && !_dismissed)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: MaterialBanner(
content: const Text(
'Push notifications are currently silenced. Tap here to fix.',
),
),
if (_showBanner && !_dismissed)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: MaterialBanner(
content: const Text(
'Push notifications are currently silenced. Tap here to fix.',
),
actions: [
TextButton(
onPressed: () {
openAppSettings();
},
child: const Text('Open settings'),
),
TextButton(
onPressed: () {
setState(() => _dismissed = true);
},
child: const Text('Dismiss'),
),
],
actions: [
TextButton(
onPressed: openAppSettings,
child: const Text('Open settings'),
),
),
Expanded(
child: ListView.separated(
TextButton(
onPressed: () => setState(() => _dismissed = true),
child: const Text('Dismiss'),
),
],
),
),
Expanded(
child: notificationsAsync.when(
data: (items) {
if (items.isEmpty) {
return const AppEmptyView(
icon: Icons.notifications_none_outlined,
title: 'No notifications yet',
subtitle:
"You'll see updates here when something needs your attention.",
);
}
return ListView.separated(
padding: const EdgeInsets.only(bottom: 24),
itemCount: items.length,
separatorBuilder: (context, index) =>
@@ -124,7 +122,6 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
final title = _notificationTitle(item.type, actorName);
final icon = _notificationIcon(item.type);
// M3 Expressive: compact card shape, no shadow.
return Card(
shape: AppSurfaces.of(context).compactShape,
child: ListTile(
@@ -142,10 +139,10 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
],
),
trailing: item.isUnread
? const Icon(
? Icon(
Icons.circle,
size: 10,
color: Colors.red,
color: Theme.of(context).colorScheme.error,
)
: null,
onTap: () async {
@@ -174,14 +171,16 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
),
);
},
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => AppErrorView(
error: error,
onRetry: () => ref.invalidate(notificationsProvider),
),
],
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) =>
Center(child: Text('Failed to load notifications: $error')),
),
),
],
),
);
}