mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
zwift click emulation #1
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
class BleUuid {
|
||||
static final DEVICE_INFORMATION_SERVICE_UUID = "0000180a-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION = "00002a26-0000-1000-8000-00805f9b34fb"
|
||||
.toLowerCase();
|
||||
static const DEVICE_INFORMATION_SERVICE_UUID = "0000180a-0000-1000-8000-00805f9b34fb";
|
||||
static const DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION = "00002a26-0000-1000-8000-00805f9b34fb";
|
||||
|
||||
static final DEVICE_BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final DEVICE_INFORMATION_CHARACTERISTIC_BATTERY_LEVEL = "00002a19-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static const DEVICE_BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
|
||||
static const DEVICE_INFORMATION_CHARACTERISTIC_BATTERY_LEVEL = "00002A19-0000-1000-8000-00805F9B34FB";
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
if (device != null) {
|
||||
return device;
|
||||
} else if (scanResult.services.containsAny([
|
||||
ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID.toLowerCase(),
|
||||
ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID.toLowerCase(),
|
||||
])) {
|
||||
// otherwise use the manufacturer data to identify the device
|
||||
final manufacturerData = scanResult.manufacturerDataList;
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
|
||||
class ZwiftConstants {
|
||||
static final ZWIFT_CUSTOM_SERVICE_UUID = "00000001-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_RIDE_CUSTOM_SERVICE_UUID = "0000fc82-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final ZWIFT_ASYNC_CHARACTERISTIC_UUID = "00000002-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_SYNC_RX_CHARACTERISTIC_UUID = "00000003-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_SYNC_TX_CHARACTERISTIC_UUID = "00000004-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static const ZWIFT_CUSTOM_SERVICE_UUID = "00000001-19CA-4651-86E5-FA29DCDD09D1";
|
||||
static const ZWIFT_RIDE_CUSTOM_SERVICE_UUID = "0000fc82-0000-1000-8000-00805f9b34fb";
|
||||
static const ZWIFT_ASYNC_CHARACTERISTIC_UUID = "00000002-19CA-4651-86E5-FA29DCDD09D1";
|
||||
static const ZWIFT_SYNC_RX_CHARACTERISTIC_UUID = "00000003-19CA-4651-86E5-FA29DCDD09D1";
|
||||
static const ZWIFT_SYNC_TX_CHARACTERISTIC_UUID = "00000004-19CA-4651-86E5-FA29DCDD09D1";
|
||||
|
||||
static const ZWIFT_MANUFACTURER_ID = 2378; // Zwift, Inc => 0x094A
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
|
||||
@override
|
||||
Future<void> handleServices(List<BleService> services) async {
|
||||
final customService = services.firstOrNullWhere((service) => service.uuid == customServiceId);
|
||||
final customService = services.firstOrNullWhere((service) => service.uuid == customServiceId.toLowerCase());
|
||||
|
||||
if (customService == null) {
|
||||
throw Exception(
|
||||
@@ -33,10 +33,10 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
}
|
||||
|
||||
final deviceInformationService = services.firstOrNullWhere(
|
||||
(service) => service.uuid == BleUuid.DEVICE_INFORMATION_SERVICE_UUID,
|
||||
(service) => service.uuid == BleUuid.DEVICE_INFORMATION_SERVICE_UUID.toLowerCase(),
|
||||
);
|
||||
final firmwareCharacteristic = deviceInformationService?.characteristics.firstOrNullWhere(
|
||||
(c) => c.uuid == BleUuid.DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION,
|
||||
(c) => c.uuid == BleUuid.DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION.toLowerCase(),
|
||||
);
|
||||
if (firmwareCharacteristic != null) {
|
||||
final firmwareData = await UniversalBle.read(
|
||||
@@ -56,13 +56,13 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
}
|
||||
|
||||
final asyncCharacteristic = customService.characteristics.firstOrNullWhere(
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID,
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID.toLowerCase(),
|
||||
);
|
||||
final syncTxCharacteristic = customService.characteristics.firstOrNullWhere(
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID,
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID.toLowerCase(),
|
||||
);
|
||||
syncRxCharacteristic = customService.characteristics.firstOrNullWhere(
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID,
|
||||
(characteristic) => characteristic.uuid == ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID.toLowerCase(),
|
||||
);
|
||||
|
||||
if (asyncCharacteristic == null || syncTxCharacteristic == null || syncRxCharacteristic == null) {
|
||||
@@ -87,9 +87,9 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
|
||||
@override
|
||||
Future<void> processCharacteristic(String characteristic, Uint8List bytes) async {
|
||||
if (kDebugMode && false) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
"${DateTime.now().toString().split(" ").last} Received data on $characteristic: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}",
|
||||
"${DateTime.now().toString().split(" ").last} Received data on $characteristic:\n${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}",
|
||||
);
|
||||
}
|
||||
if (bytes.isEmpty) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/requirements/android.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
import 'package:swift_control/utils/requirements/remote.dart';
|
||||
import 'package:swift_control/utils/requirements/zwift.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
abstract class PlatformRequirement {
|
||||
@@ -83,7 +84,7 @@ Future<List<PlatformRequirement>> getRequirements(ConnectionType connectionType)
|
||||
],
|
||||
switch (connectionType) {
|
||||
ConnectionType.local => AccessibilityRequirement(),
|
||||
ConnectionType.remote => RemoteRequirement(),
|
||||
ConnectionType.remote => ZwiftRequirement(),
|
||||
ConnectionType.unknown => PlaceholderRequirement(),
|
||||
},
|
||||
];
|
||||
|
||||
409
lib/utils/requirements/zwift.dart
Normal file
409
lib/utils/requirements/zwift.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide ConnectionState;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:swift_control/bluetooth/ble.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/pages/device.dart';
|
||||
import 'package:swift_control/utils/actions/remote.dart';
|
||||
import 'package:swift_control/utils/keymap/apps/my_whoosh.dart';
|
||||
import 'package:swift_control/utils/requirements/multi.dart';
|
||||
import 'package:swift_control/utils/requirements/platform.dart';
|
||||
import 'package:swift_control/widgets/small_progress_indicator.dart';
|
||||
|
||||
import '../../pages/markdown.dart';
|
||||
|
||||
final peripheralManager = PeripheralManager();
|
||||
bool _isAdvertising = false;
|
||||
bool _isLoading = false;
|
||||
bool _isServiceAdded = false;
|
||||
bool _isSubscribedToEvents = false;
|
||||
|
||||
class ZwiftRequirement extends PlatformRequirement {
|
||||
ZwiftRequirement()
|
||||
: super(
|
||||
'Connect to your target device',
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
@override
|
||||
Widget? buildDescription() {
|
||||
return settings.getLastTarget() == null
|
||||
? null
|
||||
: Text(
|
||||
switch (settings.getLastTarget()) {
|
||||
Target.iPad =>
|
||||
'On your iPad go to Settings > Accessibility > Touch > AssistiveTouch > Pointer Devices > Devices and pair your device. Make sure AssistiveTouch is enabled.',
|
||||
_ =>
|
||||
'On your ${settings.getLastTarget()?.title} go into Bluetooth settings and look for SwiftControl or your machines name. Pairing is required to use the remote feature.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reconnect() async {
|
||||
await peripheralManager.stopAdvertising();
|
||||
await peripheralManager.removeAllServices();
|
||||
_isServiceAdded = false;
|
||||
_isAdvertising = false;
|
||||
(actionHandler as RemoteActions).setConnectedCentral(null, null);
|
||||
startAdvertising(() {});
|
||||
}
|
||||
|
||||
Future<void> startAdvertising(VoidCallback onUpdate) async {
|
||||
peripheralManager.stateChanged.forEach((state) {
|
||||
print('Peripheral manager state: ${state.state}');
|
||||
});
|
||||
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
if (Platform.isAndroid) {
|
||||
peripheralManager.connectionStateChanged.forEach((state) {
|
||||
print('Peripheral connection state: ${state.state} of ${state.central.uuid}');
|
||||
if (state.state == ConnectionState.connected) {
|
||||
/*(actionHandler as RemoteActions).setConnectedCentral(state.central, inputReport);
|
||||
//peripheralManager.stopAdvertising();
|
||||
onUpdate();*/
|
||||
} else if (state.state == ConnectionState.disconnected) {
|
||||
(actionHandler as RemoteActions).setConnectedCentral(null, null);
|
||||
onUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final status = await Permission.bluetoothAdvertise.request();
|
||||
if (!status.isGranted) {
|
||||
print('Bluetooth advertise permission not granted');
|
||||
_isAdvertising = false;
|
||||
onUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (peripheralManager.state != BluetoothLowEnergyState.poweredOn) {
|
||||
print('Waiting for peripheral manager to be powered on...');
|
||||
if (settings.getLastTarget() == Target.thisDevice) {
|
||||
return;
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
|
||||
final syncTxCharacteristic = GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString(ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.read,
|
||||
GATTCharacteristicProperty.indicate,
|
||||
],
|
||||
permissions: [
|
||||
GATTCharacteristicPermission.read,
|
||||
],
|
||||
);
|
||||
|
||||
final asyncCharacteristic = GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString(ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.notify,
|
||||
],
|
||||
permissions: [],
|
||||
);
|
||||
|
||||
if (!_isServiceAdded) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
|
||||
if (!_isSubscribedToEvents) {
|
||||
_isSubscribedToEvents = true;
|
||||
peripheralManager.characteristicReadRequested.forEach((eventArgs) async {
|
||||
print('Read request for characteristic: ${eventArgs.characteristic.uuid}');
|
||||
|
||||
switch (eventArgs.characteristic.uuid.toString().toUpperCase()) {
|
||||
case ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID:
|
||||
print('Handling read request for SYNC TX characteristic');
|
||||
break;
|
||||
case BleUuid.DEVICE_INFORMATION_CHARACTERISTIC_BATTERY_LEVEL:
|
||||
await peripheralManager.respondReadRequestWithValue(
|
||||
eventArgs.request,
|
||||
value: Uint8List.fromList([100]),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
print('Unhandled read request for characteristic: ${eventArgs.characteristic.uuid}');
|
||||
}
|
||||
|
||||
final central = eventArgs.central;
|
||||
final characteristic = eventArgs.characteristic;
|
||||
final request = eventArgs.request;
|
||||
final offset = request.offset;
|
||||
final trimmedValue = Uint8List.fromList([]);
|
||||
await peripheralManager.respondReadRequestWithValue(
|
||||
request,
|
||||
value: trimmedValue,
|
||||
);
|
||||
// You can respond to read requests here if needed
|
||||
});
|
||||
|
||||
peripheralManager.characteristicNotifyStateChanged.forEach((char) {
|
||||
print(
|
||||
'Notify state changed for characteristic: ${char.characteristic.uuid}: ${char.state}',
|
||||
);
|
||||
});
|
||||
peripheralManager.characteristicWriteRequested.forEach((eventArgs) async {
|
||||
final central = eventArgs.central;
|
||||
final characteristic = eventArgs.characteristic;
|
||||
final request = eventArgs.request;
|
||||
final offset = request.offset;
|
||||
final value = request.value;
|
||||
print(
|
||||
'Write request for characteristic: ${characteristic.uuid}, value: ${value.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}\n${String.fromCharCodes(value)}',
|
||||
);
|
||||
|
||||
switch (eventArgs.characteristic.uuid.toString().toUpperCase()) {
|
||||
case ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID:
|
||||
print('Handling write request for SYNC RX characteristic');
|
||||
|
||||
if (value.startsWith(ZwiftConstants.RIDE_ON)) {
|
||||
await peripheralManager.notifyCharacteristic(
|
||||
central,
|
||||
syncTxCharacteristic,
|
||||
value: ZwiftConstants.RIDE_ON,
|
||||
);
|
||||
await peripheralManager.notifyCharacteristic(
|
||||
central,
|
||||
asyncCharacteristic,
|
||||
value: Uint8List.fromList([0x19, 0x10, 0x03]),
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print('Unhandled write request for characteristic: ${eventArgs.characteristic.uuid}');
|
||||
}
|
||||
|
||||
await peripheralManager.respondWriteRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
// Device Information
|
||||
await peripheralManager.addService(
|
||||
GATTService(
|
||||
uuid: UUID.fromString('180A'),
|
||||
isPrimary: true,
|
||||
characteristics: [
|
||||
GATTCharacteristic.immutable(
|
||||
uuid: UUID.fromString('2A29'),
|
||||
value: Uint8List.fromList('Zwift Inc'.codeUnits),
|
||||
descriptors: [],
|
||||
),
|
||||
GATTCharacteristic.immutable(
|
||||
uuid: UUID.fromString('2A25'),
|
||||
value: Uint8List.fromList('09-B48123283828FFD82'.codeUnits),
|
||||
descriptors: [],
|
||||
),
|
||||
GATTCharacteristic.immutable(
|
||||
uuid: UUID.fromString('2A27'),
|
||||
value: Uint8List.fromList('A.0'.codeUnits),
|
||||
descriptors: [],
|
||||
),
|
||||
GATTCharacteristic.immutable(
|
||||
uuid: UUID.fromString('2A26'),
|
||||
value: Uint8List.fromList('1.1.0'.codeUnits),
|
||||
descriptors: [],
|
||||
),
|
||||
],
|
||||
includedServices: [],
|
||||
),
|
||||
);
|
||||
|
||||
// Battery Service
|
||||
await peripheralManager.addService(
|
||||
GATTService(
|
||||
uuid: UUID.fromString('180F'),
|
||||
isPrimary: true,
|
||||
characteristics: [
|
||||
GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString('2A19'),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.read,
|
||||
GATTCharacteristicProperty.notify,
|
||||
],
|
||||
permissions: [
|
||||
GATTCharacteristicPermission.read,
|
||||
],
|
||||
),
|
||||
],
|
||||
includedServices: [],
|
||||
),
|
||||
);
|
||||
|
||||
// Unknown Service
|
||||
await peripheralManager.addService(
|
||||
GATTService(
|
||||
uuid: UUID.fromString(ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID),
|
||||
isPrimary: true,
|
||||
characteristics: [
|
||||
asyncCharacteristic,
|
||||
GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString(ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.writeWithoutResponse,
|
||||
],
|
||||
permissions: [],
|
||||
),
|
||||
syncTxCharacteristic,
|
||||
GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString('00000005-19CA-4651-86E5-FA29DCDD09D1'),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.notify,
|
||||
],
|
||||
permissions: [],
|
||||
),
|
||||
GATTCharacteristic.mutable(
|
||||
uuid: UUID.fromString('00000006-19CA-4651-86E5-FA29DCDD09D1'),
|
||||
descriptors: [],
|
||||
properties: [
|
||||
GATTCharacteristicProperty.indicate,
|
||||
GATTCharacteristicProperty.read,
|
||||
GATTCharacteristicProperty.writeWithoutResponse,
|
||||
GATTCharacteristicProperty.write,
|
||||
],
|
||||
permissions: [
|
||||
GATTCharacteristicPermission.read,
|
||||
GATTCharacteristicPermission.write,
|
||||
],
|
||||
),
|
||||
],
|
||||
includedServices: [],
|
||||
),
|
||||
);
|
||||
_isServiceAdded = true;
|
||||
}
|
||||
|
||||
final advertisement = Advertisement(
|
||||
name: 'SwiftControl',
|
||||
serviceUUIDs: [UUID.fromString(ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID)],
|
||||
manufacturerSpecificData: [
|
||||
ManufacturerSpecificData(id: 0x094A, data: Uint8List.fromList([0x09, 0xFD, 0x82])),
|
||||
],
|
||||
);
|
||||
print('Starting advertising with HID service...');
|
||||
|
||||
await peripheralManager.startAdvertising(advertisement);
|
||||
_isAdvertising = true;
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context, VoidCallback onUpdate) {
|
||||
return _PairWidget(onUpdate: onUpdate, requirement: this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getStatus() async {
|
||||
status = (actionHandler as RemoteActions).isConnected || screenshotMode;
|
||||
}
|
||||
}
|
||||
|
||||
class _PairWidget extends StatefulWidget {
|
||||
final ZwiftRequirement requirement;
|
||||
final VoidCallback onUpdate;
|
||||
const _PairWidget({super.key, required this.onUpdate, required this.requirement});
|
||||
|
||||
@override
|
||||
State<_PairWidget> createState() => _PairWidgetState();
|
||||
}
|
||||
|
||||
class _PairWidgetState extends State<_PairWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// after first frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
toggle().catchError((e) {
|
||||
print('Error starting advertising: $e');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 10,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
await toggle();
|
||||
} catch (e) {
|
||||
print('Error toggling advertising: $e');
|
||||
}
|
||||
},
|
||||
child: Text(_isAdvertising ? 'Stop Pairing' : 'Start Pairing'),
|
||||
),
|
||||
if (_isAdvertising || _isLoading) SizedBox(height: 20, width: 20, child: SmallProgressIndicator()),
|
||||
],
|
||||
),
|
||||
if (settings.getTrainerApp() is MyWhoosh)
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (c) => DevicePage(),
|
||||
settings: RouteSettings(name: '/device'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Use MyWhoosh Link only'),
|
||||
Text(
|
||||
'No pairing required, connect directly via MyWhoosh Link.',
|
||||
style: TextStyle(fontSize: 10, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isAdvertising) ...[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')));
|
||||
},
|
||||
child: Text('Check the troubleshooting guide'),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> toggle() async {
|
||||
if (_isAdvertising) {
|
||||
await peripheralManager.stopAdvertising();
|
||||
_isAdvertising = false;
|
||||
(actionHandler as RemoteActions).setConnectedCentral(null, null);
|
||||
widget.onUpdate();
|
||||
_isLoading = false;
|
||||
setState(() {});
|
||||
} else {
|
||||
_isLoading = true;
|
||||
setState(() {});
|
||||
await widget.requirement.startAdvertising(widget.onUpdate);
|
||||
_isLoading = false;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user