Files
swiftcontrol/lib/utils/requirements/android.dart
2025-09-27 12:18:50 +02:00

193 lines
5.9 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:swift_control/main.dart';
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');
@override
Future<void> call() async {
return accessibilityHandler.openPermissions();
}
@override
Future<void> getStatus() async {
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,
barrierDismissible: false, // Prevent dismissing by tapping outside
builder: (BuildContext context) {
return AccessibilityDisclosureDialog(
onAccept: () {
Navigator.of(context).pop();
// Open accessibility settings after user consents
accessibilityHandler.openPermissions().then((_) {
onUpdate();
});
},
onDeny: () {
Navigator.of(context).pop();
// User denied, no action taken
},
);
},
);
}
}
class BluetoothScanRequirement extends PlatformRequirement {
BluetoothScanRequirement() : super('Allow Bluetooth Scan');
@override
Future<void> call() async {
await Permission.bluetoothScan.request();
}
@override
Future<void> getStatus() async {
final state = await Permission.bluetoothScan.status;
status = state.isGranted || state.isLimited;
}
}
class LocationRequirement extends PlatformRequirement {
LocationRequirement() : super('Allow Location so Bluetooth scan works');
@override
Future<void> call() async {
await Permission.locationWhenInUse.request();
}
@override
Future<void> getStatus() async {
final state = await Permission.locationWhenInUse.status;
status = state.isGranted || state.isLimited;
}
}
class BluetoothConnectRequirement extends PlatformRequirement {
BluetoothConnectRequirement() : super('Allow Bluetooth Connections');
@override
Future<void> call() async {
await Permission.bluetoothConnect.request();
}
@override
Future<void> getStatus() async {
final state = await Permission.bluetoothConnect.status;
status = state.isGranted || state.isLimited;
}
}
class NotificationRequirement extends PlatformRequirement {
NotificationRequirement() : super('Allow adding persistent Notification (keeps app alive)');
@override
Future<void> call() async {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
return;
}
@override
Future<void> getStatus() async {
final bool granted =
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.areNotificationsEnabled() ??
false;
status = granted;
}
static Future<void> setup() async {
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings(
'@mipmap/ic_launcher',
);
await flutterLocalNotificationsPlugin.initialize(
InitializationSettings(android: initializationSettingsAndroid),
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
onDidReceiveNotificationResponse: (n) {
notificationTapBackground(n);
},
);
const String channelGroupId = 'SwiftControl';
// create the group first
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()!
.createNotificationChannelGroup(
AndroidNotificationChannelGroup(channelGroupId, channelGroupId, description: 'Keep Alive'),
);
// create channels associated with the group
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()!
.createNotificationChannel(
const AndroidNotificationChannel(
channelGroupId,
channelGroupId,
description: 'Keep Alive',
groupId: channelGroupId,
),
);
await AndroidFlutterLocalNotificationsPlugin().startForegroundService(
1,
channelGroupId,
'Bluetooth keep alive',
foregroundServiceTypes: {AndroidServiceForegroundType.foregroundServiceTypeConnectedDevice},
notificationDetails: AndroidNotificationDetails(
channelGroupId,
'Keep Alive',
actions: [AndroidNotificationAction('Exit', 'Exit', cancelNotification: true, showsUserInterface: false)],
),
);
}
}
@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
if (notificationResponse.actionId != null) {
connection.reset();
AndroidFlutterLocalNotificationsPlugin().stopForegroundService().then((_) {
exit(0);
});
}
}