import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:flutter/material.dart'; import '../services/dio_client.dart'; class ReverbSocketService { WebSocketChannel? _channel; Timer? _pingTimer; bool _connected = false; late BuildContext _context; late int _ticketId; late Function(Map) _onMessage; late VoidCallback _onBackgroundMessage; late VoidCallback _onAdminMessage; /// Prevent duplicate messages on reconnect final Set _receivedIds = {}; // ============================ // CONNECT // ============================ Future connect({ required BuildContext context, required int ticketId, required Function(Map) onMessage, //required VoidCallback onBackgroundMessage, required VoidCallback onAdminMessage, // 👈 ADD }) async { _context = context; _ticketId = ticketId; _onMessage = onMessage; //_onBackgroundMessage = onBackgroundMessage; _onAdminMessage = onAdminMessage; // 👈 SAVE final uri = Uri.parse( 'ws://10.11.236.74:8080/app/q5fkk5rvcnatvbgadwvl' '?protocol=7&client=flutter&version=1.0', ); debugPrint("🔌 CONNECTING SOCKET → $uri"); _channel = WebSocketChannel.connect(uri); _channel!.stream.listen( _handleMessage, onDone: _handleDisconnect, onError: (e) { debugPrint("❌ SOCKET ERROR: $e"); _handleDisconnect(); }, ); } // ============================ // HANDLE SOCKET MESSAGE // ============================ Future _handleMessage(dynamic raw) async { debugPrint("📥 RAW: $raw"); final payload = jsonDecode(raw); final event = payload['event']?.toString() ?? ''; // -------------------------------- // CONNECTED // -------------------------------- if (event == 'pusher:connection_established') { final socketId = jsonDecode(payload['data'])['socket_id']; debugPrint("✅ CONNECTED | socket_id=$socketId"); await _subscribe(socketId); _startHeartbeat(); _connected = true; return; } // -------------------------------- // HEARTBEAT // -------------------------------- if (event == 'pusher:pong') { debugPrint("💓 PONG"); return; } // -------------------------------- // CHAT MESSAGE // -------------------------------- if ( event == 'NewChatMessage' || event.endsWith('.NewChatMessage') || event.contains('NewChatMessage')) { dynamic data = payload['data']; if (data is String) data = jsonDecode(data); final int msgId = data['id']; final String senderType = data['sender_type'] ?? ''; // 🔁 Prevent duplicates if (_receivedIds.contains(msgId)) { debugPrint("🔁 DUPLICATE MESSAGE IGNORED: $msgId"); return; } _receivedIds.add(msgId); debugPrint("📩 NEW MESSAGE"); debugPrint("🆔 id=$msgId"); debugPrint("👤 sender=$senderType"); debugPrint("💬 text=${data['message']}"); // Always push message to UI _onMessage(Map.from(data)); // 🔔 Increment unread ONLY if ADMIN sent message // 🔔 Increment unread ONLY if ADMIN sent message if (senderType == 'App\\Models\\Admin') { debugPrint("🔔 ADMIN MESSAGE → UNREAD +1"); _onAdminMessage(); // ✅ ACTUAL INCREMENT } return; } } // ============================ // SUBSCRIBE PRIVATE CHANNEL // ============================ Future _subscribe(String socketId) async { debugPrint("📡 SUBSCRIBING private-ticket.$_ticketId"); final dio = DioClient.getInstance(_context); final res = await dio.post( '/broadcasting/auth', data: { 'socket_id': socketId, 'channel_name': 'private-ticket.$_ticketId', }, ); _channel!.sink.add(jsonEncode({ 'event': 'pusher:subscribe', 'data': { 'channel': 'private-ticket.$_ticketId', 'auth': res.data['auth'], }, })); debugPrint("✅ SUBSCRIBED private-ticket.$_ticketId"); } // ============================ // HEARTBEAT // ============================ void _startHeartbeat() { _pingTimer?.cancel(); _pingTimer = Timer.periodic( const Duration(seconds: 30), (_) { if (_connected) { debugPrint("💓 SENDING PING"); _channel?.sink.add(jsonEncode({ 'event': 'pusher:ping', 'data': {} })); } }, ); } // ============================ // HANDLE DISCONNECT // ============================ void _handleDisconnect() { debugPrint("⚠️ SOCKET DISCONNECTED → RECONNECTING"); _connected = false; _pingTimer?.cancel(); _channel?.sink.close(); Future.delayed(const Duration(seconds: 2), () { connect( context: _context, ticketId: _ticketId, onMessage: _onMessage, // onBackgroundMessage: _onBackgroundMessage, onAdminMessage: _onAdminMessage, // 👈 ADD ); }); } // ============================ // MANUAL DISCONNECT // ============================ void disconnect() { debugPrint("❌ SOCKET CLOSED MANUALLY"); _connected = false; _pingTimer?.cancel(); _channel?.sink.close(); } }