import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:pubnub/pubnub.dart'; import '../../providers/public_keys_provider.dart'; import '../../providers/utils_singleton.dart'; class PubNubService { // ── singleton ───────────────────────────────────────────────── static final PubNubService _instance = PubNubService._internal(); factory PubNubService() => _instance; PubNubService._internal(); // ── internal state ──────────────────────────────────────────── PubNub? _pubnub; Subscription? _subscription; Stream get inboundStream => _subscription!.messages; // ── init — call after publickeys.loadPublicKeys() ───────────── void init() { if (!publickeys.guardKey(publickeys.pubnubPublishKey, 'pubnubPublishKey')) return; if (!publickeys.guardKey(publickeys.pubnubSubscribeKey, 'pubnubSubscribeKey')) return; _pubnub = PubNub( defaultKeyset: Keyset( publishKey: publickeys.pubnubPublishKey!, subscribeKey: publickeys.pubnubSubscribeKey!, userId: UserId('${utils.user?.username ?? 'anonymous'}'), ), ); print('✅ [PubNubService] initialized'); } bool get isInitialized => _pubnub != null; // ── subscribe ───────────────────────────────────────────────── void subscribeTo(Set channels) { if (!isInitialized) { debugPrint('[PubNubService] not initialized — call init() first'); return; } _subscription?.dispose(); _subscription = _pubnub!.subscribe(channels: channels); _subscription!.messages.listen((envelope) { print('🛈 [PubNub] MSG on ${envelope.channel} ⇢ ${envelope.payload}'); }); print('✅ [PubNub] Subscribed to $channels'); } // ── publish ─────────────────────────────────────────────────── Future publishJson(String channel, Object payload) async { if (!isInitialized) { debugPrint('[PubNubService] not initialized — call init() first'); return; } final encoded = jsonEncode(payload); print('⏫ [PubNub] PUBLISH → $channel : $encoded'); await _pubnub!.channel(channel).publish(encoded); } Future publishString(String channel, String text) async { if (!isInitialized) { debugPrint('[PubNubService] not initialized — call init() first'); return; } print('⏫ [PubNub] PUBLISH → $channel : $text'); await _pubnub!.channel(channel).publish(text); } // ── dispose ─────────────────────────────────────────────────── Future dispose() async { await _subscription?.dispose(); _pubnub = null; print('🛑 [PubNub] disposed'); } } // ── dispatcher — for sending offers ────────────────────────── // call sendRideOffer() only after publickeys.loadPublicKeys() Future sendRideOffer({ required int rideId, required String driverUsername, String message = 'New ride request', }) async { if (!publickeys.guardKey(publickeys.pubnubPublishKey, 'pubnubPublishKey')) return; if (!publickeys.guardKey(publickeys.pubnubSubscribeKey, 'pubnubSubscribeKey')) return; final dispatcher = PubNub( defaultKeyset: Keyset( publishKey: publickeys.pubnubPublishKey!, subscribeKey: publickeys.pubnubSubscribeKey!, userId: const UserId('dispatcher'), ), ); final payload = { 'rideId': rideId, 'candidateUsernames': [driverUsername], 'message': message, }; await dispatcher.channel('offers.drivers').publish(jsonEncode(payload)); print('Offer $rideId sent to $driverUsername'); }