diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 1739caf..743c41b 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -5,7 +5,8 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:gamepads/gamepads.dart'; -import 'package:swift_control/bluetooth/devices/gamepad/gamepad.dart'; +import 'package:swift_control/bluetooth/devices/bluetooth_device.dart'; +import 'package:swift_control/bluetooth/devices/gamepad/gamepad_device.dart'; import 'package:swift_control/main.dart'; import 'package:swift_control/utils/actions/android.dart'; import 'package:swift_control/utils/requirements/android.dart'; @@ -17,9 +18,13 @@ import 'messages/notification.dart'; class Connection { final devices = []; + + List get bluetoothDevices => devices.whereType().toList(); + List get gamepadDevices => devices.whereType().toList(); + var _androidNotificationsSetup = false; - final _connectionQueue = []; + final _connectionQueue = []; var _handlingConnectionQueue = false; final Map> _streamSubscriptions = {}; @@ -51,10 +56,11 @@ class Connection { print('Scan result: ${result.name} - ${result.deviceId}'); } - final scanResult = BaseDevice.fromScanResult(result); + final scanResult = BluetoothDevice.fromScanResult(result); if (scanResult != null) { _actionStreams.add(LogNotification('Found new device: ${scanResult.runtimeType}')); + _connectionQueue.addAll([scanResult]); _addDevices([scanResult]); } else { final manufacturerData = result.manufacturerDataList; @@ -69,7 +75,7 @@ class Connection { }; UniversalBle.onValueChange = (deviceId, characteristicUuid, value) { - final device = devices.firstOrNullWhere((e) => e.device.deviceId == deviceId); + final device = bluetoothDevices.firstOrNullWhere((e) => e.device.deviceId == deviceId); if (device == null) { _actionStreams.add(LogNotification('Device not found: $deviceId')); UniversalBle.disconnect(deviceId); @@ -80,27 +86,17 @@ class Connection { }; Gamepads.list().then((list) { - print('Connected gamepads: ${list.length}'); if (list.isNotEmpty) { final pads = list .map( - (pad) => Gamepad( - BleDevice( - deviceId: pad.id, - name: pad.name, - rssi: 0, - services: [], - ), - ), + (pad) => GamepadDevice(pad.name, id: pad.id), ) .toList(); _addDevices(pads); Gamepads.events.listen((event) { _actionStreams.add(LogNotification('Gamepad event: $event')); - final device = devices.firstOrNullWhere((e) => e.device.deviceId == event.gamepadId); - if (device is Gamepad) { - device.processGamepadEvent(event); - } + final device = gamepadDevices.firstOrNullWhere((e) => e.id == event.gamepadId); + device?.processGamepadEvent(event); }); } }); @@ -117,10 +113,11 @@ class Connection { // does not work on web, may not work on Windows if (!kIsWeb && !Platform.isWindows) { UniversalBle.getSystemDevices( - withServices: BaseDevice.servicesToScan, + withServices: BluetoothDevice.servicesToScan, ).then((devices) async { - final baseDevices = devices.mapNotNull(BaseDevice.fromScanResult).toList(); + final baseDevices = devices.mapNotNull(BluetoothDevice.fromScanResult).toList(); if (baseDevices.isNotEmpty) { + _connectionQueue.addAll(baseDevices); _addDevices(baseDevices); } }); @@ -129,15 +126,13 @@ class Connection { await UniversalBle.startScan( // allow all to enable Wahoo Kickr Bike Shift detection //scanFilter: ScanFilter(withServices: BaseDevice.servicesToScan), - platformConfig: PlatformConfig(web: WebOptions(optionalServices: BaseDevice.servicesToScan)), + platformConfig: PlatformConfig(web: WebOptions(optionalServices: BluetoothDevice.servicesToScan)), ); } void _addDevices(List dev) { final newDevices = dev.where((device) => !devices.contains(device)).toList(); devices.addAll(newDevices); - - _connectionQueue.addAll(newDevices); _handleConnectionQueue(); hasDevices.value = devices.isNotEmpty; @@ -154,30 +149,28 @@ class Connection { if (_connectionQueue.isNotEmpty && !_handlingConnectionQueue) { _handlingConnectionQueue = true; final device = _connectionQueue.removeAt(0); - if (device is! Gamepad) { - _actionStreams.add(LogNotification('Connecting to: ${device.device.name ?? device.runtimeType}')); - _connect(device) - .then((_) { - _handlingConnectionQueue = false; - _actionStreams.add(LogNotification('Connection finished: ${device.device.name ?? device.runtimeType}')); - if (_connectionQueue.isNotEmpty) { - _handleConnectionQueue(); - } - }) - .catchError((e) { - _handlingConnectionQueue = false; - _actionStreams.add( - LogNotification('Connection failed: ${device.device.name ?? device.runtimeType} - $e'), - ); - if (_connectionQueue.isNotEmpty) { - _handleConnectionQueue(); - } - }); - } + _actionStreams.add(LogNotification('Connecting to: ${device.device.name ?? device.runtimeType}')); + _connect(device) + .then((_) { + _handlingConnectionQueue = false; + _actionStreams.add(LogNotification('Connection finished: ${device.device.name ?? device.runtimeType}')); + if (_connectionQueue.isNotEmpty) { + _handleConnectionQueue(); + } + }) + .catchError((e) { + _handlingConnectionQueue = false; + _actionStreams.add( + LogNotification('Connection failed: ${device.device.name ?? device.runtimeType} - $e'), + ); + if (_connectionQueue.isNotEmpty) { + _handleConnectionQueue(); + } + }); } } - Future _connect(BaseDevice bleDevice) async { + Future _connect(BluetoothDevice bleDevice) async { try { final actionSubscription = bleDevice.actionStream.listen((data) { _actionStreams.add(data); @@ -219,7 +212,7 @@ class Connection { } UniversalBle.stopScan(); isScanning.value = false; - for (var device in devices) { + for (var device in bluetoothDevices) { _streamSubscriptions[device]?.cancel(); _streamSubscriptions.remove(device); _connectionSubscriptions[device]?.cancel(); diff --git a/lib/bluetooth/devices/base_device.dart b/lib/bluetooth/devices/base_device.dart index 2b0d639..c414004 100644 --- a/lib/bluetooth/devices/base_device.dart +++ b/lib/bluetooth/devices/base_device.dart @@ -1,152 +1,41 @@ import 'dart:async'; -import 'package:dartx/dartx.dart'; -import 'package:flutter/foundation.dart'; -import 'package:swift_control/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart'; -import 'package:swift_control/bluetooth/devices/zwift/constants.dart'; -import 'package:swift_control/bluetooth/devices/zwift/zwift_click.dart'; -import 'package:swift_control/bluetooth/devices/zwift/zwift_clickv2.dart'; -import 'package:swift_control/bluetooth/devices/zwift/zwift_play.dart'; -import 'package:swift_control/bluetooth/devices/zwift/zwift_ride.dart'; +import 'package:flutter/material.dart'; import 'package:swift_control/main.dart'; import 'package:swift_control/utils/actions/desktop.dart'; -import 'package:universal_ble/universal_ble.dart'; import '../../utils/keymap/buttons.dart'; import '../messages/notification.dart'; -import 'elite/elite_square.dart'; -import 'elite/elite_sterzo.dart'; abstract class BaseDevice { - final BleDevice scanResult; + final String name; final bool isBeta; final List availableButtons; - BaseDevice(this.scanResult, {required this.availableButtons, this.isBeta = false}); + BaseDevice(this.name, {required this.availableButtons, this.isBeta = false}); bool isConnected = false; - int? batteryLevel; - String? firmwareVersion; Timer? _longPressTimer; Set _previouslyPressedButtons = {}; - static List servicesToScan = [ - ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID, - ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID, - SquareConstants.SERVICE_UUID, - WahooKickrBikeShiftConstants.SERVICE_UUID, - SterzoConstants.SERVICE_UUID, - ]; - - static BaseDevice? fromScanResult(BleDevice scanResult) { - // Use the name first as the "System Devices" and Web (android sometimes Windows) don't have manufacturer data - BaseDevice? device; - if (kIsWeb) { - device = switch (scanResult.name) { - 'Zwift Ride' => ZwiftRide(scanResult), - 'Zwift Play' => ZwiftPlay(scanResult), - 'Zwift Click' => ZwiftClickV2(scanResult), - 'SQUARE' => EliteSquare(scanResult), - _ => null, - }; - - if (scanResult.name != null && scanResult.name!.toUpperCase().startsWith('KICKR BIKE SHIFT')) { - device = WahooKickrBikeShift(scanResult); - } - - if (scanResult.name != null && scanResult.name!.toUpperCase().startsWith('STERZO')) { - device = EliteSterzo(scanResult); - } - } else { - device = switch (scanResult.name) { - //'Zwift Ride' => ZwiftRide(scanResult), special case for Zwift Ride: we must only connect to the left controller - // https://www.makinolo.com/blog/2024/07/26/zwift-ride-protocol/ - 'Zwift Play' => ZwiftPlay(scanResult), - //'Zwift Click' => ZwiftClick(scanResult), special case for Zwift Click v2: we must only connect to the left controller - _ => null, - }; - - if (scanResult.name != null) { - if (scanResult.name!.toUpperCase().startsWith('STERZO')) { - device = EliteSterzo(scanResult); - } else if (scanResult.name!.toUpperCase().startsWith('KICKR BIKE SHIFT')) { - return WahooKickrBikeShift(scanResult); - } - } - } - - if (device != null) { - return device; - } else if (scanResult.services.containsAny([ - ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID, - ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID, - ])) { - // otherwise use the manufacturer data to identify the device - final manufacturerData = scanResult.manufacturerDataList; - final data = manufacturerData - .firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID) - ?.payload; - - if (data == null || data.isEmpty) { - return null; - } - - final type = ZwiftDeviceType.fromManufacturerData(data.first); - return switch (type) { - ZwiftDeviceType.click => ZwiftClick(scanResult), - ZwiftDeviceType.playRight => ZwiftPlay(scanResult), - ZwiftDeviceType.playLeft => ZwiftPlay(scanResult), - ZwiftDeviceType.rideLeft => ZwiftRide(scanResult), - //DeviceType.rideRight => ZwiftRide(scanResult), // see comment above - ZwiftDeviceType.clickV2Left => ZwiftClickV2(scanResult), - //DeviceType.clickV2Right => ZwiftClickV2(scanResult), // see comment above - _ => null, - }; - } else if (scanResult.services.contains(SquareConstants.SERVICE_UUID)) { - return EliteSquare(scanResult); - } else if (scanResult.services.contains(SterzoConstants.SERVICE_UUID)) { - return EliteSterzo(scanResult); - } else { - return null; - } - } - @override bool operator ==(Object other) => - identical(this, other) || - other is BaseDevice && runtimeType == other.runtimeType && scanResult == other.scanResult; + identical(this, other) || other is BaseDevice && runtimeType == other.runtimeType && name == other.name; @override - int get hashCode => scanResult.hashCode; + int get hashCode => name.hashCode; @override String toString() { return runtimeType.toString(); } - BleDevice get device => scanResult!; final StreamController actionStreamInternal = StreamController.broadcast(); Stream get actionStream => actionStreamInternal.stream; - Future connect() async { - actionStream.listen((message) { - print("Received message: $message"); - }); - - await UniversalBle.connect(device.deviceId); - - if (!kIsWeb) { - await UniversalBle.requestMtu(device.deviceId, 517); - } - - final services = await UniversalBle.discoverServices(device.deviceId); - await handleServices(services); - } - - Future handleServices(List services); - Future processCharacteristic(String characteristic, Uint8List bytes); + Future connect(); Future handleButtonsClicked(List? buttonsClicked) async { if (buttonsClicked == null) { @@ -232,7 +121,8 @@ abstract class BaseDevice { await (actionHandler as DesktopActions).releaseAllHeldKeys(_previouslyPressedButtons.toList()); } _previouslyPressedButtons.clear(); - await UniversalBle.disconnect(device.deviceId); isConnected = false; } + + Widget showInformation(BuildContext context); } diff --git a/lib/bluetooth/devices/bluetooth_device.dart b/lib/bluetooth/devices/bluetooth_device.dart new file mode 100644 index 0000000..b4c26a7 --- /dev/null +++ b/lib/bluetooth/devices/bluetooth_device.dart @@ -0,0 +1,185 @@ +import 'dart:async'; + +import 'package:dartx/dartx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:swift_control/bluetooth/devices/base_device.dart'; +import 'package:swift_control/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart'; +import 'package:swift_control/bluetooth/devices/zwift/constants.dart'; +import 'package:swift_control/bluetooth/devices/zwift/zwift_click.dart'; +import 'package:swift_control/bluetooth/devices/zwift/zwift_clickv2.dart'; +import 'package:swift_control/bluetooth/devices/zwift/zwift_device.dart'; +import 'package:swift_control/bluetooth/devices/zwift/zwift_play.dart'; +import 'package:swift_control/bluetooth/devices/zwift/zwift_ride.dart'; +import 'package:swift_control/pages/device.dart'; +import 'package:swift_control/widgets/beta_pill.dart'; +import 'package:universal_ble/universal_ble.dart'; + +import 'elite/elite_square.dart'; +import 'elite/elite_sterzo.dart'; + +abstract class BluetoothDevice extends BaseDevice { + final BleDevice scanResult; + + BluetoothDevice(this.scanResult, {required super.availableButtons, super.isBeta = false}) + : super(scanResult.name ?? 'Unknown Device'); + + int? batteryLevel; + String? firmwareVersion; + + static List servicesToScan = [ + ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID, + ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID, + SquareConstants.SERVICE_UUID, + WahooKickrBikeShiftConstants.SERVICE_UUID, + SterzoConstants.SERVICE_UUID, + ]; + + static BluetoothDevice? fromScanResult(BleDevice scanResult) { + // Use the name first as the "System Devices" and Web (android sometimes Windows) don't have manufacturer data + BluetoothDevice? device; + if (kIsWeb) { + device = switch (scanResult.name) { + 'Zwift Ride' => ZwiftRide(scanResult), + 'Zwift Play' => ZwiftPlay(scanResult), + 'Zwift Click' => ZwiftClickV2(scanResult), + 'SQUARE' => EliteSquare(scanResult), + _ => null, + }; + + if (scanResult.name != null && scanResult.name!.toUpperCase().startsWith('KICKR BIKE SHIFT')) { + device = WahooKickrBikeShift(scanResult); + } + + if (scanResult.name != null && scanResult.name!.toUpperCase().startsWith('STERZO')) { + device = EliteSterzo(scanResult); + } + } else { + device = switch (scanResult.name) { + //'Zwift Ride' => ZwiftRide(scanResult), special case for Zwift Ride: we must only connect to the left controller + // https://www.makinolo.com/blog/2024/07/26/zwift-ride-protocol/ + 'Zwift Play' => ZwiftPlay(scanResult), + //'Zwift Click' => ZwiftClick(scanResult), special case for Zwift Click v2: we must only connect to the left controller + _ => null, + }; + + if (scanResult.name != null) { + if (scanResult.name!.toUpperCase().startsWith('STERZO')) { + device = EliteSterzo(scanResult); + } else if (scanResult.name!.toUpperCase().startsWith('KICKR BIKE SHIFT')) { + return WahooKickrBikeShift(scanResult); + } + } + } + + if (device != null) { + return device; + } else if (scanResult.services.containsAny([ + ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID, + ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID, + ])) { + // otherwise use the manufacturer data to identify the device + final manufacturerData = scanResult.manufacturerDataList; + final data = manufacturerData + .firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID) + ?.payload; + + if (data == null || data.isEmpty) { + return null; + } + + final type = ZwiftDeviceType.fromManufacturerData(data.first); + return switch (type) { + ZwiftDeviceType.click => ZwiftClick(scanResult), + ZwiftDeviceType.playRight => ZwiftPlay(scanResult), + ZwiftDeviceType.playLeft => ZwiftPlay(scanResult), + ZwiftDeviceType.rideLeft => ZwiftRide(scanResult), + //DeviceType.rideRight => ZwiftRide(scanResult), // see comment above + ZwiftDeviceType.clickV2Left => ZwiftClickV2(scanResult), + //DeviceType.clickV2Right => ZwiftClickV2(scanResult), // see comment above + _ => null, + }; + } else if (scanResult.services.contains(SquareConstants.SERVICE_UUID)) { + return EliteSquare(scanResult); + } else if (scanResult.services.contains(SterzoConstants.SERVICE_UUID)) { + return EliteSterzo(scanResult); + } else { + return null; + } + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BluetoothDevice && runtimeType == other.runtimeType && scanResult == other.scanResult; + + @override + int get hashCode => scanResult.hashCode; + + @override + String toString() { + return runtimeType.toString(); + } + + BleDevice get device => scanResult; + + @override + Future connect() async { + actionStream.listen((message) { + print("Received message: $message"); + }); + + await UniversalBle.connect(device.deviceId); + + if (!kIsWeb) { + await UniversalBle.requestMtu(device.deviceId, 517); + } + + final services = await UniversalBle.discoverServices(device.deviceId); + await handleServices(services); + } + + Future handleServices(List services); + Future processCharacteristic(String characteristic, Uint8List bytes); + + @override + Future disconnect() async { + await UniversalBle.disconnect(device.deviceId); + super.disconnect(); + } + + @override + Widget showInformation(BuildContext context) { + return Row( + children: [ + Text( + device.name?.screenshot ?? device.runtimeType.toString(), + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (isBeta) BetaPill(), + if (batteryLevel != null) ...[ + Icon(switch (batteryLevel!) { + >= 80 => Icons.battery_full, + >= 60 => Icons.battery_6_bar, + >= 50 => Icons.battery_5_bar, + >= 25 => Icons.battery_4_bar, + >= 10 => Icons.battery_2_bar, + _ => Icons.battery_alert, + }), + Text('$batteryLevel%'), + if (firmwareVersion != null) Text(' - Firmware: $firmwareVersion'), + if (firmwareVersion != null && + this is ZwiftDevice && + firmwareVersion != (this as ZwiftDevice).latestFirmwareVersion) ...[ + SizedBox(width: 8), + Icon(Icons.warning, color: Theme.of(context).colorScheme.error), + Text( + ' (latest: ${(this as ZwiftDevice).latestFirmwareVersion})', + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ], + ], + ], + ); + } +} diff --git a/lib/bluetooth/devices/elite/elite_square.dart b/lib/bluetooth/devices/elite/elite_square.dart index a4746cb..b0065cf 100644 --- a/lib/bluetooth/devices/elite/elite_square.dart +++ b/lib/bluetooth/devices/elite/elite_square.dart @@ -1,13 +1,13 @@ import 'dart:typed_data'; import 'package:dartx/dartx.dart'; -import 'package:swift_control/bluetooth/devices/base_device.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; import 'package:universal_ble/universal_ble.dart'; import '../../messages/notification.dart'; +import '../bluetooth_device.dart'; -class EliteSquare extends BaseDevice { +class EliteSquare extends BluetoothDevice { EliteSquare(super.scanResult) : super( availableButtons: SquareConstants.BUTTON_MAPPING.values.toList(), diff --git a/lib/bluetooth/devices/elite/elite_sterzo.dart b/lib/bluetooth/devices/elite/elite_sterzo.dart index dc2c9a8..f0f4b76 100644 --- a/lib/bluetooth/devices/elite/elite_sterzo.dart +++ b/lib/bluetooth/devices/elite/elite_sterzo.dart @@ -4,13 +4,13 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:swift_control/bluetooth/devices/base_device.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; import 'package:universal_ble/universal_ble.dart'; import '../../messages/notification.dart'; +import '../bluetooth_device.dart'; -class EliteSterzo extends BaseDevice { +class EliteSterzo extends BluetoothDevice { EliteSterzo(super.scanResult) : super( availableButtons: [ diff --git a/lib/bluetooth/devices/gamepad/gamepad.dart b/lib/bluetooth/devices/gamepad/gamepad_device.dart similarity index 52% rename from lib/bluetooth/devices/gamepad/gamepad.dart rename to lib/bluetooth/devices/gamepad/gamepad_device.dart index 9e00984..4d5afd4 100644 --- a/lib/bluetooth/devices/gamepad/gamepad.dart +++ b/lib/bluetooth/devices/gamepad/gamepad_device.dart @@ -1,27 +1,15 @@ -import 'dart:typed_data'; - +import 'package:flutter/material.dart'; import 'package:gamepads/gamepads.dart'; import 'package:swift_control/bluetooth/devices/base_device.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; -import 'package:universal_ble/src/models/ble_service.dart'; -class Gamepad extends BaseDevice { - Gamepad(super.scanResult) : super(availableButtons: ControllerButton.values.toList(), isBeta: true); +class GamepadDevice extends BaseDevice { + final String id; - @override - Future handleServices(List services) { - // TODO: implement handleServices - throw UnimplementedError(); - } - - @override - Future processCharacteristic(String characteristic, Uint8List bytes) { - // TODO: implement processCharacteristic - throw UnimplementedError(); - } + GamepadDevice(super.name, {required this.id}) + : super(availableButtons: ControllerButton.values.toList(), isBeta: true); void processGamepadEvent(GamepadEvent event) { - print('KEy: ${event.key}'); switch (event.key) { case 'AXIS_HAT_X': handleButtonsClicked([ControllerButton.shiftUpLeft]); @@ -32,4 +20,14 @@ class Gamepad extends BaseDevice { } handleButtonsClicked([]); } + + @override + Future connect() async {} + + @override + Widget showInformation(BuildContext context) { + return Row( + children: [], + ); + } } diff --git a/lib/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart b/lib/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart index 75a5b3c..92c482c 100644 --- a/lib/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart +++ b/lib/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart @@ -1,11 +1,12 @@ import 'dart:collection'; import 'dart:typed_data'; -import 'package:swift_control/bluetooth/devices/base_device.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; import 'package:universal_ble/universal_ble.dart'; -class WahooKickrBikeShift extends BaseDevice { +import '../bluetooth_device.dart'; + +class WahooKickrBikeShift extends BluetoothDevice { WahooKickrBikeShift(super.scanResult) : super( availableButtons: WahooKickrBikeShiftConstants.prefixToButton.values.toList(), diff --git a/lib/bluetooth/devices/zwift/zwift_device.dart b/lib/bluetooth/devices/zwift/zwift_device.dart index 4b5622a..b52fa5a 100644 --- a/lib/bluetooth/devices/zwift/zwift_device.dart +++ b/lib/bluetooth/devices/zwift/zwift_device.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:dartx/dartx.dart'; import 'package:flutter/foundation.dart'; import 'package:swift_control/bluetooth/ble.dart'; -import 'package:swift_control/bluetooth/devices/base_device.dart'; +import 'package:swift_control/bluetooth/devices/bluetooth_device.dart'; import 'package:swift_control/bluetooth/devices/zwift/constants.dart'; import 'package:swift_control/bluetooth/messages/notification.dart'; import 'package:swift_control/main.dart'; @@ -11,7 +11,7 @@ import 'package:swift_control/utils/keymap/buttons.dart'; import 'package:swift_control/utils/single_line_exception.dart'; import 'package:universal_ble/universal_ble.dart'; -abstract class ZwiftDevice extends BaseDevice { +abstract class ZwiftDevice extends BluetoothDevice { ZwiftDevice(super.scanResult, {required super.availableButtons, super.isBeta}); BleCharacteristic? syncRxCharacteristic; diff --git a/lib/pages/device.dart b/lib/pages/device.dart index 3fc3b20..ce7f0fe 100644 --- a/lib/pages/device.dart +++ b/lib/pages/device.dart @@ -6,14 +6,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:swift_control/bluetooth/devices/zwift/protocol/zp.pbenum.dart'; import 'package:swift_control/bluetooth/devices/zwift/zwift_clickv2.dart'; -import 'package:swift_control/bluetooth/devices/zwift/zwift_device.dart'; import 'package:swift_control/main.dart'; import 'package:swift_control/pages/markdown.dart'; import 'package:swift_control/pages/touch_area.dart'; import 'package:swift_control/utils/actions/desktop.dart'; import 'package:swift_control/utils/actions/link.dart'; import 'package:swift_control/utils/keymap/manager.dart'; -import 'package:swift_control/widgets/beta_pill.dart'; import 'package:swift_control/widgets/ingameactions_customizer.dart'; import 'package:swift_control/widgets/keymap_explanation.dart'; import 'package:swift_control/widgets/loading_widget.dart'; @@ -121,7 +119,7 @@ class _DevicePageState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - final canVibrate = connection.devices.any( + final canVibrate = connection.bluetoothDevices.any( (device) => (device.device.name == 'Zwift Ride' || device.device.name == 'Zwift Play') && device.isConnected, ); @@ -174,37 +172,7 @@ class _DevicePageState extends State with WidgetsBindingObserver { children: [ if (connection.devices.isEmpty) Text('No devices connected. Searching...'), ...connection.devices.map( - (device) => Row( - children: [ - Text( - device.device.name?.screenshot ?? device.runtimeType.toString(), - style: TextStyle(fontWeight: FontWeight.bold), - ), - if (device.isBeta) BetaPill(), - if (device.batteryLevel != null) ...[ - Icon(switch (device.batteryLevel!) { - >= 80 => Icons.battery_full, - >= 60 => Icons.battery_6_bar, - >= 50 => Icons.battery_5_bar, - >= 25 => Icons.battery_4_bar, - >= 10 => Icons.battery_2_bar, - _ => Icons.battery_alert, - }), - Text('${device.batteryLevel}%'), - if (device.firmwareVersion != null) Text(' - Firmware: ${device.firmwareVersion}'), - if (device.firmwareVersion != null && - device is ZwiftDevice && - device.firmwareVersion != device.latestFirmwareVersion) ...[ - SizedBox(width: 8), - Icon(Icons.warning, color: Theme.of(context).colorScheme.error), - Text( - ' (latest: ${device.latestFirmwareVersion})', - style: TextStyle(color: Theme.of(context).colorScheme.error), - ), - ], - ], - ], - ), + (device) => device.showInformation(context), ), if (actionHandler is RemoteActions) Row( diff --git a/lib/widgets/ingameactions_customizer.dart b/lib/widgets/ingameactions_customizer.dart index 83bda05..fb56141 100644 --- a/lib/widgets/ingameactions_customizer.dart +++ b/lib/widgets/ingameactions_customizer.dart @@ -39,7 +39,7 @@ class _InGameActionsCustomizerState extends State { Padding( padding: const EdgeInsets.all(6), child: Text( - 'Button on your ${connectedDevice?.device.name?.screenshot ?? connectedDevice?.runtimeType ?? 'device'}', + 'Button on your ${connectedDevice?.name.screenshot ?? connectedDevice?.runtimeType ?? 'device'}', style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), ), ), diff --git a/lib/widgets/keymap_explanation.dart b/lib/widgets/keymap_explanation.dart index 4535edf..5de0f02 100644 --- a/lib/widgets/keymap_explanation.dart +++ b/lib/widgets/keymap_explanation.dart @@ -56,7 +56,7 @@ class KeymapExplanation extends StatelessWidget { Padding( padding: const EdgeInsets.all(6), child: Text( - 'Button on your ${connectedDevice?.device.name?.screenshot ?? connectedDevice?.runtimeType ?? 'device'}', + 'Button on your ${connectedDevice?.name.screenshot ?? connectedDevice?.runtimeType ?? 'device'}', style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), ), ),