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(
create:
(context) =>
NotificationViewModel(repositoryViewModel: context.read()),
(context) => NotificationViewModel(
repositoryViewModel: context.read(),
homeViewModel: context.read(),
),
),
];
}
@@ -39,4 +39,10 @@ class NotificationsRepository {
offset: offset,
);
}
Future<Result<NotificationResponseModel>> markAsRead({
required String notificationId,
}) {
return _notificationsService.markAsRead(notificationId: notificationId);
}
}
+9 -3
View File
@@ -6,14 +6,20 @@ class NotificationApi {
}
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 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';
}
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';
}
}
+3 -5
View File
@@ -1,8 +1,8 @@
// notification_service.dart
import 'dart:convert';
import 'package:tm_app/core/utils/result.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 '../../core/network/network_manager.dart';
class NotificationsService {
@@ -34,12 +34,10 @@ class NotificationsService {
Future<Result<NotificationResponseModel>> markAsRead({
required String notificationId,
}) async {
final data = jsonEncode({'notification_id': notificationId});
final result = await _networkManager.makeRequest(
NotificationApi.markAsRead,
method: 'POST',
'${NotificationApi.markAsRead}/$notificationId',
method: 'GET',
(json) => NotificationResponseModel.fromJson(json),
data: data,
);
return result;
}
+104 -10
View File
@@ -1,6 +1,5 @@
// notification_view_model.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/result.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 {
final NotificationsRepository _repository;
final HomeViewModel _homeViewModel;
NotificationViewModel({required NotificationsRepository repositoryViewModel})
: _repository = repositoryViewModel {
NotificationViewModel({
required NotificationsRepository repositoryViewModel,
required HomeViewModel homeViewModel,
}) : _repository = repositoryViewModel,
_homeViewModel = homeViewModel {
WidgetsBinding.instance.addPostFrameCallback((_) {
init();
});
@@ -214,10 +217,10 @@ class NotificationViewModel with ChangeNotifier {
void _updateImportantFromAll() {
final all = allNotificationResponse?.data ?? [];
final filtered =
(all.where((n) {
all.where((n) {
final p = n.priority ?? '';
return p.toLowerCase() == 'important';
}).toList());
}).toList();
if (allNotificationResponse != null) {
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) {
AppToast.success(
context,
@@ -341,12 +347,100 @@ class NotificationViewModel with ChangeNotifier {
}
void setNotificationFalse(BuildContext context) {
final homeViewModel = context.read<HomeViewModel>();
homeViewModel.setUnreadNotificationFalse();
_homeViewModel.setUnreadNotificationFalse();
}
void checkNotifications(BuildContext context) {
final homeViewModel = context.read<HomeViewModel>();
homeViewModel.checkUnreadNotifications();
void _checkAndUpdateUnreadState() {
// Check if all notifications are read
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(),
TextButton(
onPressed: () {
viewModel.markAsRead(notificationId: notification.id!);
showDialog(
context: context,
builder: (context) {
@@ -131,11 +132,7 @@ class NotificationCard extends StatelessWidget {
viewModel: viewModel,
);
},
).then((value) {
if (context.mounted) {
viewModel.checkNotifications(context);
}
});
);
},
child: Row(
children: [