Merge pull request 'mark single notification as read' (#186) from seen_notification into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/186
This commit is contained in:
a.ghabeli
2025-09-28 14:28:52 +03:30
6 changed files with 130 additions and 27 deletions
+4 -2
View File
@@ -159,8 +159,10 @@ List<SingleChildWidget> get viewModels {
), ),
ChangeNotifierProvider( ChangeNotifierProvider(
create: create:
(context) => (context) => NotificationViewModel(
NotificationViewModel(repositoryViewModel: context.read()), repositoryViewModel: context.read(),
homeViewModel: context.read(),
),
), ),
]; ];
} }
@@ -39,4 +39,10 @@ class NotificationsRepository {
offset: offset, offset: offset,
); );
} }
Future<Result<NotificationResponseModel>> markAsRead({
required String notificationId,
}) {
return _notificationsService.markAsRead(notificationId: notificationId);
}
} }
+10 -4
View File
@@ -6,14 +6,20 @@ class NotificationApi {
} }
static const String markAllAsRead = '/api/v1/notifications/mark'; static const String markAllAsRead = '/api/v1/notifications/mark';
static const String markAsRead = '/api/v1/notifications/mark-as-read'; static const String markAsRead = '/api/v1/notifications/mark';
static const String unreadNotifications = '/api/v1/notifications?seen=false'; static const String unreadNotifications = '/api/v1/notifications?seen=false';
static String getUnreadNotifications({required int limit, required int offset}) { static String getUnreadNotifications({
required int limit,
required int offset,
}) {
return '/api/v1/notifications?seen=false&limit=$limit&offset=$offset'; return '/api/v1/notifications?seen=false&limit=$limit&offset=$offset';
} }
static String getImportantNotifications({required int limit, required int offset}) { static String getImportantNotifications({
required int limit,
required int offset,
}) {
return '/api/v1/notifications?priority=important&limit=$limit&offset=$offset'; return '/api/v1/notifications?priority=important&limit=$limit&offset=$offset';
} }
} }
+4 -6
View File
@@ -1,8 +1,8 @@
// notification_service.dart // notification_service.dart
import 'dart:convert';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/api/notification_api.dart'; import 'package:tm_app/data/services/api/notification_api.dart';
import 'package:tm_app/data/services/model/notification__response/notification_response_model.dart'; import 'package:tm_app/data/services/model/notification__response/notification_response_model.dart';
import '../../core/network/network_manager.dart'; import '../../core/network/network_manager.dart';
class NotificationsService { class NotificationsService {
@@ -34,13 +34,11 @@ class NotificationsService {
Future<Result<NotificationResponseModel>> markAsRead({ Future<Result<NotificationResponseModel>> markAsRead({
required String notificationId, required String notificationId,
}) async { }) async {
final data = jsonEncode({'notification_id': notificationId});
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
NotificationApi.markAsRead, '${NotificationApi.markAsRead}/$notificationId',
method: 'POST', method: 'GET',
(json) => NotificationResponseModel.fromJson(json), (json) => NotificationResponseModel.fromJson(json),
data: data, );
);
return result; return result;
} }
+104 -10
View File
@@ -1,6 +1,5 @@
// notification_view_model.dart // notification_view_model.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/app_toast.dart'; import 'package:tm_app/core/utils/app_toast.dart';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/repositories/notification_repository.dart'; import 'package:tm_app/data/repositories/notification_repository.dart';
@@ -10,9 +9,13 @@ import 'package:tm_app/views/notification/strings/notification_strings.dart';
class NotificationViewModel with ChangeNotifier { class NotificationViewModel with ChangeNotifier {
final NotificationsRepository _repository; final NotificationsRepository _repository;
final HomeViewModel _homeViewModel;
NotificationViewModel({required NotificationsRepository repositoryViewModel}) NotificationViewModel({
: _repository = repositoryViewModel { required NotificationsRepository repositoryViewModel,
required HomeViewModel homeViewModel,
}) : _repository = repositoryViewModel,
_homeViewModel = homeViewModel {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
init(); init();
}); });
@@ -214,10 +217,10 @@ class NotificationViewModel with ChangeNotifier {
void _updateImportantFromAll() { void _updateImportantFromAll() {
final all = allNotificationResponse?.data ?? []; final all = allNotificationResponse?.data ?? [];
final filtered = final filtered =
(all.where((n) { all.where((n) {
final p = n.priority ?? ''; final p = n.priority ?? '';
return p.toLowerCase() == 'important'; return p.toLowerCase() == 'important';
}).toList()); }).toList();
if (allNotificationResponse != null) { if (allNotificationResponse != null) {
importantNotificationResponse = allNotificationResponse!.copyWith( importantNotificationResponse = allNotificationResponse!.copyWith(
@@ -294,6 +297,9 @@ class NotificationViewModel with ChangeNotifier {
} }
} }
// Ensure unread list is empty after marking all as read
_checkAndUpdateUnreadState();
if (context.mounted) { if (context.mounted) {
AppToast.success( AppToast.success(
context, context,
@@ -341,12 +347,100 @@ class NotificationViewModel with ChangeNotifier {
} }
void setNotificationFalse(BuildContext context) { void setNotificationFalse(BuildContext context) {
final homeViewModel = context.read<HomeViewModel>(); _homeViewModel.setUnreadNotificationFalse();
homeViewModel.setUnreadNotificationFalse();
} }
void checkNotifications(BuildContext context) { void _checkAndUpdateUnreadState() {
final homeViewModel = context.read<HomeViewModel>(); // Check if all notifications are read
homeViewModel.checkUnreadNotifications(); final allNotifications = allNotificationResponse?.data ?? [];
final hasUnreadNotifications = allNotifications.any((n) => n.seen != true);
if (!hasUnreadNotifications) {
// Clear unread list if all notifications are read
unreadNotificationResponse = unreadNotificationResponse?.copyWith(
data: [],
);
}
}
int get unreadCount {
return unreadNotificationResponse?.data?.length ?? 0;
}
int get importantCount {
return importantNotificationResponse?.data?.length ?? 0;
}
bool get hasUnreadNotifications {
return unreadCount > 0;
}
bool get hasImportantNotifications {
return importantCount > 0;
}
void checkNotifications() {
_homeViewModel.checkUnreadNotifications();
}
Future<void> markAsRead({required String notificationId}) async {
final result = await _repository.markAsRead(notificationId: notificationId);
switch (result) {
case Ok<NotificationResponseModel>():
// Find and update only the specific notification that matches the ID
final updatedNotifications =
allNotificationResponse?.data?.map((n) {
if (n.id == notificationId) {
return n.copyWith(seen: true);
}
return n;
}).toList();
if (updatedNotifications != null) {
allNotificationResponse = allNotificationResponse!.copyWith(
data: updatedNotifications,
);
}
// Update unread list - remove the notification that was marked as read
if (unreadNotificationResponse?.data != null) {
final updatedUnreadList =
unreadNotificationResponse!.data!
.where((n) => n.id != notificationId)
.toList();
unreadNotificationResponse = unreadNotificationResponse!.copyWith(
data: updatedUnreadList,
);
}
// Update important list - mark the notification as seen if it exists
if (importantNotificationResponse?.data != null) {
final updatedImportantList =
importantNotificationResponse!.data!.map((n) {
if (n.id == notificationId) {
return n.copyWith(seen: true);
}
return n;
}).toList();
importantNotificationResponse = importantNotificationResponse!
.copyWith(data: updatedImportantList);
}
// Check if all notifications are now read
_checkAndUpdateUnreadState();
notifyListeners();
checkNotifications();
break;
case Error<NotificationResponseModel>():
_errorMessage = result.error.toString();
break;
}
notifyListeners();
} }
} }
@@ -123,6 +123,7 @@ class NotificationCard extends StatelessWidget {
const Spacer(), const Spacer(),
TextButton( TextButton(
onPressed: () { onPressed: () {
viewModel.markAsRead(notificationId: notification.id!);
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
@@ -131,11 +132,7 @@ class NotificationCard extends StatelessWidget {
viewModel: viewModel, viewModel: viewModel,
); );
}, },
).then((value) { );
if (context.mounted) {
viewModel.checkNotifications(context);
}
});
}, },
child: Row( child: Row(
children: [ children: [