mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Add mark as read action to notifications list
Users can now mark a notification as read via a popup menu on each list item or by swiping left. Both actions call the backend API, remove the item from the list, and show a snackbar confirmation. Swiping an already read notification shows an error snackbar and bounces the item back. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
09b67c2707
commit
913bf0e839
@@ -19,9 +19,9 @@ For Flutter/Dart code:
|
|||||||
|
|
||||||
## Project commands
|
## Project commands
|
||||||
|
|
||||||
- `` - Run the backend
|
- `sudo cargo run` from the `backend/` folder - Run the backend (you need sudo to enable the process to attach itself to the network channel)
|
||||||
- `` - Run the front-end for the web
|
- `flutter run -d web-server --web-port 3333` from the `frontend/` folder - Run the front-end for the web
|
||||||
- `` - Run the backend tests
|
- `run_tests.sh` - Run the backend tests
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -40,5 +40,10 @@ For Flutter/Dart code:
|
|||||||
- It uses the [Material 3](https://m3.material.io/develop/flutter) framework for Flutter
|
- It uses the [Material 3](https://m3.material.io/develop/flutter) framework for Flutter
|
||||||
- The application needs to be responsive and adapt to a web experience in the desktop, tablets and phones
|
- The application needs to be responsive and adapt to a web experience in the desktop, tablets and phones
|
||||||
- The application is also available in iOS and Android as a native experience (via de App Store and Play store)
|
- The application is also available in iOS and Android as a native experience (via de App Store and Play store)
|
||||||
|
- Backend API access is implemented in the `utils/oott_api.dart` component
|
||||||
|
|
||||||
## Important notes
|
## Important notes
|
||||||
|
- NEVER add or commit .env files or files with secrets (passwords, API keys or similar information)
|
||||||
|
- Code must be as simple as possible, human readable and modularized
|
||||||
|
- ALWAYS write unit tests for new or modified backend components
|
||||||
|
- ALWAYS run all tests after making a new change and do not continue until all tests pass
|
||||||
|
|||||||
@@ -32,6 +32,34 @@ class _NotificationListState extends State<NotificationList> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<bool> _markAsRead(
|
||||||
|
BuildContext context,
|
||||||
|
oott_model.Notification item,
|
||||||
|
) async {
|
||||||
|
if (!item.isNew) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Notification was already marked as read'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
showCloseIcon: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await BackendAPI.instance.markNotificationAsRead(item.id);
|
||||||
|
_pagingController.value = _pagingController.value.filterItems(
|
||||||
|
(n) => n.id != item.id,
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Event marked as read'),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
showCloseIcon: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -87,19 +115,8 @@ class _NotificationListState extends State<NotificationList> {
|
|||||||
children: [
|
children: [
|
||||||
Dismissible(
|
Dismissible(
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
onDismissed: (direction) {
|
confirmDismiss: (direction) =>
|
||||||
setState(() {
|
_markAsRead(context, item),
|
||||||
// _events!.removeAt(index);
|
|
||||||
});
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Event marked as read'),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
showCloseIcon: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
background: Container(
|
background: Container(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -114,7 +131,21 @@ class _NotificationListState extends State<NotificationList> {
|
|||||||
'${FriendlyDateFormatter().format(item.createdOn)} - ${item.title}',
|
'${FriendlyDateFormatter().format(item.createdOn)} - ${item.title}',
|
||||||
),
|
),
|
||||||
subtitle: Text(item.body, maxLines: 5),
|
subtitle: Text(item.body, maxLines: 5),
|
||||||
trailing: Icon(Icons.more_vert),
|
trailing: PopupMenuButton<String>(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
onSelected: (value) async {
|
||||||
|
if (value == 'mark_read') {
|
||||||
|
await _markAsRead(context, item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
if (item.isNew)
|
||||||
|
const PopupMenuItem(
|
||||||
|
value: 'mark_read',
|
||||||
|
child: Text('Mark as read'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ class BackendAPI {
|
|||||||
late String _apiKey;
|
late String _apiKey;
|
||||||
late Dio _dio;
|
late Dio _dio;
|
||||||
|
|
||||||
|
Future<void> markNotificationAsRead(int id) async {
|
||||||
|
print('About to call /notifications/$id');
|
||||||
|
await _dio.get('/notifications/$id');
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Notification>> listNotifications(bool? isNew, int offset) async {
|
Future<List<Notification>> listNotifications(bool? isNew, int offset) async {
|
||||||
print('About to call /notifications');
|
print('About to call /notifications');
|
||||||
|
|
||||||
|
|||||||
@@ -244,10 +244,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.18"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -457,10 +457,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.10"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user