447 lines
12 KiB
Dart
447 lines
12 KiB
Dart
// 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<void> 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<NotificationResponseModel>():
|
|
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<NotificationResponseModel>():
|
|
_errorMessage = result.error.toString();
|
|
break;
|
|
}
|
|
|
|
isLoadingAll = false;
|
|
isMoreLoadingAll = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> 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<NotificationResponseModel>():
|
|
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<NotificationResponseModel>():
|
|
_errorMessage = result.error.toString();
|
|
break;
|
|
}
|
|
|
|
isLoadingUnread = false;
|
|
isMoreLoadingUnread = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> 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<NotificationResponseModel>():
|
|
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<NotificationResponseModel>():
|
|
_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<void> jumpToPageAll(int page) async {
|
|
if (totalPagesAll == 0 || page < 1 || page > totalPagesAll) {
|
|
return;
|
|
}
|
|
await getNotifications(page: page);
|
|
}
|
|
|
|
Future<void> jumpToPageUnread(int page) async {
|
|
if (totalPagesUnread == 0 || page < 1 || page > totalPagesUnread) {
|
|
return;
|
|
}
|
|
await getUnreadNotifications(page: page);
|
|
}
|
|
|
|
Future<void> jumpToPageImportant(int page) async {
|
|
if (totalPagesImportant == 0 || page < 1 || page > totalPagesImportant) {
|
|
return;
|
|
}
|
|
await getImportantNotifications(page: page);
|
|
}
|
|
|
|
Future<void> markAllAsRead(BuildContext context) async {
|
|
isLoadingAll = true;
|
|
_errorMessage = null;
|
|
notifyListeners();
|
|
|
|
final result = await _repository.markAllAsRead();
|
|
|
|
if (!context.mounted) {
|
|
return;
|
|
}
|
|
|
|
switch (result) {
|
|
case Ok<Map<String, dynamic>>():
|
|
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<Map<String, dynamic>>():
|
|
_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<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();
|
|
}
|
|
}
|