feat: refine AI navigation and accessibility recovery

This commit is contained in:
2026-08-26 22:58:26 +08:00
parent c6f98b5445
commit 9ccbdfead4
15 changed files with 521 additions and 203 deletions
@@ -62,9 +62,39 @@ class RecognitionDiagnostic {
}
}
enum AccessibilityConnectionState {
unauthorized,
reconnecting,
connected,
disconnected;
static AccessibilityConnectionState parse(
Object? value, {
required bool authorized,
required bool connected,
}) {
return switch (value?.toString()) {
'reconnecting' => AccessibilityConnectionState.reconnecting,
'connected' => AccessibilityConnectionState.connected,
'disconnected' => AccessibilityConnectionState.disconnected,
'unauthorized' => AccessibilityConnectionState.unauthorized,
_ =>
!authorized
? unauthorized
: connected
? AccessibilityConnectionState.connected
: disconnected,
};
}
}
class RecognitionStatus {
final bool accessibilityAuthorized;
final bool accessibilityConnected;
final AccessibilityConnectionState accessibilityConnectionState;
final DateTime? accessibilityLastConnectedAt;
final DateTime? accessibilityLastDisconnectedAt;
final DateTime? recognitionProcessStartedAt;
final bool notificationAuthorized;
final bool notificationConnected;
final bool postNotificationsGranted;
@@ -81,6 +111,11 @@ class RecognitionStatus {
const RecognitionStatus({
required this.accessibilityAuthorized,
required this.accessibilityConnected,
this.accessibilityConnectionState =
AccessibilityConnectionState.unauthorized,
this.accessibilityLastConnectedAt,
this.accessibilityLastDisconnectedAt,
this.recognitionProcessStartedAt,
required this.notificationAuthorized,
required this.notificationConnected,
required this.postNotificationsGranted,
@@ -101,10 +136,28 @@ class RecognitionStatus {
final settings = rawSettings == null || rawSettings.isEmpty
? const <String, dynamic>{}
: jsonDecode(rawSettings) as Map<String, dynamic>;
final accessibilityAuthorized =
value['accessibilityAuthorized'] as bool? ?? false;
final accessibilityConnected =
value['accessibilityConnected'] as bool? ?? false;
DateTime? epochDate(String key) {
final epoch = (value[key] as num?)?.toInt() ?? 0;
return epoch <= 0 ? null : DateTime.fromMillisecondsSinceEpoch(epoch);
}
return RecognitionStatus(
accessibilityAuthorized:
value['accessibilityAuthorized'] as bool? ?? false,
accessibilityConnected: value['accessibilityConnected'] as bool? ?? false,
accessibilityAuthorized: accessibilityAuthorized,
accessibilityConnected: accessibilityConnected,
accessibilityConnectionState: AccessibilityConnectionState.parse(
value['accessibilityConnectionState'],
authorized: accessibilityAuthorized,
connected: accessibilityConnected,
),
accessibilityLastConnectedAt: epochDate('accessibilityLastConnectedAt'),
accessibilityLastDisconnectedAt: epochDate(
'accessibilityLastDisconnectedAt',
),
recognitionProcessStartedAt: epochDate('recognitionProcessStartedAt'),
notificationAuthorized: value['notificationAuthorized'] as bool? ?? false,
notificationConnected: value['notificationConnected'] as bool? ?? false,
postNotificationsGranted:
@@ -130,6 +183,19 @@ class RecognitionStatus {
),
);
}
bool get accessibilityNeedsRecovery =>
accessibilityAuthorized &&
!accessibilityConnected &&
accessibilityConnectionState == AccessibilityConnectionState.disconnected;
String get accessibilityConnectionLabel =>
switch (accessibilityConnectionState) {
AccessibilityConnectionState.unauthorized => '未授权',
AccessibilityConnectionState.reconnecting => '正在重连',
AccessibilityConnectionState.connected => '已连接',
AccessibilityConnectionState.disconnected => '服务未连接',
};
}
class RecognitionCandidate {
@@ -403,6 +469,31 @@ class ScreenshotChannel {
}
}
static Future<RecognitionStatus> waitForAccessibilityConnection({
Duration timeout = const Duration(seconds: 5),
Duration interval = const Duration(milliseconds: 500),
}) async {
var status = await recognitionStatus();
final recognitionEnabled =
status.accessibilityEvents || status.aiScreenshot;
if (!recognitionEnabled ||
!status.accessibilityAuthorized ||
status.accessibilityConnected) {
return status;
}
final deadline = DateTime.now().add(timeout);
while (DateTime.now().isBefore(deadline)) {
await Future<void>.delayed(interval);
status = await recognitionStatus();
if (!status.accessibilityAuthorized ||
status.accessibilityConnected ||
!(status.accessibilityEvents || status.aiScreenshot)) {
return status;
}
}
return status;
}
static Future<bool> clearRecognitionDiagnostic() async {
try {
return await _channel.invokeMethod<bool>('clearRecognitionDiagnostic') ??