Files
mobile-app/lib/features/notifications/presentation/providers/notification_provider.dart

172 lines
4.9 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
import 'package:real_estate_mobile/features/notifications/data/models/notification_model.dart';
import 'package:real_estate_mobile/features/notifications/data/notification_repository.dart';
/// Unread notification count — polled every 30 seconds.
final unreadCountProvider =
StateNotifierProvider<UnreadCountNotifier, int>((ref) {
return UnreadCountNotifier(NotificationRepository());
});
class UnreadCountNotifier extends StateNotifier<int> {
final NotificationRepository _repo;
Timer? _timer;
UnreadCountNotifier(this._repo) : super(0) {
refresh();
_timer = Timer.periodic(const Duration(seconds: 30), (_) => refresh());
}
Future<void> refresh() async {
// Skip if no token (logged out) — prevents 401 errors
final token = await SecureStorage.getAccessToken();
if (token == null) {
if (mounted) state = 0;
return;
}
try {
final count = await _repo.getUnreadCount();
if (mounted) state = count;
} catch (_) {
// Silently fail — badge just won't update
}
}
/// Stop polling (call on logout).
void stopPolling() {
_timer?.cancel();
_timer = null;
if (mounted) state = 0;
}
/// Restart polling (call on login).
void startPolling() {
_timer?.cancel();
refresh();
_timer = Timer.periodic(const Duration(seconds: 30), (_) => refresh());
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
}
/// Notification list state.
class NotificationListState {
final List<AppNotification> notifications;
final bool isLoading;
final bool isLoadingMore;
final String? error;
final String filter; // 'all' | 'unread'
final int currentPage;
final int totalPages;
const NotificationListState({
this.notifications = const [],
this.isLoading = false,
this.isLoadingMore = false,
this.error,
this.filter = 'all',
this.currentPage = 1,
this.totalPages = 1,
});
NotificationListState copyWith({
List<AppNotification>? notifications,
bool? isLoading,
bool? isLoadingMore,
String? error,
String? filter,
int? currentPage,
int? totalPages,
}) {
return NotificationListState(
notifications: notifications ?? this.notifications,
isLoading: isLoading ?? this.isLoading,
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
error: error,
filter: filter ?? this.filter,
currentPage: currentPage ?? this.currentPage,
totalPages: totalPages ?? this.totalPages,
);
}
}
final notificationListProvider =
StateNotifierProvider<NotificationListNotifier, NotificationListState>(
(ref) {
return NotificationListNotifier(NotificationRepository(), ref);
});
class NotificationListNotifier extends StateNotifier<NotificationListState> {
final NotificationRepository _repo;
final Ref _ref;
NotificationListNotifier(this._repo, this._ref)
: super(const NotificationListState());
Future<void> loadNotifications({String filter = 'all'}) async {
state = state.copyWith(isLoading: true, error: null, filter: filter);
try {
final response =
await _repo.getNotifications(filter: filter, page: 1, limit: 20);
state = state.copyWith(
notifications: response.notifications,
isLoading: false,
currentPage: response.currentPage,
totalPages: response.totalPages,
);
} catch (e) {
state = state.copyWith(isLoading: false, error: e.toString());
}
}
Future<void> loadMore() async {
if (state.isLoadingMore || state.currentPage >= state.totalPages) return;
state = state.copyWith(isLoadingMore: true);
try {
final nextPage = state.currentPage + 1;
final response = await _repo.getNotifications(
filter: state.filter, page: nextPage, limit: 20);
state = state.copyWith(
notifications: [...state.notifications, ...response.notifications],
isLoadingMore: false,
currentPage: response.currentPage,
totalPages: response.totalPages,
);
} catch (_) {
state = state.copyWith(isLoadingMore: false);
}
}
Future<void> markAsRead(String notificationId) async {
try {
await _repo.markAsRead(notificationId);
state = state.copyWith(
notifications: state.notifications.map((n) {
if (n.id == notificationId) return n.copyWith(read: true);
return n;
}).toList(),
);
_ref.read(unreadCountProvider.notifier).refresh();
} catch (_) {}
}
Future<void> markAllAsRead() async {
try {
await _repo.markAllAsRead();
state = state.copyWith(
notifications:
state.notifications.map((n) => n.copyWith(read: true)).toList(),
);
_ref.read(unreadCountProvider.notifier).refresh();
} catch (_) {}
}
}