From 2e80434e9bb264b03bf70089542f9b0fd289835f Mon Sep 17 00:00:00 2001 From: ramadhan sjamsani Date: Thu, 9 Apr 2026 14:57:36 +0800 Subject: [PATCH] Phase 3.1: Local notification for WS chat requests, router fix, cleanup - Show local notification (sound + vibrate) when chat_request arrives via WebSocket while mitra app is backgrounded - Add NotificationService.showLocalNotification() for programmatic use - Fix router redirect: don't redirect auth routes to splash during loading - Handle binary/string WebSocket frames in ChatRequestNotifier - Remove debug logging from backend and Flutter - Control center: mitra ping config UI - Both apps: dynamic ping, FCM deep-linking, unread badges Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lib/core/chat/chat_request_notifier.dart | 19 +++++++++--- .../notifications/notification_service.dart | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/mitra_app/lib/core/chat/chat_request_notifier.dart b/mitra_app/lib/core/chat/chat_request_notifier.dart index abb237e..7b1880f 100644 --- a/mitra_app/lib/core/chat/chat_request_notifier.dart +++ b/mitra_app/lib/core/chat/chat_request_notifier.dart @@ -7,6 +7,7 @@ import 'package:web_socket_channel/web_socket_channel.dart'; import '../api/api_client.dart'; import '../api/api_client_provider.dart'; import '../constants.dart'; +import '../notifications/notification_service.dart'; part 'chat_request_notifier.g.dart'; @@ -77,9 +78,12 @@ class ChatRequest extends _$ChatRequest { _wsSubscription = _channel!.stream.listen( (raw) { - final data = jsonDecode(raw as String) as Map; - if (data['type'] == WsMessage.authOk) return; - _onRequestReceived(data); + try { + final text = raw is String ? raw : String.fromCharCodes(raw as List); + final data = jsonDecode(text) as Map; + if (data['type'] == WsMessage.authOk) return; + _onRequestReceived(data); + } catch (_) {} }, onError: (_) => _onConnectionError(), onDone: () => _onConnectionError(), @@ -105,7 +109,14 @@ class ChatRequest extends _$ChatRequest { final type = data['type'] as String?; if (type == WsMessage.chatRequest) { - state = ChatRequestIncomingData(data['session_id'] as String); + final sessionId = data['session_id'] as String; + state = ChatRequestIncomingData(sessionId); + // Show local notification so mitra is alerted even when app is backgrounded + NotificationService.showLocalNotification( + title: 'Permintaan Chat Baru', + body: 'Ada pelanggan yang ingin curhat! Ketuk untuk menerima.', + data: {'type': 'chat_request', 'session_id': sessionId, 'action': 'open_accept'}, + ); } else if (type == WsMessage.chatRequestClosed) { if (state is ChatRequestIncomingData) { state = const ChatRequestListeningData(); diff --git a/mitra_app/lib/core/notifications/notification_service.dart b/mitra_app/lib/core/notifications/notification_service.dart index bd1aca3..cb70a5e 100644 --- a/mitra_app/lib/core/notifications/notification_service.dart +++ b/mitra_app/lib/core/notifications/notification_service.dart @@ -83,6 +83,36 @@ class NotificationService { _navigateFromMessage(message.data); } + /// Show a local notification programmatically (e.g. from WebSocket while backgrounded) + static Future showLocalNotification({ + required String title, + required String body, + Map? data, + }) async { + await _localNotifications.show( + id: DateTime.now().millisecondsSinceEpoch % 100000, + title: title, + body: body, + notificationDetails: NotificationDetails( + android: AndroidNotificationDetails( + _channel.id, + _channel.name, + channelDescription: _channel.description, + importance: Importance.high, + priority: Priority.high, + playSound: true, + enableVibration: true, + ), + iOS: const DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + ), + ), + payload: data != null ? jsonEncode(data) : null, + ); + } + static void _navigateFromMessage(Map data) { if (_router == null) return; final sessionId = data['session_id'] as String?;