mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
try it on Android #1
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
<!-- Allow Bluetooth -->
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
|
||||
|
||||
<!-- New Bluetooth permissions in Android 12
|
||||
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||
|
||||
@@ -142,9 +142,22 @@ class _RequirementsPageState extends State<RequirementsPage> 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<RequirementsPage> with WidgetsBinding
|
||||
);
|
||||
}
|
||||
|
||||
void _callRequirement(PlatformRequirement req) {
|
||||
req.call().then((_) {
|
||||
void _callRequirement(PlatformRequirement req, BuildContext context, VoidCallback onUpdate) {
|
||||
req.call(context, onUpdate).then((_) {
|
||||
_reloadRequirements();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> 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<String> performAction(ZwiftButton action, {bool isKeyDown = true, bool isKeyUp = false}) {
|
||||
|
||||
@@ -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<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<void> call() async {
|
||||
return accessibilityHandler.openPermissions();
|
||||
Future<void> 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<void> _showDisclosureDialog(BuildContext context, VoidCallback onUpdate) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
@@ -72,7 +51,7 @@ class BluetoothScanRequirement extends PlatformRequirement {
|
||||
BluetoothScanRequirement() : super('Allow Bluetooth Scan');
|
||||
|
||||
@override
|
||||
Future<void> call() async {
|
||||
Future<void> 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<void> call() async {
|
||||
Future<void> 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<void> call() async {
|
||||
Future<void> 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<void> call() async {
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission();
|
||||
|
||||
@@ -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<void> call() async {}
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
Future<void> 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<void> getStatus() async {
|
||||
status = (actionHandler as IosActions).isConnected;
|
||||
status = (actionHandler as AccessibilityActions).isConnected && false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class KeyboardRequirement extends PlatformRequirement {
|
||||
KeyboardRequirement() : super('Keyboard access');
|
||||
|
||||
@override
|
||||
Future<void> call() async {
|
||||
Future<void> 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<void> call() async {
|
||||
Future<void> 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<void> call() async {}
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
@override
|
||||
Future<void> getStatus() async {}
|
||||
@@ -60,7 +60,7 @@ class BluetoothScanning extends PlatformRequirement {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> call() async {}
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate) async {}
|
||||
|
||||
@override
|
||||
Future<void> getStatus() async {}
|
||||
|
||||
@@ -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<void> getStatus();
|
||||
|
||||
Future<void> call();
|
||||
Future<void> call(BuildContext context, VoidCallback onUpdate);
|
||||
|
||||
Widget? build(BuildContext context, VoidCallback onUpdate) {
|
||||
return null;
|
||||
@@ -37,6 +38,7 @@ Future<List<PlatformRequirement>> getRequirements() async {
|
||||
final deviceInfo = await deviceInfoPlugin.androidInfo;
|
||||
list = [
|
||||
BluetoothTurnedOn(),
|
||||
ConnectRequirement(),
|
||||
AccessibilityRequirement(),
|
||||
NotificationRequirement(),
|
||||
if (deviceInfo.version.sdkInt <= 30)
|
||||
|
||||
Reference in New Issue
Block a user