From 913bf0e839d3972a413c7fe94d9db5e79dbb6969 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Wed, 27 May 2026 09:58:39 -0400 Subject: [PATCH] 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 --- CLAUDE.md | 11 +++- .../lib/notifications/notification_list.dart | 59 ++++++++++++++----- frontend/lib/utils/oott_api.dart | 5 ++ frontend/pubspec.lock | 8 +-- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 95fa241..bd9541b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,9 +19,9 @@ For Flutter/Dart code: ## Project commands -- `` - Run the backend -- `` - Run the front-end for the web -- `` - Run the backend tests +- `sudo cargo run` from the `backend/` folder - Run the backend (you need sudo to enable the process to attach itself to the network channel) +- `flutter run -d web-server --web-port 3333` from the `frontend/` folder - Run the front-end for the web +- `run_tests.sh` - Run the backend tests ## Architecture @@ -40,5 +40,10 @@ For Flutter/Dart code: - 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 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 +- 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 diff --git a/frontend/lib/notifications/notification_list.dart b/frontend/lib/notifications/notification_list.dart index 935408c..c36e226 100644 --- a/frontend/lib/notifications/notification_list.dart +++ b/frontend/lib/notifications/notification_list.dart @@ -32,6 +32,34 @@ class _NotificationListState extends State { }, ); + Future _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 Widget build(BuildContext context) { return Scaffold( @@ -87,19 +115,8 @@ class _NotificationListState extends State { children: [ Dismissible( key: UniqueKey(), - onDismissed: (direction) { - setState(() { - // _events!.removeAt(index); - }); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Event marked as read'), - behavior: SnackBarBehavior.floating, - showCloseIcon: true, - ), - ); - }, + confirmDismiss: (direction) => + _markAsRead(context, item), background: Container( color: Theme.of( context, @@ -114,7 +131,21 @@ class _NotificationListState extends State { '${FriendlyDateFormatter().format(item.createdOn)} - ${item.title}', ), subtitle: Text(item.body, maxLines: 5), - trailing: Icon(Icons.more_vert), + trailing: PopupMenuButton( + 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: () {}, isThreeLine: true, ), diff --git a/frontend/lib/utils/oott_api.dart b/frontend/lib/utils/oott_api.dart index d50435d..4a99d92 100644 --- a/frontend/lib/utils/oott_api.dart +++ b/frontend/lib/utils/oott_api.dart @@ -66,6 +66,11 @@ class BackendAPI { late String _apiKey; late Dio _dio; + Future markNotificationAsRead(int id) async { + print('About to call /notifications/$id'); + await _dio.get('/notifications/$id'); + } + Future> listNotifications(bool? isNew, int offset) async { print('About to call /notifications'); diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 9ea4bcb..f7d9361 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -244,10 +244,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -457,10 +457,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" typed_data: dependency: transitive description: