Files
xianyan/lib/core/services/input/speech_provider.dart
Developer 9ea8d3d606 chore: 汇总批量提交的功能优化与bug修复
本次提交包含多项迭代优化和问题修复:
1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持
2. 优化底部导航栏主题色统一使用动态accent色值
3. 修复多处图表动画、路由跳转、API请求相关问题
4. 简化服务器公告文案,调整默认分屏状态为关闭
5. 新增安卓/iOS桌面快捷方式配置
6. 重构多处状态管理类使用SafeNotifierInit统一异常保护
7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取
8. 优化缓存预加载逻辑,移除无用代码
9. 调整默认设置项,优化用户体验细节
2026-05-31 12:24:05 +08:00

158 lines
4.3 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言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;
});