- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
132 lines
4.1 KiB
Dart
132 lines
4.1 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 无障碍服务
|
||
/// 创建时间: 2026-06-05
|
||
/// 更新时间: 2026-06-12
|
||
/// 作用: 管理VoiceOver/TalkBack适配,提供无障碍模式检测、
|
||
/// 语义标注辅助、设置管理
|
||
/// 上次更新: 从 accessibility/ 子目录上移至 services/ 目录
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/semantics.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
import '../utils/logger.dart';
|
||
|
||
/// 无障碍服务 — 单例管理VoiceOver/TalkBack适配
|
||
///
|
||
/// 职责:
|
||
/// - 检测系统无障碍状态(高对比度、减少动画、粗体文本)
|
||
/// - 管理语义调试开关(SemanticsDebugger)
|
||
/// - 提供语义标注辅助方法
|
||
class AccessibilityService {
|
||
AccessibilityService._();
|
||
static AccessibilityService? _instance;
|
||
static AccessibilityService get instance => _instance ??= AccessibilityService._();
|
||
|
||
// ── 状态字段 ──
|
||
|
||
/// 是否启用语义调试(开发者选项)
|
||
bool _semanticsDebugEnabled = false;
|
||
|
||
/// 是否高对比度模式(系统级,只读)
|
||
bool _highContrastEnabled = false;
|
||
|
||
/// 是否减少动画(系统级,只读)
|
||
bool _reducedMotionEnabled = false;
|
||
|
||
/// 是否粗体文本(系统级,只读)
|
||
bool _boldTextEnabled = false;
|
||
|
||
// ── Getter ──
|
||
|
||
/// 是否启用语义调试
|
||
bool get semanticsDebugEnabled => _semanticsDebugEnabled;
|
||
|
||
/// 是否高对比度模式
|
||
bool get highContrastEnabled => _highContrastEnabled;
|
||
|
||
/// 是否减少动画
|
||
bool get reducedMotionEnabled => _reducedMotionEnabled;
|
||
|
||
/// 是否粗体文本
|
||
bool get boldTextEnabled => _boldTextEnabled;
|
||
|
||
// ── 初始化与更新 ──
|
||
|
||
/// 从BuildContext更新系统无障碍状态
|
||
///
|
||
/// 应在App根组件build时调用,以同步系统级无障碍设置
|
||
void updateFromContext(BuildContext context) {
|
||
final mediaQuery = MediaQuery.of(context);
|
||
final changed = _highContrastEnabled != mediaQuery.highContrast ||
|
||
_reducedMotionEnabled != mediaQuery.disableAnimations ||
|
||
_boldTextEnabled != mediaQuery.boldText;
|
||
|
||
_highContrastEnabled = mediaQuery.highContrast;
|
||
_reducedMotionEnabled = mediaQuery.disableAnimations;
|
||
_boldTextEnabled = mediaQuery.boldText;
|
||
|
||
if (changed) {
|
||
Log.i(
|
||
'无障碍状态更新: 高对比度=$_highContrastEnabled, '
|
||
'减少动画=$_reducedMotionEnabled, '
|
||
'粗体文本=$_boldTextEnabled',
|
||
null,
|
||
null,
|
||
LogCategory.service,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 初始化语义调试状态(从持久化存储恢复)
|
||
void initSemanticsDebug(bool enabled) {
|
||
_semanticsDebugEnabled = enabled;
|
||
Log.i('无障碍服务初始化: 语义调试=$enabled', null, null, LogCategory.service);
|
||
}
|
||
|
||
/// 切换语义调试开关
|
||
///
|
||
/// 开启后会在应用最外层显示SemanticsDebugger覆盖层,
|
||
/// 方便开发者检查语义标注是否正确
|
||
void setSemanticsDebug(bool enabled) {
|
||
if (_semanticsDebugEnabled == enabled) return;
|
||
_semanticsDebugEnabled = enabled;
|
||
Log.i('语义调试切换: $enabled', null, null, LogCategory.service);
|
||
|
||
// 通知语义调试状态变化
|
||
// ignore: deprecated_member_use
|
||
SemanticsService.announce(
|
||
enabled ? '语义调试已开启' : '语义调试已关闭',
|
||
TextDirection.ltr,
|
||
);
|
||
}
|
||
|
||
// ── 语义标注辅助 ──
|
||
|
||
/// 为卡片类组件生成语义标注字符串
|
||
///
|
||
/// [title] 卡片标题
|
||
/// [subtitle] 卡片副标题
|
||
/// [extra] 额外信息(如点赞数、评论数)
|
||
static String cardLabel({
|
||
required String title,
|
||
String? subtitle,
|
||
String? extra,
|
||
}) {
|
||
final parts = [title];
|
||
if (subtitle != null && subtitle.isNotEmpty) parts.add(subtitle);
|
||
if (extra != null && extra.isNotEmpty) parts.add(extra);
|
||
return parts.join(',');
|
||
}
|
||
|
||
/// 为开关类组件生成语义hint
|
||
static String toggleHint(String label, bool currentValue) {
|
||
return currentValue ? '关闭$label' : '开启$label';
|
||
}
|
||
|
||
/// 为列表项生成语义value
|
||
static String listItemValue(int index, int total) {
|
||
return '第${index + 1}项,共$total项';
|
||
}
|
||
}
|