Skip to content

Commit 7aaa270

Browse files
committed
Clearer Bluetooth required message when trying to connect / no scan button if BLE is disabled
1 parent aa41ed6 commit 7aaa270

File tree

6 files changed

+88
-4
lines changed

6 files changed

+88
-4
lines changed

android/local.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ sdk.dir=/opt/homebrew/share/android-commandlinetools
22
flutter.sdk=/opt/homebrew/share/flutter
33
flutter.buildMode=release
44
flutter.versionName=1.0.0
5-
flutter.versionCode=1769747121
5+
flutter.versionCode=1

lib/providers/app_state_provider.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ class AppStateProvider extends ChangeNotifier {
8989
String? _connectionError;
9090
bool _isAuthError = false; // Track if connection failed due to auth
9191

92+
// Bluetooth adapter state (on/off)
93+
BluetoothAdapterState _bluetoothAdapterState = BluetoothAdapterState.unknown;
94+
StreamSubscription? _adapterStateSubscription;
95+
9296
// GPS state
9397
GpsStatus _gpsStatus = GpsStatus.permissionDenied;
9498
Position? _currentPosition;
@@ -194,6 +198,9 @@ class AppStateProvider extends ChangeNotifier {
194198
ConnectionStep get connectionStep => _connectionStep;
195199
String? get connectionError => _connectionError;
196200
bool get isAuthError => _isAuthError;
201+
BluetoothAdapterState get bluetoothAdapterState => _bluetoothAdapterState;
202+
bool get isBluetoothOn => _bluetoothAdapterState == BluetoothAdapterState.on;
203+
bool get isBluetoothOff => _bluetoothAdapterState == BluetoothAdapterState.off;
197204
GpsStatus get gpsStatus => _gpsStatus;
198205
Position? get currentPosition => _currentPosition;
199206
DeviceModel? get deviceModel => _deviceModel;
@@ -380,6 +387,20 @@ class AppStateProvider extends ChangeNotifier {
380387
// Set device ID for API
381388
_apiService.setDeviceId(_deviceId);
382389

390+
// Listen to Bluetooth adapter state changes (on/off)
391+
_adapterStateSubscription = _bluetoothService.adapterStateStream.listen((state) {
392+
final previousState = _bluetoothAdapterState;
393+
_bluetoothAdapterState = state;
394+
395+
if (state != previousState) {
396+
debugLog('[BLE] Adapter state changed: $state');
397+
398+
// If Bluetooth was turned off while connected, the BLE disconnect handler
399+
// will take care of session cleanup via connectionStream
400+
notifyListeners();
401+
}
402+
});
403+
383404
// Listen to Bluetooth connection changes
384405
_bluetoothService.connectionStream.listen((status) async {
385406
_connectionStatus = status;
@@ -2541,6 +2562,7 @@ class AppStateProvider extends ChangeNotifier {
25412562

25422563
@override
25432564
void dispose() {
2565+
_adapterStateSubscription?.cancel();
25442566
_logRxDataSubscription?.cancel();
25452567
_noiseFloorSubscription?.cancel();
25462568
_batterySubscription?.cancel();

lib/screens/connection_screen.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
7777

7878
// Build FAB for scanning - only show when fully disconnected and idle
7979
// Hide FAB entirely during maintenance mode (maintenance UI has its own buttons)
80+
// Hide FAB when Bluetooth is off (shows full-screen message instead)
8081
Widget? fab;
8182
if (appState.connectionStep == ConnectionStep.disconnected &&
82-
(!appState.maintenanceMode || appState.offlineMode)) {
83+
(!appState.maintenanceMode || appState.offlineMode) &&
84+
!appState.isBluetoothOff) {
8385
// Offline mode bypasses both zone and maintenance checks
8486
final canScan = appState.offlineMode || appState.inZone == true;
8587
fab = isLandscape
@@ -806,8 +808,9 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
806808
}) {
807809
final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
808810

809-
return SingleChildScrollView(
810-
child: Center(
811+
// Use Center with CustomScrollView for both vertical centering and scroll capability
812+
return Center(
813+
child: SingleChildScrollView(
811814
child: Padding(
812815
padding: EdgeInsets.all(isLandscape ? 16 : 32),
813816
child: Column(
@@ -930,6 +933,24 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
930933
);
931934
}
932935

936+
// Show Bluetooth off message (takes priority over zone checks)
937+
if (appState.isBluetoothOff) {
938+
return Column(
939+
children: [
940+
_buildZoneStatusBar(context, appState),
941+
Expanded(
942+
child: _buildMessageContent(
943+
context: context,
944+
icon: Icons.bluetooth_disabled,
945+
iconColor: Colors.red.withValues(alpha: 0.7),
946+
title: 'Bluetooth is Off',
947+
message: 'Please enable Bluetooth to scan for MeshCore devices.',
948+
),
949+
),
950+
],
951+
);
952+
}
953+
933954
// Show onboarding message when outside zone (but NOT when offline mode is enabled)
934955
if (appState.inZone == false && !appState.offlineMode) {
935956
return Column(

lib/services/bluetooth/bluetooth_service.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ class DiscoveredDevice {
2929
String toString() => 'DiscoveredDevice($name, $id, rssi=$rssi)';
3030
}
3131

32+
/// Bluetooth adapter state
33+
enum BluetoothAdapterState {
34+
unknown,
35+
on,
36+
off,
37+
turningOn,
38+
turningOff,
39+
unavailable,
40+
}
41+
3242
/// Abstract Bluetooth service interface
3343
/// Platform implementations provided by MobileBluetoothService and WebBluetoothService
3444
abstract class BluetoothService {
@@ -38,6 +48,9 @@ abstract class BluetoothService {
3848
/// Stream of received data from device
3949
Stream<Uint8List> get dataStream;
4050

51+
/// Stream of Bluetooth adapter state changes (on/off)
52+
Stream<BluetoothAdapterState> get adapterStateStream;
53+
4154
/// Current connection status
4255
ConnectionStatus get connectionStatus;
4356

lib/services/bluetooth/mobile_bluetooth.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ class MobileBluetoothService implements BluetoothService {
6363
return _dataController!.stream;
6464
}
6565

66+
@override
67+
Stream<BluetoothAdapterState> get adapterStateStream {
68+
return fbp.FlutterBluePlus.adapterState.map((state) {
69+
switch (state) {
70+
case fbp.BluetoothAdapterState.on:
71+
return BluetoothAdapterState.on;
72+
case fbp.BluetoothAdapterState.off:
73+
return BluetoothAdapterState.off;
74+
case fbp.BluetoothAdapterState.turningOn:
75+
return BluetoothAdapterState.turningOn;
76+
case fbp.BluetoothAdapterState.turningOff:
77+
return BluetoothAdapterState.turningOff;
78+
case fbp.BluetoothAdapterState.unavailable:
79+
return BluetoothAdapterState.unavailable;
80+
default:
81+
return BluetoothAdapterState.unknown;
82+
}
83+
});
84+
}
85+
6686
@override
6787
ConnectionStatus get connectionStatus => _connectionStatus;
6888

lib/services/bluetooth/web_bluetooth.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ class WebBluetoothService implements BluetoothService {
2929
@override
3030
Stream<Uint8List> get dataStream => _dataController.stream;
3131

32+
@override
33+
Stream<BluetoothAdapterState> get adapterStateStream {
34+
// Web Bluetooth availability stream (true = available, false = not available)
35+
return _webBluetooth.isAvailable.map((available) {
36+
return available ? BluetoothAdapterState.on : BluetoothAdapterState.off;
37+
});
38+
}
39+
3240
@override
3341
ConnectionStatus get connectionStatus => _connectionStatus;
3442

0 commit comments

Comments
 (0)