Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions open_wearable/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
PODS:
- audioplayers_darwin (0.0.1):
- Flutter
- FlutterMacOS
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
Expand Down Expand Up @@ -48,6 +51,8 @@ PODS:
- SwiftProtobuf
- open_file_ios (1.0.3):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- SDWebImage (5.21.5):
Expand All @@ -66,20 +71,25 @@ PODS:
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
- Flutter
- ZIPFoundation (0.9.19)

DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`)
- Flutter (from `Flutter`)
- flutter_archive (from `.symlinks/plugins/flutter_archive/ios`)
- mcumgr_flutter (from `.symlinks/plugins/mcumgr_flutter/ios`)
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)

SPEC REPOS:
trunk:
Expand All @@ -93,6 +103,8 @@ SPEC REPOS:
- ZIPFoundation

EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
file_selector_ios:
Expand All @@ -105,6 +117,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/mcumgr_flutter/ios"
open_file_ios:
:path: ".symlinks/plugins/open_file_ios/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
Expand All @@ -115,8 +129,11 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/universal_ble/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"

SPEC CHECKSUMS:
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Expand All @@ -126,6 +143,7 @@ SPEC CHECKSUMS:
iOSMcuManagerLibrary: e9555825af11a61744fe369c12e1e66621061b58
mcumgr_flutter: 969e99cc15e9fe658242669ce1075bf4612aef8a
open_file_ios: 46184d802ee7959203f6392abcfa0dd49fdb5be0
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
Expand All @@ -135,6 +153,7 @@ SPEC CHECKSUMS:
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c

PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce
Expand Down
79 changes: 62 additions & 17 deletions open_wearable/lib/models/bluetooth_auto_connector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'logger.dart';
/// - `start()` / `stop()` lifecycle control and `onWearableConnected` callback.
class BluetoothAutoConnector {
static const Duration _scanRetryInterval = Duration(seconds: 3);
static const Duration _iosScanRestartDelay = Duration(seconds: 1);

final NavigatorState? Function() navStateGetter;
final WearableManager wearableManager;
Expand All @@ -46,6 +47,10 @@ class BluetoothAutoConnector {
final Set<String> _connectedDeviceIds = <String>{};
final Map<String, int> _connectedNameCounts = <String, int>{};
final Set<String> _pendingDeviceIds = <String>{};
final Set<String> _disconnectListenerAttachedIds = <String>{};

DateTime? _lastScanStoppedAt;
bool _isStartingScan = false;

BluetoothAutoConnector({
required this.navStateGetter,
Expand All @@ -60,6 +65,7 @@ class BluetoothAutoConnector {
_connectedDeviceIds.clear();
_connectedNameCounts.clear();
_pendingDeviceIds.clear();
_disconnectListenerAttachedIds.clear();

// Load the last connected names
await _reloadTargetNames(token: token, reloadPrefs: false);
Expand Down Expand Up @@ -91,7 +97,9 @@ class BluetoothAutoConnector {
_preferencesSubscription = null;
_isAttemptingConnection = false;
_isConnecting = false;
_isStartingScan = false;
_pendingDeviceIds.clear();
_disconnectListenerAttachedIds.clear();
_scanRetryTimer?.cancel();
_scanRetryTimer = null;
_stopScanning();
Expand Down Expand Up @@ -238,22 +246,44 @@ class BluetoothAutoConnector {
// Stop scanning immediately when a successful connection is made
_stopScanning();

// Set up the disconnect listener to trigger a scan for the saved name.
wearable.addDisconnectListener(() async {
if (token != _sessionToken) {
return;
}
logger.i(
"Device ${wearable.name} disconnected. Initiating reconnection scan.",
);
_markDisconnected(deviceId: wearable.deviceId, deviceName: wearable.name);
// Set up one disconnect listener per device id to avoid reconnection storms.
if (_disconnectListenerAttachedIds.add(wearable.deviceId)) {
wearable.addDisconnectListener(() async {
if (token != _sessionToken) {
return;
}
logger.i(
"Device ${wearable.name} disconnected. Initiating reconnection scan.",
);
_disconnectListenerAttachedIds.remove(wearable.deviceId);
_markDisconnected(
deviceId: wearable.deviceId,
deviceName: wearable.name,
);

await _syncTargetsWithPreferences(token: token);
await _syncTargetsWithPreferences(token: token);

if (_hasUnconnectedTargets()) {
_attemptConnection();
}
});
if (_hasUnconnectedTargets()) {
await _attemptConnection();
}
});
}
}

Future<void> _applyIosScanCooldownIfNeeded() async {
if (!Platform.isIOS) {
return;
}
final stoppedAt = _lastScanStoppedAt;
if (stoppedAt == null) {
return;
}

final elapsed = DateTime.now().difference(stoppedAt);
if (elapsed >= _iosScanRestartDelay) {
return;
}
await Future.delayed(_iosScanRestartDelay - elapsed);
}

Future<void> _attemptConnection({int? token}) async {
Expand Down Expand Up @@ -291,7 +321,15 @@ class BluetoothAutoConnector {

if (_targetNames.isNotEmpty && _hasUnconnectedTargets()) {
_setupScanListener();
await wearableManager.startScan();
if (!_isStartingScan) {
_isStartingScan = true;
try {
await _applyIosScanCooldownIfNeeded();
await wearableManager.startScan();
} finally {
_isStartingScan = false;
}
}
}
} catch (error, stackTrace) {
logger.w('Auto-connect attempt failed: $error\n$stackTrace');
Expand Down Expand Up @@ -354,7 +392,7 @@ class BluetoothAutoConnector {
}

Future<void> _restartScanIfNeeded() async {
if (_isConnecting || _isAttemptingConnection) {
if (_isConnecting || _isAttemptingConnection || _isStartingScan) {
return;
}
if (_scanSubscription != null) {
Expand All @@ -365,14 +403,21 @@ class BluetoothAutoConnector {
}
try {
_setupScanListener();
await wearableManager.startScan();
_isStartingScan = true;
try {
await _applyIosScanCooldownIfNeeded();
await wearableManager.startScan();
} finally {
_isStartingScan = false;
}
} catch (error, stackTrace) {
logger.w('Failed to restart auto-connect scan: $error\n$stackTrace');
_stopScanning();
}
}

void _stopScanning() {
_lastScanStoppedAt = DateTime.now();
_scanSubscription?.cancel();
_scanSubscription = null;
}
Expand Down
7 changes: 7 additions & 0 deletions open_wearable/lib/models/wearable_connector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class WearableConnector {
// final Map<DiscoveredDevice, Wearable> _connectedDevices = {};

final WearableManager _wm;
final Set<String> _trackedWearableIds = <String>{};

final _events = StreamController<WearableEvent>.broadcast();
Stream<WearableEvent> get events => _events.stream;
Expand All @@ -70,8 +71,14 @@ class WearableConnector {
}

void _handleConnection(Wearable wearable) {
if (_trackedWearableIds.contains(wearable.deviceId)) {
return;
}
_trackedWearableIds.add(wearable.deviceId);

//_connectedDevices[device] = wearable;
wearable.addDisconnectListener(() {
_trackedWearableIds.remove(wearable.deviceId);
_events.add(
WearableDisconnectedEvent(
DisconnectReason.system,
Expand Down
2 changes: 0 additions & 2 deletions open_wearable/macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import file_selector_macos
import flutter_archive
import open_file_mac
import package_info_plus
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import universal_ble
Expand All @@ -25,7 +24,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterArchivePlugin.register(with: registry.registrar(forPlugin: "FlutterArchivePlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin"))
Expand Down
Loading