From bfffb2856dc1e9e755fa05af14541aba2e3dbc93 Mon Sep 17 00:00:00 2001 From: Jonas Bark Date: Wed, 8 Oct 2025 09:18:53 +0200 Subject: [PATCH] try it on Android #1 --- android/app/src/main/AndroidManifest.xml | 2 ++ lib/pages/requirements.dart | 23 ++++++++++--- lib/utils/actions/android.dart | 2 +- lib/utils/actions/base_actions.dart | 13 +++++++ lib/utils/actions/ios.dart | 15 ++------- lib/utils/requirements/android.dart | 43 ++++++------------------ lib/utils/requirements/ios.dart | 42 ++++++++++++++++++----- lib/utils/requirements/multi.dart | 8 ++--- lib/utils/requirements/platform.dart | 6 ++-- 9 files changed, 88 insertions(+), 66 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cc0b263..9e0e47e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/lib/pages/requirements.dart b/lib/pages/requirements.dart index 3680bbd..6057937 100644 --- a/lib/pages/requirements.dart +++ b/lib/pages/requirements.dart @@ -142,9 +142,22 @@ class _RequirementsPageState extends State with WidgetsBinding _reloadRequirements(); }) : null) ?? - ElevatedButton( - onPressed: req.status ? null : () => _callRequirement(req), - child: Text(req.name), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + if (req.description != null) + Text(req.description!, style: TextStyle(fontSize: 16)), + ElevatedButton( + onPressed: + req.status + ? null + : () => _callRequirement(req, context, () { + _reloadRequirements(); + }), + child: Text(req.name), + ), + ], ), ), state: req.status ? StepState.complete : StepState.indexed, @@ -158,8 +171,8 @@ class _RequirementsPageState extends State with WidgetsBinding ); } - void _callRequirement(PlatformRequirement req) { - req.call().then((_) { + void _callRequirement(PlatformRequirement req, BuildContext context, VoidCallback onUpdate) { + req.call(context, onUpdate).then((_) { _reloadRequirements(); }); } diff --git a/lib/utils/actions/android.dart b/lib/utils/actions/android.dart index 5643068..4c0f1dd 100644 --- a/lib/utils/actions/android.dart +++ b/lib/utils/actions/android.dart @@ -9,7 +9,7 @@ import 'package:swift_control/widgets/keymap_explanation.dart'; import '../keymap/apps/supported_app.dart'; import '../single_line_exception.dart'; -class AndroidActions extends BaseActions { +class AndroidActions extends AccessibilityActions { WindowEvent? windowInfo; @override diff --git a/lib/utils/actions/base_actions.dart b/lib/utils/actions/base_actions.dart index 61a425f..383a9af 100644 --- a/lib/utils/actions/base_actions.dart +++ b/lib/utils/actions/base_actions.dart @@ -1,3 +1,4 @@ +import 'package:bluetooth_low_energy/bluetooth_low_energy.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; import '../keymap/apps/supported_app.dart'; @@ -12,6 +13,18 @@ abstract class BaseActions { Future performAction(ZwiftButton action, {bool isKeyDown = true, bool isKeyUp = false}); } +abstract class AccessibilityActions extends BaseActions { + Central? connectedCentral; + GATTCharacteristic? connectedCharacteristic; + + void setConnectedCentral(Central? central, GATTCharacteristic? gattCharacteristic) { + connectedCentral = central; + connectedCharacteristic = gattCharacteristic; + } + + bool get isConnected => connectedCentral != null; +} + class StubActions extends BaseActions { @override Future performAction(ZwiftButton action, {bool isKeyDown = true, bool isKeyUp = false}) { diff --git a/lib/utils/actions/ios.dart b/lib/utils/actions/ios.dart index d613fe8..72297b2 100644 --- a/lib/utils/actions/ios.dart +++ b/lib/utils/actions/ios.dart @@ -1,4 +1,3 @@ -import 'package:bluetooth_low_energy/bluetooth_low_energy.dart'; import 'package:flutter/foundation.dart'; import 'package:swift_control/utils/actions/base_actions.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; @@ -6,10 +5,7 @@ import 'package:swift_control/widgets/keymap_explanation.dart'; import '../requirements/ios.dart'; -class IosActions extends BaseActions { - Central? _connectedCentral; - GATTCharacteristic? _connectedCharacteristic; - +class IosActions extends AccessibilityActions { @override Future performAction(ZwiftButton action, {bool isKeyDown = true, bool isKeyUp = false}) async { if (supportedApp == null) { @@ -59,13 +55,6 @@ class IosActions extends BaseActions { print('Sending abs mouse report: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0'))}'); } - await peripheralManager.notifyCharacteristic(_connectedCentral!, _connectedCharacteristic!, value: bytes); + await peripheralManager.notifyCharacteristic(connectedCentral!, connectedCharacteristic!, value: bytes); } - - void setConnectedCentral(Central? central, GATTCharacteristic? gattCharacteristic) { - _connectedCentral = central; - _connectedCharacteristic = gattCharacteristic; - } - - bool get isConnected => _connectedCentral != null; } diff --git a/lib/utils/requirements/android.dart b/lib/utils/requirements/android.dart index fa4491c..4aef147 100644 --- a/lib/utils/requirements/android.dart +++ b/lib/utils/requirements/android.dart @@ -8,11 +8,15 @@ import 'package:swift_control/utils/requirements/platform.dart'; import 'package:swift_control/widgets/accessibility_disclosure_dialog.dart'; class AccessibilityRequirement extends PlatformRequirement { - AccessibilityRequirement() : super('Allow Accessibility Service'); + AccessibilityRequirement() + : super( + 'Allow Accessibility Service', + description: 'SwiftControl needs accessibility permission to control your training apps.', + ); @override - Future call() async { - return accessibilityHandler.openPermissions(); + Future call(BuildContext context, VoidCallback onUpdate) async { + _showDisclosureDialog(context, onUpdate); } @override @@ -20,31 +24,6 @@ class AccessibilityRequirement extends PlatformRequirement { status = await accessibilityHandler.hasPermission(); } - @override - Widget? build(BuildContext context, VoidCallback onUpdate) { - if (status) { - return null; // Already granted, no need for disclosure - } - - return Container( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'SwiftControl needs accessibility permission to control your training apps.', - style: TextStyle(fontSize: 16), - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () => _showDisclosureDialog(context, onUpdate), - child: const Text('Show Permission Details'), - ), - ], - ), - ); - } - Future _showDisclosureDialog(BuildContext context, VoidCallback onUpdate) async { return showDialog( context: context, @@ -72,7 +51,7 @@ class BluetoothScanRequirement extends PlatformRequirement { BluetoothScanRequirement() : super('Allow Bluetooth Scan'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { await Permission.bluetoothScan.request(); } @@ -87,7 +66,7 @@ class LocationRequirement extends PlatformRequirement { LocationRequirement() : super('Allow Location so Bluetooth scan works'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { await Permission.locationWhenInUse.request(); } @@ -102,7 +81,7 @@ class BluetoothConnectRequirement extends PlatformRequirement { BluetoothConnectRequirement() : super('Allow Bluetooth Connections'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { await Permission.bluetoothConnect.request(); } @@ -117,7 +96,7 @@ class NotificationRequirement extends PlatformRequirement { NotificationRequirement() : super('Allow adding persistent Notification (keeps app alive)'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation() ?.requestNotificationsPermission(); diff --git a/lib/utils/requirements/ios.dart b/lib/utils/requirements/ios.dart index 409b020..bfdd81b 100644 --- a/lib/utils/requirements/ios.dart +++ b/lib/utils/requirements/ios.dart @@ -1,13 +1,13 @@ import 'dart:io'; -import 'dart:typed_data'; import 'package:bluetooth_low_energy/bluetooth_low_energy.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:swift_control/main.dart'; +import 'package:swift_control/utils/actions/base_actions.dart'; +import 'package:swift_control/utils/actions/ios.dart'; import 'package:swift_control/utils/requirements/platform.dart'; -import '../actions/ios.dart'; - final peripheralManager = PeripheralManager(); bool _isAdvertising = false; bool _isSubscribedToEvents = false; @@ -16,7 +16,7 @@ class ConnectRequirement extends PlatformRequirement { ConnectRequirement() : super('Connect to your other iOS device'); @override - Future call() async {} + Future call(BuildContext context, VoidCallback onUpdate) async {} Future startAdvertising(VoidCallback onUpdate) async { if (Platform.isAndroid) { @@ -152,12 +152,21 @@ class ConnectRequirement extends PlatformRequirement { // You can respond to read requests here if needed }); + if (Platform.isAndroid) { + peripheralManager.connectionStateChanged.forEach((state) { + print('Peripheral connection state: ${state.state}'); + }); + peripheralManager.stateChanged.forEach((state) { + print('Peripheral manager state: ${state.state}'); + }); + } + peripheralManager.characteristicNotifyStateChanged.forEach((char) { if (char.characteristic.uuid == inputReport.uuid) { if (char.state) { - (actionHandler as IosActions).setConnectedCentral(char.central, char.characteristic); + (actionHandler as AccessibilityActions).setConnectedCentral(char.central, char.characteristic); } else { - (actionHandler as IosActions).setConnectedCentral(null, null); + (actionHandler as AccessibilityActions).setConnectedCentral(null, null); } onUpdate(); } @@ -191,6 +200,7 @@ class ConnectRequirement extends PlatformRequirement { /*pm.connectionStateChanged.forEach((state) { print('Peripheral connection state: $state'); });*/ + print('Starting advertising with HID service...'); await peripheralManager.startAdvertising(advertisement); } @@ -210,18 +220,32 @@ class ConnectRequirement extends PlatformRequirement { if (_isAdvertising) { await peripheralManager.stopAdvertising(); _isAdvertising = false; - (actionHandler as IosActions).setConnectedCentral(null, null); + (actionHandler as AccessibilityActions).setConnectedCentral(null, null); onUpdate(); setState(() {}); } else { _isAdvertising = true; setState(() {}); + await startAdvertising(onUpdate); } - await startAdvertising(onUpdate); }, child: Text(_isAdvertising ? 'Stop Pairing' : 'Start Pairing'), ), if (_isAdvertising) SizedBox(height: 20, width: 20, child: CircularProgressIndicator()), + if (kDebugMode) + ElevatedButton( + onPressed: () { + final instance = IosActions(); + instance.setConnectedCentral( + (actionHandler as AccessibilityActions).connectedCentral, + (actionHandler as AccessibilityActions).connectedCharacteristic, + ); + instance.sendAbsMouseReport(0, 90, 90); + instance.sendAbsMouseReport(1, 90, 90); + instance.sendAbsMouseReport(0, 90, 90); + }, + child: Text('Test'), + ), ], ), if (_isAdvertising) @@ -235,6 +259,6 @@ class ConnectRequirement extends PlatformRequirement { @override Future getStatus() async { - status = (actionHandler as IosActions).isConnected; + status = (actionHandler as AccessibilityActions).isConnected && false; } } diff --git a/lib/utils/requirements/multi.dart b/lib/utils/requirements/multi.dart index 7d20a16..1fb96b1 100644 --- a/lib/utils/requirements/multi.dart +++ b/lib/utils/requirements/multi.dart @@ -12,7 +12,7 @@ class KeyboardRequirement extends PlatformRequirement { KeyboardRequirement() : super('Keyboard access'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { await keyPressSimulator.requestAccess(onlyOpenPrefPane: Platform.isMacOS); } @@ -26,7 +26,7 @@ class BluetoothTurnedOn extends PlatformRequirement { BluetoothTurnedOn() : super('Bluetooth turned on'); @override - Future call() async { + Future call(BuildContext context, VoidCallback onUpdate) async { if (!kIsWeb && Platform.isIOS) { // on iOS we cannot programmatically enable Bluetooth, just open settings await peripheralManager.showAppSettings(); @@ -48,7 +48,7 @@ class UnsupportedPlatform extends PlatformRequirement { } @override - Future call() async {} + Future call(BuildContext context, VoidCallback onUpdate) async {} @override Future getStatus() async {} @@ -60,7 +60,7 @@ class BluetoothScanning extends PlatformRequirement { } @override - Future call() async {} + Future call(BuildContext context, VoidCallback onUpdate) async {} @override Future getStatus() async {} diff --git a/lib/utils/requirements/platform.dart b/lib/utils/requirements/platform.dart index 3290582..38d48b0 100644 --- a/lib/utils/requirements/platform.dart +++ b/lib/utils/requirements/platform.dart @@ -9,13 +9,14 @@ import 'package:swift_control/utils/requirements/multi.dart'; abstract class PlatformRequirement { String name; + String? description; late bool status; - PlatformRequirement(this.name); + PlatformRequirement(this.name, {this.description}); Future getStatus(); - Future call(); + Future call(BuildContext context, VoidCallback onUpdate); Widget? build(BuildContext context, VoidCallback onUpdate) { return null; @@ -37,6 +38,7 @@ Future> getRequirements() async { final deviceInfo = await deviceInfoPlugin.androidInfo; list = [ BluetoothTurnedOn(), + ConnectRequirement(), AccessibilityRequirement(), NotificationRequirement(), if (deviceInfo.version.sdkInt <= 30)