// notification_view_model.dart import 'package:flutter/material.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'; import 'package:tm_app/data/services/model/notification__response/notification_response_model.dart'; import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/notification/strings/notification_strings.dart'; class NotificationViewModel with ChangeNotifier { final NotificationsRepository _repository; final HomeViewModel _homeViewModel; NotificationViewModel({ required NotificationsRepository repositoryViewModel, required HomeViewModel homeViewModel, }) : _repository = repositoryViewModel, _homeViewModel = homeViewModel { WidgetsBinding.instance.addPostFrameCallback((_) { init(); }); } void init() async { clearData(); await getNotifications(); await getUnreadNotifications(); await getImportantNotifications(); } void clearData() { allNotificationResponse = null; unreadNotificationResponse = null; importantNotificationResponse = null; _errorMessage = null; currentPageAll = 1; totalPagesAll = 0; currentPageUnread = 1; totalPagesUnread = 0; currentPageImportant = 1; totalPagesImportant = 0; notifyListeners(); } static const int _pageSize = 10; String? _errorMessage; String? get errorMessage => _errorMessage; // All NotificationResponseModel? allNotificationResponse; bool isLoadingAll = false; bool isMoreLoadingAll = false; bool hasMoreAll = true; int currentPageAll = 1; int totalPagesAll = 0; // Unread NotificationResponseModel? unreadNotificationResponse; bool isLoadingUnread = false; bool isMoreLoadingUnread = false; bool hasMoreUnread = true; int currentPageUnread = 1; int totalPagesUnread = 0; // Important NotificationResponseModel? importantNotificationResponse; bool isLoadingImportant = false; bool isMoreLoadingImportant = false; bool hasMoreImportant = true; int currentPageImportant = 1; int totalPagesImportant = 0; Future getNotifications({ int page = 1, bool isPagination = false, }) async { if (isPagination) { isMoreLoadingAll = true; } else { isLoadingAll = true; } notifyListeners(); final int offset = page * _pageSize; final result = await _repository.getNotifications( limit: _pageSize, offset: offset, ); switch (result) { case Ok(): final data = result.value; if (isPagination && allNotificationResponse != null) { final old = allNotificationResponse?.data ?? []; final fresh = data.data ?? []; allNotificationResponse = allNotificationResponse!.copyWith( data: [...old, ...fresh], meta: data.meta, ); } else { allNotificationResponse = data; } _updateImportantFromAll(); currentPageAll = page; totalPagesAll = data.meta?.pages ?? 1; hasMoreAll = currentPageAll < totalPagesAll; break; case Error(): _errorMessage = result.error.toString(); break; } isLoadingAll = false; isMoreLoadingAll = false; notifyListeners(); } Future getUnreadNotifications({ int page = 1, bool isPagination = false, }) async { if (isPagination) { isMoreLoadingUnread = true; } else { isLoadingUnread = true; } notifyListeners(); final int offset = page * _pageSize; final result = await _repository.getUnreadNotifications( limit: _pageSize, offset: offset, ); switch (result) { case Ok(): final data = result.value; if (isPagination && unreadNotificationResponse != null) { final old = unreadNotificationResponse?.data ?? []; final fresh = data.data ?? []; unreadNotificationResponse = data.copyWith(data: [...old, ...fresh]); } else { unreadNotificationResponse = data; } currentPageUnread = page; totalPagesUnread = data.meta?.pages ?? 1; hasMoreUnread = (data.data?.length ?? 0) >= _pageSize; break; case Error(): _errorMessage = result.error.toString(); break; } isLoadingUnread = false; isMoreLoadingUnread = false; notifyListeners(); } Future getImportantNotifications({ int page = 1, bool isPagination = false, }) async { if (isPagination) { isMoreLoadingImportant = true; } else { isLoadingImportant = true; } notifyListeners(); final int offset = page * _pageSize; final result = await _repository.getImportantNotifications( limit: _pageSize, offset: offset, ); switch (result) { case Ok(): final data = result.value; if (isPagination && importantNotificationResponse != null) { final old = importantNotificationResponse?.data ?? []; final fresh = data.data ?? []; importantNotificationResponse = data.copyWith( data: [...old, ...fresh], ); } else { importantNotificationResponse = data; } currentPageImportant = page; totalPagesImportant = data.meta?.pages ?? 1; hasMoreImportant = (data.data?.length ?? 0) >= _pageSize; break; case Error(): _errorMessage = result.error.toString(); _updateImportantFromAll(); break; } isLoadingImportant = false; isMoreLoadingImportant = false; notifyListeners(); } void _updateImportantFromAll() { final all = allNotificationResponse?.data ?? []; final filtered = all.where((n) { final p = n.priority ?? ''; return p.toLowerCase() == 'important'; }).toList(); if (allNotificationResponse != null) { importantNotificationResponse = allNotificationResponse!.copyWith( data: filtered, ); } else { importantNotificationResponse = null; } } Future jumpToPageAll(int page) async { if (totalPagesAll == 0 || page < 1 || page > totalPagesAll) { return; } await getNotifications(page: page); } Future jumpToPageUnread(int page) async { if (totalPagesUnread == 0 || page < 1 || page > totalPagesUnread) { return; } await getUnreadNotifications(page: page); } Future jumpToPageImportant(int page) async { if (totalPagesImportant == 0 || page < 1 || page > totalPagesImportant) { return; } await getImportantNotifications(page: page); } Future markAllAsRead(BuildContext context) async { isLoadingAll = true; _errorMessage = null; notifyListeners(); final result = await _repository.markAllAsRead(); if (!context.mounted) { return; } switch (result) { case Ok>(): final response = result.value; final success = response['success'] as bool? ?? false; final message = response['message'] as String? ?? NotificationStrings.unknownResponse; if (success) { if (allNotificationResponse?.data != null) { final updatedNotifications = allNotificationResponse!.data! .map((n) => n.copyWith(seen: true)) .toList(); allNotificationResponse = allNotificationResponse!.copyWith( data: updatedNotifications, ); unreadNotificationResponse = unreadNotificationResponse?.copyWith( data: [], ); if (importantNotificationResponse?.data != null) { final updatedImportant = importantNotificationResponse!.data! .map((n) => n.copyWith(seen: true)) .toList(); importantNotificationResponse = importantNotificationResponse! .copyWith(data: updatedImportant); } } // Ensure unread list is empty after marking all as read _checkAndUpdateUnreadState(); if (context.mounted) { AppToast.success( context, NotificationStrings.allNotificationMarkedAsRead, ); } setNotificationFalse(context); } else { _errorMessage = message; if (context.mounted) { AppToast.error(context, message); } } break; case Error>(): _errorMessage = result.error.toString(); if (context.mounted) { AppToast.error(context, _errorMessage!); } break; } isLoadingAll = false; notifyListeners(); } String timeAgo(timestamp) { final int ts = int.tryParse(timestamp.toString()) ?? 0; final createdAt = DateTime.fromMillisecondsSinceEpoch(ts * 1000).toLocal(); final now = DateTime.now(); final difference = now.difference(createdAt); if (difference.inMinutes < 1) { return NotificationStrings.now; } else if (difference.inMinutes < 60) { return '${difference.inMinutes} ${NotificationStrings.min}'; } else if (difference.inHours < 24) { return '${difference.inHours} ${NotificationStrings.hour}'; } else if (difference.inDays == 1) { return NotificationStrings.yesterday; } else { return '${difference.inDays} ${NotificationStrings.daysAgo}'; } } void setNotificationFalse(BuildContext context) { _homeViewModel.setUnreadNotificationFalse(); } 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 markAsRead({required String notificationId}) async { final result = await _repository.markAsRead(notificationId: notificationId); switch (result) { case Ok(): // 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(): _errorMessage = result.error.toString(); break; } notifyListeners(); } }