| .. | .. |
|---|
| 5 | 5 | import 'package:crypto/crypto.dart'; |
|---|
| 6 | 6 | |
|---|
| 7 | 7 | import 'package:bonsoir/bonsoir.dart'; |
|---|
| 8 | +import 'package:connectivity_plus/connectivity_plus.dart'; |
|---|
| 8 | 9 | import 'package:flutter/foundation.dart'; |
|---|
| 9 | 10 | import 'package:flutter/widgets.dart'; |
|---|
| 10 | 11 | import 'package:path_provider/path_provider.dart' as pp; |
|---|
| .. | .. |
|---|
| 50 | 51 | bool _intentionalClose = false; |
|---|
| 51 | 52 | String? _clientId; |
|---|
| 52 | 53 | String? _lastDiscoveredHost; |
|---|
| 54 | + StreamSubscription? _connectivitySub; |
|---|
| 55 | + List<ConnectivityResult>? _lastConnectivity; |
|---|
| 53 | 56 | StreamSubscription? _updatesSub; |
|---|
| 54 | 57 | |
|---|
| 55 | 58 | // Message deduplication |
|---|
| .. | .. |
|---|
| 103 | 106 | |
|---|
| 104 | 107 | _intentionalClose = false; |
|---|
| 105 | 108 | _setStatus(ConnectionStatus.connecting); |
|---|
| 109 | + |
|---|
| 110 | + // Start listening for network changes (WiFi↔cellular, VPN connect/disconnect) |
|---|
| 111 | + _connectivitySub ??= Connectivity().onConnectivityChanged.listen((results) { |
|---|
| 112 | + if (_lastConnectivity != null && !_intentionalClose) { |
|---|
| 113 | + final changed = results.length != _lastConnectivity!.length || |
|---|
| 114 | + !results.every((r) => _lastConnectivity!.contains(r)); |
|---|
| 115 | + if (changed) { |
|---|
| 116 | + _mqttLog('MQTT: network changed: ${results.map((r) => r.name).join(",")} — forcing reconnect'); |
|---|
| 117 | + // Force disconnect and reconnect on new network |
|---|
| 118 | + final client = _client; |
|---|
| 119 | + if (client != null) { |
|---|
| 120 | + _intentionalClose = true; |
|---|
| 121 | + client.autoReconnect = false; |
|---|
| 122 | + try { client.disconnect(); } catch (_) {} |
|---|
| 123 | + _client = null; |
|---|
| 124 | + _updatesSub?.cancel(); |
|---|
| 125 | + _updatesSub = null; |
|---|
| 126 | + _intentionalClose = false; |
|---|
| 127 | + } |
|---|
| 128 | + _lastDiscoveredHost = null; // Clear cached discovery — subnet may have changed |
|---|
| 129 | + connectedHost = null; |
|---|
| 130 | + connectedVia = null; |
|---|
| 131 | + _setStatus(ConnectionStatus.reconnecting); |
|---|
| 132 | + Future.delayed(const Duration(milliseconds: 500), () { |
|---|
| 133 | + if (!_intentionalClose) connect(); |
|---|
| 134 | + }); |
|---|
| 135 | + } |
|---|
| 136 | + } |
|---|
| 137 | + _lastConnectivity = results; |
|---|
| 138 | + }); |
|---|
| 106 | 139 | |
|---|
| 107 | 140 | // Load trusted cert fingerprint for TOFU verification |
|---|
| 108 | 141 | if (_trustedFingerprint == null) await _loadTrustedFingerprint(); |
|---|
| .. | .. |
|---|
| 724 | 757 | _intentionalClose = true; |
|---|
| 725 | 758 | _updatesSub?.cancel(); |
|---|
| 726 | 759 | _updatesSub = null; |
|---|
| 760 | + _connectivitySub?.cancel(); |
|---|
| 761 | + _connectivitySub = null; |
|---|
| 727 | 762 | |
|---|
| 728 | 763 | try { |
|---|
| 729 | 764 | _client?.disconnect(); |
|---|