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) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
|||||||
import '../api/api_client.dart';
|
import '../api/api_client.dart';
|
||||||
import '../api/api_client_provider.dart';
|
import '../api/api_client_provider.dart';
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
|
import '../notifications/notification_service.dart';
|
||||||
|
|
||||||
part 'chat_request_notifier.g.dart';
|
part 'chat_request_notifier.g.dart';
|
||||||
|
|
||||||
@@ -77,9 +78,12 @@ class ChatRequest extends _$ChatRequest {
|
|||||||
|
|
||||||
_wsSubscription = _channel!.stream.listen(
|
_wsSubscription = _channel!.stream.listen(
|
||||||
(raw) {
|
(raw) {
|
||||||
final data = jsonDecode(raw as String) as Map<String, dynamic>;
|
try {
|
||||||
|
final text = raw is String ? raw : String.fromCharCodes(raw as List<int>);
|
||||||
|
final data = jsonDecode(text) as Map<String, dynamic>;
|
||||||
if (data['type'] == WsMessage.authOk) return;
|
if (data['type'] == WsMessage.authOk) return;
|
||||||
_onRequestReceived(data);
|
_onRequestReceived(data);
|
||||||
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
onError: (_) => _onConnectionError(),
|
onError: (_) => _onConnectionError(),
|
||||||
onDone: () => _onConnectionError(),
|
onDone: () => _onConnectionError(),
|
||||||
@@ -105,7 +109,14 @@ class ChatRequest extends _$ChatRequest {
|
|||||||
final type = data['type'] as String?;
|
final type = data['type'] as String?;
|
||||||
|
|
||||||
if (type == WsMessage.chatRequest) {
|
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) {
|
} else if (type == WsMessage.chatRequestClosed) {
|
||||||
if (state is ChatRequestIncomingData) {
|
if (state is ChatRequestIncomingData) {
|
||||||
state = const ChatRequestListeningData();
|
state = const ChatRequestListeningData();
|
||||||
|
|||||||
@@ -83,6 +83,36 @@ class NotificationService {
|
|||||||
_navigateFromMessage(message.data);
|
_navigateFromMessage(message.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show a local notification programmatically (e.g. from WebSocket while backgrounded)
|
||||||
|
static Future<void> showLocalNotification({
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
Map<String, dynamic>? 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<String, dynamic> data) {
|
static void _navigateFromMessage(Map<String, dynamic> data) {
|
||||||
if (_router == null) return;
|
if (_router == null) return;
|
||||||
final sessionId = data['session_id'] as String?;
|
final sessionId = data['session_id'] as String?;
|
||||||
|
|||||||
Reference in New Issue
Block a user