import 'dart:async'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_client.dart'; import '../../core/auth/auth_provider.dart'; import '../../core/hub/hub_provider.dart'; import 'waiter_notification.dart'; // ── Local notification setup ──────────────────────────────────────────────── final _localNotifications = FlutterLocalNotificationsPlugin(); Future initLocalNotifications() async { const android = AndroidInitializationSettings('@mipmap/ic_launcher'); const ios = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); await _localNotifications.initialize( const InitializationSettings(android: android, iOS: ios), ); } Future _showLocalNotification(WaiterNotification n) async { const android = AndroidNotificationDetails( 'meezi_waiter_channel', 'میزی — گارسون', channelDescription: 'اعلان‌های میزی برای گارسون', importance: Importance.max, priority: Priority.high, playSound: true, ); const ios = DarwinNotificationDetails(presentSound: true, presentAlert: true); await _localNotifications.show( n.createdAt.millisecondsSinceEpoch ~/ 1000, n.title, n.body, const NotificationDetails(android: android, iOS: ios), ); } // ── Notification state ─────────────────────────────────────────────────────── class NotificationState { const NotificationState({ this.items = const [], this.isLoading = false, }); final List items; final bool isLoading; int get unreadCount => items.where((n) => !n.isRead).length; NotificationState copyWith({ List? items, bool? isLoading, }) => NotificationState( items: items ?? this.items, isLoading: isLoading ?? this.isLoading, ); } final notificationProvider = StateNotifierProvider((ref) { final notifier = NotificationNotifier(ref); notifier._init(); return notifier; }); class NotificationNotifier extends StateNotifier { NotificationNotifier(this._ref) : super(const NotificationState()); final Ref _ref; StreamSubscription? _hubSub; void _init() { _fetchFromApi(); _subscribeHub(); } void _subscribeHub() { _hubSub?.cancel(); final hub = _ref.read(hubClientProvider); if (hub == null) return; _hubSub = hub.notifications.listen((event) { final n = WaiterNotification.fromHub(event); state = state.copyWith(items: [n, ...state.items]); _showLocalNotification(n); }); } Future _fetchFromApi() async { final session = _ref.read(authProvider); if (session == null) return; state = state.copyWith(isLoading: true); try { final client = _ref.read(apiClientProvider); final res = await client.dio.get>( '/api/cafes/${session.cafeId}/notifications', queryParameters: {'limit': 50}, ); final raw = res.data?['data'] as List?; final items = raw ?.map((e) => WaiterNotification.fromJson(Map.from(e as Map))) .toList() ?? []; state = state.copyWith(items: items, isLoading: false); } catch (_) { state = state.copyWith(isLoading: false); } } Future refresh() => _fetchFromApi(); Future markRead(String id) async { final session = _ref.read(authProvider); if (session == null) return; try { final client = _ref.read(apiClientProvider); await client.dio.post( '/api/cafes/${session.cafeId}/notifications/read', data: {'ids': [id]}, ); } catch (_) {} final updated = state.items.map((n) { if (n.id == id) n.isRead = true; return n; }).toList(); state = state.copyWith(items: updated); } Future markAllRead() async { final session = _ref.read(authProvider); if (session == null) return; try { final client = _ref.read(apiClientProvider); await client.dio.post( '/api/cafes/${session.cafeId}/notifications/read', data: {'all': true}, ); } catch (_) {} final updated = state.items.map((n) { n.isRead = true; return n; }).toList(); state = state.copyWith(items: updated); } @override void dispose() { _hubSub?.cancel(); super.dispose(); } }