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)