153 lines
7.5 KiB
Dart
153 lines
7.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'models/notification_model.dart';
|
|
import 'widgets/empty_state_widget.dart';
|
|
|
|
class NotificationPage extends StatefulWidget {
|
|
const NotificationPage({super.key});
|
|
|
|
@override
|
|
State<NotificationPage> createState() => _NotificationPageState();
|
|
}
|
|
|
|
class _NotificationPageState extends State<NotificationPage> {
|
|
final List<NotificationModel> _notifications = [
|
|
NotificationModel(id: '1', title: 'Order Shipped!', message: 'Your order #AMZ12345 for Nighthawk AX12 Router has been shipped.', timestamp: DateTime.now().subtract(const Duration(hours: 2)), icon: Icons.local_shipping_outlined),
|
|
NotificationModel(id: '2', title: 'Weekend Deal!', message: 'Get 15% off on all Mesh Systems this weekend. Use code WEEKEND15.', timestamp: DateTime.now().subtract(const Duration(days: 1)), icon: Icons.campaign_outlined, isRead: true),
|
|
NotificationModel(id: '3', title: 'Password Reset Confirmation', message: 'Your password was successfully reset on a new device an hour ago.', timestamp: DateTime.now().subtract(const Duration(days: 3)), icon: Icons.lock_reset_outlined),
|
|
NotificationModel(id: '4', title: 'New Login Detected', message: 'We detected a new login to your account from a new device in California, US.', timestamp: DateTime.now().subtract(const Duration(days: 3, hours: 2)), icon: Icons.login_outlined, isRead: true),
|
|
NotificationModel(id: '5', title: 'Feature Update: Dark Mode', message: 'Dark mode is now even better! Check out the latest app improvements and enjoy a smoother experience.', timestamp: DateTime.now().subtract(const Duration(days: 5)), icon: Icons.new_releases_outlined),
|
|
];
|
|
|
|
void _markAsRead(NotificationModel notification) {
|
|
setState(() {
|
|
notification.isRead = true;
|
|
});
|
|
}
|
|
|
|
void _deleteNotification(String id) {
|
|
setState(() {
|
|
_notifications.removeWhere((n) => n.id == id);
|
|
});
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Notification dismissed'), duration: Duration(seconds: 1)),
|
|
);
|
|
}
|
|
|
|
String _timeAgo(DateTime date) {
|
|
final Duration diff = DateTime.now().difference(date);
|
|
if (diff.inDays >= 365) return "${(diff.inDays / 365).floor()} year(s)";
|
|
if (diff.inDays >= 30) return "${(diff.inDays / 30).floor()} month(s)";
|
|
if (diff.inDays > 0) return "${diff.inDays} day(s)";
|
|
if (diff.inHours > 0) return "${diff.inHours} hour(s)";
|
|
if (diff.inMinutes > 0) return "${diff.inMinutes} minute(s)";
|
|
return "Just now";
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Notifications'),
|
|
centerTitle: true,
|
|
actions: [
|
|
if (_notifications.any((n) => !n.isRead))
|
|
IconButton(
|
|
icon: const Icon(Icons.mark_chat_read_outlined),
|
|
tooltip: 'Mark all as read',
|
|
onPressed: () {
|
|
setState(() {
|
|
for (var n in _notifications) {
|
|
n.isRead = true;
|
|
}
|
|
});
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('All notifications marked as read.')),
|
|
);
|
|
},
|
|
)
|
|
],
|
|
),
|
|
body: _notifications.isEmpty
|
|
? const EmptyStateWidget(
|
|
icon: Icons.notifications_off_outlined,
|
|
message: 'You have no notifications yet.',
|
|
)
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
itemCount: _notifications.length,
|
|
itemBuilder: (context, index) {
|
|
final notification = _notifications[index];
|
|
return Dismissible(
|
|
key: Key(notification.id),
|
|
direction: DismissDirection.endToStart,
|
|
onDismissed: (direction) {
|
|
_deleteNotification(notification.id);
|
|
},
|
|
background: Container(
|
|
color: theme.colorScheme.errorContainer,
|
|
alignment: Alignment.centerRight,
|
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
child: Icon(Icons.delete_outline, color: theme.colorScheme.onErrorContainer),
|
|
),
|
|
child: Card(
|
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
elevation: notification.isRead ? 1 : 2.5,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(
|
|
color: notification.isRead ? theme.dividerColor.withOpacity(0.5) : theme.colorScheme.primary.withOpacity(0.6),
|
|
width: notification.isRead ? 0.8 : 1.2,
|
|
),
|
|
borderRadius: BorderRadius.circular(theme.cardTheme.shape is RoundedRectangleBorder ? (theme.cardTheme.shape as RoundedRectangleBorder).borderRadius.resolve(Directionality.of(context)).topLeft.x : 8)
|
|
),
|
|
child: ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: notification.isRead
|
|
? theme.colorScheme.secondaryContainer.withOpacity(0.5)
|
|
: theme.colorScheme.primaryContainer,
|
|
child: Icon(
|
|
notification.icon,
|
|
color: notification.isRead
|
|
? theme.colorScheme.onSecondaryContainer.withOpacity(0.7)
|
|
: theme.colorScheme.onPrimaryContainer,
|
|
size: 22,
|
|
),
|
|
),
|
|
title: Text(
|
|
notification.title,
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: notification.isRead ? FontWeight.normal : FontWeight.w600,
|
|
color: notification.isRead ? theme.textTheme.titleMedium?.color?.withOpacity(0.8) : theme.textTheme.titleMedium?.color,
|
|
),
|
|
),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(notification.message, maxLines: 2, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${_timeAgo(notification.timestamp)} ago',
|
|
style: theme.textTheme.bodySmall?.copyWith(color: theme.textTheme.bodySmall?.color?.withOpacity(0.6), fontSize: 11),
|
|
),
|
|
],
|
|
),
|
|
isThreeLine: true,
|
|
trailing: !notification.isRead
|
|
? Icon(Icons.circle, size: 10, color: theme.colorScheme.primary)
|
|
: null,
|
|
onTap: () {
|
|
_markAsRead(notification);
|
|
// Mock action: show snackbar
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Notification "${notification.title}" viewed.'), duration: const Duration(seconds: 2)),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
} |