本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
158 lines
4.3 KiB
Dart
158 lines
4.3 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 语音输入Provider
|
||
/// 创建时间: 2026-05-25
|
||
/// 更新时间: 2026-05-30
|
||
/// 作用: 语音服务状态管理 + 识别结果Provider
|
||
/// 上次更新: 修复隐私问题 — build()不再自动初始化语音服务,改为用户主动触发时才初始化
|
||
/// ============================================================
|
||
|
||
import 'dart:async';
|
||
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import '../../services/input/speech_service.dart';
|
||
import '../../utils/logger.dart';
|
||
|
||
/// 语音服务状态
|
||
class SpeechState {
|
||
const SpeechState({
|
||
this.isAvailable = false,
|
||
this.isListening = false,
|
||
this.currentText = '',
|
||
this.lastResult = '',
|
||
this.locale = 'zh_CN',
|
||
this.errorMessage,
|
||
this.isInitialized = false,
|
||
});
|
||
|
||
final bool isAvailable;
|
||
final bool isListening;
|
||
final String currentText;
|
||
final String lastResult;
|
||
final String locale;
|
||
final String? errorMessage;
|
||
|
||
/// 是否已初始化(仅用户主动触发后才为true)
|
||
final bool isInitialized;
|
||
|
||
SpeechState copyWith({
|
||
bool? isAvailable,
|
||
bool? isListening,
|
||
String? currentText,
|
||
String? lastResult,
|
||
String? locale,
|
||
String? errorMessage,
|
||
bool? isInitialized,
|
||
}) {
|
||
return SpeechState(
|
||
isAvailable: isAvailable ?? this.isAvailable,
|
||
isListening: isListening ?? this.isListening,
|
||
currentText: currentText ?? this.currentText,
|
||
lastResult: lastResult ?? this.lastResult,
|
||
locale: locale ?? this.locale,
|
||
errorMessage: errorMessage,
|
||
isInitialized: isInitialized ?? this.isInitialized,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 语音服务Notifier
|
||
///
|
||
/// 安全设计:build()不自动初始化语音服务,避免在页面加载时
|
||
/// 触发麦克风权限申请。仅在用户主动点击语音按钮时调用init()。
|
||
class SpeechNotifier extends Notifier<SpeechState> {
|
||
StreamSubscription<SpeechServiceState>? _stateSub;
|
||
StreamSubscription<SpeechResult>? _resultSub;
|
||
StreamSubscription<String>? _errorSub;
|
||
|
||
@override
|
||
SpeechState build() {
|
||
ref.onDispose(_onDispose);
|
||
return const SpeechState();
|
||
}
|
||
|
||
/// 用户主动触发初始化 — 仅此时才请求麦克风权限
|
||
Future<bool> init() async {
|
||
if (state.isInitialized) return state.isAvailable;
|
||
|
||
final service = SpeechService.instance;
|
||
final available = await service.init();
|
||
state = state.copyWith(isAvailable: available, isInitialized: true);
|
||
|
||
_stateSub = service.onStateChanged.listen((s) {
|
||
state = state.copyWith(
|
||
isListening: s == SpeechServiceState.listening,
|
||
isAvailable: s != SpeechServiceState.unavailable,
|
||
);
|
||
});
|
||
|
||
_resultSub = service.onResult.listen((result) {
|
||
if (result.isFinal) {
|
||
state = state.copyWith(
|
||
lastResult: result.text,
|
||
currentText: result.text,
|
||
);
|
||
} else {
|
||
state = state.copyWith(currentText: result.text);
|
||
}
|
||
});
|
||
|
||
_errorSub = service.onError.listen((error) {
|
||
state = state.copyWith(errorMessage: error);
|
||
Log.w('SpeechProvider: 错误 $error');
|
||
});
|
||
|
||
return available;
|
||
}
|
||
|
||
/// 开始语音识别
|
||
Future<void> startListening({String? localeId}) async {
|
||
state = state.copyWith();
|
||
await SpeechService.instance.startListening(
|
||
localeId: localeId ?? state.locale,
|
||
);
|
||
}
|
||
|
||
/// 停止语音识别
|
||
Future<void> stopListening() async {
|
||
await SpeechService.instance.stopListening();
|
||
}
|
||
|
||
/// 取消语音识别
|
||
Future<void> cancelListening() async {
|
||
await SpeechService.instance.cancelListening();
|
||
}
|
||
|
||
/// 切换语言
|
||
void setLocale(String localeId) {
|
||
SpeechService.instance.setLocale(localeId);
|
||
state = state.copyWith(locale: localeId);
|
||
}
|
||
|
||
/// 清除识别结果
|
||
void clearResult() {
|
||
state = state.copyWith(currentText: '', lastResult: '');
|
||
}
|
||
|
||
void _onDispose() {
|
||
_stateSub?.cancel();
|
||
_resultSub?.cancel();
|
||
_errorSub?.cancel();
|
||
}
|
||
}
|
||
|
||
/// 语音服务状态Provider
|
||
final speechProvider = NotifierProvider<SpeechNotifier, SpeechState>(
|
||
SpeechNotifier.new,
|
||
);
|
||
|
||
/// 语音识别结果Provider — 仅暴露最终结果
|
||
final speechResultProvider = Provider<String>((ref) {
|
||
return ref.watch(speechProvider).lastResult;
|
||
});
|
||
|
||
/// 语音是否可用Provider
|
||
final speechAvailableProvider = Provider<bool>((ref) {
|
||
return ref.watch(speechProvider).isAvailable;
|
||
});
|