This commit is contained in:
sjk
2025-11-17 14:09:17 +08:00
commit 31e46c5bf6
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,284 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'storage_service.dart';
import '../config/environment.dart';
class ApiResponse {
final dynamic data;
final int statusCode;
final String? message;
ApiResponse({
required this.data,
required this.statusCode,
this.message,
});
}
class ApiService {
late final Dio _dio;
final StorageService _storageService;
ApiService({required StorageService storageService})
: _storageService = storageService {
_dio = Dio(BaseOptions(
baseUrl: EnvironmentConfig.baseUrl,
connectTimeout: Duration(milliseconds: EnvironmentConfig.connectTimeout),
receiveTimeout: Duration(milliseconds: EnvironmentConfig.receiveTimeout),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
_setupInterceptors();
}
void _setupInterceptors() {
// 请求拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
// 添加认证token
final token = await _storageService.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
if (kDebugMode) {
print('API Request: ${options.method} ${options.uri}');
print('Headers: ${options.headers}');
if (options.data != null) {
print('Data: ${options.data}');
}
}
handler.next(options);
},
onResponse: (response, handler) {
if (kDebugMode) {
print('API Response: ${response.statusCode} ${response.requestOptions.uri}');
}
handler.next(response);
},
onError: (error, handler) {
if (kDebugMode) {
print('API Error: ${error.message}');
print('Response: ${error.response?.data}');
}
handler.next(error);
},
));
}
// GET请求
Future<ApiResponse> get(
String path, {
Map<String, dynamic>? queryParams,
Options? options,
}) async {
try {
final response = await _dio.get(
path,
queryParameters: queryParams,
options: options,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// POST请求
Future<ApiResponse> post(
String path,
dynamic data, {
Map<String, dynamic>? queryParams,
Options? options,
}) async {
try {
final response = await _dio.post(
path,
data: data,
queryParameters: queryParams,
options: options,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// PUT请求
Future<ApiResponse> put(
String path,
dynamic data, {
Map<String, dynamic>? queryParams,
Options? options,
}) async {
try {
final response = await _dio.put(
path,
data: data,
queryParameters: queryParams,
options: options,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// PATCH请求
Future<ApiResponse> patch(
String path,
dynamic data, {
Map<String, dynamic>? queryParams,
Options? options,
}) async {
try {
final response = await _dio.patch(
path,
data: data,
queryParameters: queryParams,
options: options,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// DELETE请求
Future<ApiResponse> delete(
String path, {
Map<String, dynamic>? queryParams,
Options? options,
}) async {
try {
final response = await _dio.delete(
path,
queryParameters: queryParams,
options: options,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 上传文件
Future<ApiResponse> uploadFile(
String path,
String filePath, {
String? fileName,
Map<String, dynamic>? data,
ProgressCallback? onSendProgress,
}) async {
try {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
filePath,
filename: fileName,
),
...?data,
});
final response = await _dio.post(
path,
data: formData,
options: Options(
headers: {
'Content-Type': 'multipart/form-data',
},
),
onSendProgress: onSendProgress,
);
return ApiResponse(
data: response.data,
statusCode: response.statusCode ?? 200,
message: response.statusMessage,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 下载文件
Future<void> downloadFile(
String url,
String savePath, {
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
}) async {
try {
await _dio.download(
url,
savePath,
onReceiveProgress: onReceiveProgress,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 错误处理
Exception _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return Exception('网络连接超时,请检查网络设置');
case DioExceptionType.badResponse:
final statusCode = error.response?.statusCode;
final message = error.response?.data?['message'] ?? '请求失败';
switch (statusCode) {
case 400:
return Exception('请求参数错误: $message');
case 401:
return Exception('认证失败,请重新登录');
case 403:
return Exception('权限不足: $message');
case 404:
return Exception('请求的资源不存在');
case 500:
return Exception('服务器内部错误,请稍后重试');
default:
return Exception('请求失败($statusCode): $message');
}
case DioExceptionType.cancel:
return Exception('请求已取消');
case DioExceptionType.connectionError:
return Exception('网络连接失败,请检查网络设置');
case DioExceptionType.unknown:
default:
return Exception('未知错误: ${error.message}');
}
}
// 取消所有请求
void cancelRequests() {
_dio.close();
}
}

View File

@@ -0,0 +1,378 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
// 音频录制状态
enum RecordingState {
idle,
recording,
paused,
stopped,
}
// 音频播放状态
enum PlaybackState {
idle,
playing,
paused,
stopped,
}
class AudioService {
// 录制相关
RecordingState _recordingState = RecordingState.idle;
String? _currentRecordingPath;
DateTime? _recordingStartTime;
// 播放相关
PlaybackState _playbackState = PlaybackState.idle;
String? _currentPlayingPath;
// 回调函数
Function(RecordingState)? onRecordingStateChanged;
Function(PlaybackState)? onPlaybackStateChanged;
Function(Duration)? onRecordingProgress;
Function(Duration)? onPlaybackProgress;
Function(String)? onRecordingComplete;
Function()? onPlaybackComplete;
// Getters
RecordingState get recordingState => _recordingState;
PlaybackState get playbackState => _playbackState;
String? get currentRecordingPath => _currentRecordingPath;
String? get currentPlayingPath => _currentPlayingPath;
bool get isRecording => _recordingState == RecordingState.recording;
bool get isPlaying => _playbackState == PlaybackState.playing;
// 初始化音频服务
Future<void> initialize() async {
// 请求麦克风权限
await _requestPermissions();
}
// 请求权限
Future<bool> _requestPermissions() async {
// Web平台不支持某些权限需要特殊处理
if (kIsWeb) {
// Web平台只需要麦克风权限且通过浏览器API处理
if (kDebugMode) {
print('Web平台跳过权限请求');
}
return true;
}
try {
final microphoneStatus = await Permission.microphone.request();
if (microphoneStatus != PermissionStatus.granted) {
throw Exception('需要麦克风权限才能录音');
}
// 存储权限在某些平台可能不需要
try {
final storageStatus = await Permission.storage.request();
if (storageStatus != PermissionStatus.granted) {
if (kDebugMode) {
print('存储权限未授予,但继续执行');
}
}
} catch (e) {
// 某些平台不支持存储权限,忽略错误
if (kDebugMode) {
print('存储权限请求失败(可能不支持): $e');
}
}
return true;
} catch (e) {
if (kDebugMode) {
print('权限请求失败: $e');
}
// 在某些平台上,权限请求可能失败,但仍然可以继续
return true;
}
}
// 开始录音
Future<void> startRecording({String? fileName}) async {
try {
if (_recordingState == RecordingState.recording) {
throw Exception('已经在录音中');
}
await _requestPermissions();
// 生成录音文件路径
if (kIsWeb) {
// Web平台使用内存存储或IndexedDB
fileName ??= 'recording_${DateTime.now().millisecondsSinceEpoch}.webm';
_currentRecordingPath = '/recordings/$fileName';
} else {
final directory = await getApplicationDocumentsDirectory();
final recordingsDir = Directory('${directory.path}/recordings');
if (!await recordingsDir.exists()) {
await recordingsDir.create(recursive: true);
}
fileName ??= 'recording_${DateTime.now().millisecondsSinceEpoch}.m4a';
_currentRecordingPath = '${recordingsDir.path}/$fileName';
}
// 这里应该使用实际的录音插件,比如 record 或 flutter_sound
// 由于没有实际的录音插件,这里只是模拟
_recordingStartTime = DateTime.now();
_setRecordingState(RecordingState.recording);
if (kDebugMode) {
print('开始录音: $_currentRecordingPath');
}
// 模拟录音进度更新
_startRecordingProgressTimer();
} catch (e) {
throw Exception('开始录音失败: ${e.toString()}');
}
}
// 停止录音
Future<String?> stopRecording() async {
try {
if (_recordingState != RecordingState.recording) {
throw Exception('当前没有在录音');
}
// 这里应该调用实际录音插件的停止方法
_setRecordingState(RecordingState.stopped);
final recordingPath = _currentRecordingPath;
if (kDebugMode) {
print('录音完成: $recordingPath');
}
// 通知录音完成
if (recordingPath != null && onRecordingComplete != null) {
onRecordingComplete!(recordingPath);
}
return recordingPath;
} catch (e) {
throw Exception('停止录音失败: ${e.toString()}');
}
}
// 暂停录音
Future<void> pauseRecording() async {
try {
if (_recordingState != RecordingState.recording) {
throw Exception('当前没有在录音');
}
// 这里应该调用实际录音插件的暂停方法
_setRecordingState(RecordingState.paused);
if (kDebugMode) {
print('录音已暂停');
}
} catch (e) {
throw Exception('暂停录音失败: ${e.toString()}');
}
}
// 恢复录音
Future<void> resumeRecording() async {
try {
if (_recordingState != RecordingState.paused) {
throw Exception('录音没有暂停');
}
// 这里应该调用实际录音插件的恢复方法
_setRecordingState(RecordingState.recording);
if (kDebugMode) {
print('录音已恢复');
}
} catch (e) {
throw Exception('恢复录音失败: ${e.toString()}');
}
}
// 播放音频
Future<void> playAudio(String audioPath) async {
try {
if (_playbackState == PlaybackState.playing) {
await stopPlayback();
}
_currentPlayingPath = audioPath;
// 这里应该使用实际的音频播放插件,比如 audioplayers 或 just_audio
// 由于没有实际的播放插件,这里只是模拟
_setPlaybackState(PlaybackState.playing);
if (kDebugMode) {
print('开始播放: $audioPath');
}
// 模拟播放进度和完成
_startPlaybackProgressTimer();
} catch (e) {
throw Exception('播放音频失败: ${e.toString()}');
}
}
// 暂停播放
Future<void> pausePlayback() async {
try {
if (_playbackState != PlaybackState.playing) {
throw Exception('当前没有在播放');
}
// 这里应该调用实际播放插件的暂停方法
_setPlaybackState(PlaybackState.paused);
if (kDebugMode) {
print('播放已暂停');
}
} catch (e) {
throw Exception('暂停播放失败: ${e.toString()}');
}
}
// 恢复播放
Future<void> resumePlayback() async {
try {
if (_playbackState != PlaybackState.paused) {
throw Exception('播放没有暂停');
}
// 这里应该调用实际播放插件的恢复方法
_setPlaybackState(PlaybackState.playing);
if (kDebugMode) {
print('播放已恢复');
}
} catch (e) {
throw Exception('恢复播放失败: ${e.toString()}');
}
}
// 停止播放
Future<void> stopPlayback() async {
try {
// 这里应该调用实际播放插件的停止方法
_setPlaybackState(PlaybackState.stopped);
_currentPlayingPath = null;
if (kDebugMode) {
print('播放已停止');
}
} catch (e) {
throw Exception('停止播放失败: ${e.toString()}');
}
}
// 获取音频文件时长
Future<Duration?> getAudioDuration(String audioPath) async {
try {
// 这里应该使用实际的音频插件获取时长
// 模拟返回时长
return const Duration(seconds: 30);
} catch (e) {
if (kDebugMode) {
print('获取音频时长失败: ${e.toString()}');
}
return null;
}
}
// 删除录音文件
Future<bool> deleteRecording(String filePath) async {
try {
final file = File(filePath);
if (await file.exists()) {
await file.delete();
return true;
}
return false;
} catch (e) {
if (kDebugMode) {
print('删除录音文件失败: ${e.toString()}');
}
return false;
}
}
// 获取所有录音文件
Future<List<String>> getAllRecordings() async {
try {
final directory = await getApplicationDocumentsDirectory();
final recordingsDir = Directory('${directory.path}/recordings');
if (!await recordingsDir.exists()) {
return [];
}
final files = await recordingsDir.list().toList();
return files
.where((file) => file is File && file.path.endsWith('.m4a'))
.map((file) => file.path)
.toList();
} catch (e) {
if (kDebugMode) {
print('获取录音文件列表失败: ${e.toString()}');
}
return [];
}
}
// 私有方法:设置录音状态
void _setRecordingState(RecordingState state) {
_recordingState = state;
onRecordingStateChanged?.call(state);
}
// 私有方法:设置播放状态
void _setPlaybackState(PlaybackState state) {
_playbackState = state;
onPlaybackStateChanged?.call(state);
}
// 私有方法:录音进度计时器
void _startRecordingProgressTimer() {
// 这里应该实现实际的进度更新逻辑
// 模拟进度更新
}
// 私有方法:播放进度计时器
void _startPlaybackProgressTimer() {
// 这里应该实现实际的播放进度更新逻辑
// 模拟播放完成
Future.delayed(const Duration(seconds: 3), () {
_setPlaybackState(PlaybackState.stopped);
onPlaybackComplete?.call();
});
}
// 释放资源
void dispose() {
// 停止所有操作
if (_recordingState == RecordingState.recording) {
stopRecording();
}
if (_playbackState == PlaybackState.playing) {
stopPlayback();
}
// 清理回调
onRecordingStateChanged = null;
onPlaybackStateChanged = null;
onRecordingProgress = null;
onPlaybackProgress = null;
onRecordingComplete = null;
onPlaybackComplete = null;
}
}

View File

@@ -0,0 +1,327 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import '../models/user_model.dart';
import '../network/api_client.dart';
import '../network/api_endpoints.dart';
import '../errors/app_exception.dart';
/// 认证服务
class AuthService {
final ApiClient _apiClient;
AuthService(this._apiClient);
/// 登录
Future<AuthResponse> login({
required String account, // 用户名或邮箱
required String password,
bool rememberMe = false,
}) async {
try {
final response = await _apiClient.post(
ApiEndpoints.login,
data: {
'account': account,
'password': password,
},
);
// 后端返回格式: {code, message, data: {user, access_token, refresh_token, expires_in}}
final data = response.data['data'];
final userInfo = data['user'];
return AuthResponse(
user: User(
id: userInfo['id'].toString(),
username: userInfo['username'],
email: userInfo['email'],
avatar: userInfo['avatar'],
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
token: data['access_token'],
refreshToken: data['refresh_token'],
expiresAt: DateTime.now().add(Duration(seconds: data['expires_in'])),
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('登录失败: $e');
}
}
/// 用户注册
Future<AuthResponse> register({
required String email,
required String password,
required String username,
required String nickname,
}) async {
try {
final response = await _apiClient.post(
ApiEndpoints.register,
data: {
'email': email,
'username': username,
'password': password,
'nickname': nickname,
},
);
// 后端返回的数据结构需要转换
final data = response.data['data'];
final userInfo = data['user'];
return AuthResponse(
user: User(
id: userInfo['id'].toString(),
username: userInfo['username'],
email: userInfo['email'],
avatar: userInfo['avatar'],
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
token: data['access_token'],
refreshToken: data['refresh_token'],
expiresAt: DateTime.now().add(Duration(seconds: data['expires_in'])),
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('注册失败: $e');
}
}
/// 第三方登录
Future<AuthResponse> socialLogin({
required String provider,
required String accessToken,
}) async {
try {
final response = await _apiClient.post(
ApiEndpoints.socialLogin,
data: {
'provider': provider,
'access_token': accessToken,
},
);
return AuthResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('第三方登录失败: $e');
}
}
/// 忘记密码
Future<void> forgotPassword(String email) async {
try {
await _apiClient.post(
ApiEndpoints.forgotPassword,
data: {'email': email},
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('发送重置密码邮件失败: $e');
}
}
/// 重置密码
Future<void> resetPassword({
required String token,
required String newPassword,
required String confirmPassword,
}) async {
try {
await _apiClient.post(
ApiEndpoints.resetPassword,
data: {
'token': token,
'new_password': newPassword,
'confirm_password': confirmPassword,
},
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('重置密码失败: $e');
}
}
/// 修改密码
Future<void> changePassword({
required String currentPassword,
required String newPassword,
required String confirmPassword,
}) async {
try {
await _apiClient.put(
ApiEndpoints.changePassword,
data: {
'current_password': currentPassword,
'new_password': newPassword,
'confirm_password': confirmPassword,
},
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('修改密码失败: $e');
}
}
/// 刷新Token
Future<TokenRefreshResponse> refreshToken(String refreshToken) async {
try {
final response = await _apiClient.post(
ApiEndpoints.refreshToken,
data: {'refresh_token': refreshToken},
);
return TokenRefreshResponse.fromJson(response.data);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('刷新Token失败: $e');
}
}
/// 登出
Future<void> logout() async {
try {
await _apiClient.post(ApiEndpoints.logout);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('登出失败: $e');
}
}
/// 获取用户信息
Future<User> getUserInfo() async {
try {
final response = await _apiClient.get(ApiEndpoints.userInfo);
return User.fromJson(response.data);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('获取用户信息失败: $e');
}
}
/// 获取当前用户信息getUserInfo的别名
Future<User> getCurrentUser() async {
return await getUserInfo();
}
/// 更新用户信息
Future<User> updateUserInfo(Map<String, dynamic> data) async {
try {
final response = await _apiClient.put(
ApiEndpoints.userInfo,
data: data,
);
return User.fromJson(response.data);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('更新用户信息失败: $e');
}
}
/// 验证邮箱
Future<void> verifyEmail(String token) async {
try {
await _apiClient.post(
ApiEndpoints.verifyEmail,
data: {'token': token},
);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('验证邮箱失败: $e');
}
}
/// 重新发送验证邮件
Future<void> resendVerificationEmail() async {
try {
await _apiClient.post(ApiEndpoints.resendVerificationEmail);
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('发送验证邮件失败: $e');
}
}
/// 检查用户名是否可用
Future<bool> checkUsernameAvailability(String username) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.checkUsername}?username=$username',
);
return response.data['available'] ?? false;
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('检查用户名失败: $e');
}
}
/// 检查邮箱是否可用
Future<bool> checkEmailAvailability(String email) async {
try {
final response = await _apiClient.get(
'${ApiEndpoints.checkEmail}?email=$email',
);
return response.data['available'] ?? false;
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {
throw AppException('检查邮箱失败: $e');
}
}
/// 处理Dio异常
AppException _handleDioException(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return NetworkException('连接超时');
case DioExceptionType.sendTimeout:
return NetworkException('发送超时');
case DioExceptionType.receiveTimeout:
return NetworkException('接收超时');
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
final message = e.response?.data?['message'] ?? '请求失败';
switch (statusCode) {
case 400:
return ValidationException(message);
case 401:
return AuthException('认证失败');
case 403:
return AuthException('权限不足');
case 404:
return AppException('资源不存在');
case 422:
return ValidationException(message);
case 500:
return ServerException('服务器内部错误');
default:
return AppException('请求失败: $message');
}
case DioExceptionType.cancel:
return AppException('请求已取消');
case DioExceptionType.connectionError:
return NetworkException('网络连接错误');
case DioExceptionType.badCertificate:
return NetworkException('证书错误');
case DioExceptionType.unknown:
default:
return AppException('未知错误: ${e.message}');
}
}
}

View File

@@ -0,0 +1,252 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../storage/storage_service.dart';
/// API数据缓存服务
class CacheService {
static const String _cachePrefix = 'api_cache_';
static const String _timestampPrefix = 'cache_timestamp_';
static const Duration _defaultCacheDuration = Duration(minutes: 30);
/// 设置缓存
static Future<void> setCache(
String key,
dynamic data, {
Duration? duration,
}) async {
try {
final cacheKey = _cachePrefix + key;
final timestampKey = _timestampPrefix + key;
// 存储数据
final jsonData = jsonEncode(data);
await StorageService.setString(cacheKey, jsonData);
// 存储时间戳
final timestamp = DateTime.now().millisecondsSinceEpoch;
await StorageService.setInt(timestampKey, timestamp);
if (kDebugMode) {
print('Cache set for key: $key');
}
} catch (e) {
if (kDebugMode) {
print('Error setting cache for key $key: $e');
}
}
}
/// 获取缓存
static Future<T?> getCache<T>(
String key, {
Duration? duration,
T Function(Map<String, dynamic>)? fromJson,
}) async {
try {
final cacheKey = _cachePrefix + key;
final timestampKey = _timestampPrefix + key;
// 检查缓存是否存在
final cachedData = await StorageService.getString(cacheKey);
final timestamp = await StorageService.getInt(timestampKey);
if (cachedData == null || timestamp == null) {
return null;
}
// 检查缓存是否过期
final cacheDuration = duration ?? _defaultCacheDuration;
final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now();
if (now.difference(cacheTime) > cacheDuration) {
// 缓存过期,删除缓存
await removeCache(key);
return null;
}
// 解析数据
final jsonData = jsonDecode(cachedData);
if (fromJson != null && jsonData is Map<String, dynamic>) {
return fromJson(jsonData);
}
return jsonData as T;
} catch (e) {
if (kDebugMode) {
print('Error getting cache for key $key: $e');
}
return null;
}
}
/// 获取列表缓存
static Future<List<T>?> getListCache<T>(
String key, {
Duration? duration,
required T Function(Map<String, dynamic>) fromJson,
}) async {
try {
final cacheKey = _cachePrefix + key;
final timestampKey = _timestampPrefix + key;
// 检查缓存是否存在
final cachedData = await StorageService.getString(cacheKey);
final timestamp = await StorageService.getInt(timestampKey);
if (cachedData == null || timestamp == null) {
return null;
}
// 检查缓存是否过期
final cacheDuration = duration ?? _defaultCacheDuration;
final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now();
if (now.difference(cacheTime) > cacheDuration) {
// 缓存过期,删除缓存
await removeCache(key);
return null;
}
// 解析数据
final jsonData = jsonDecode(cachedData);
if (jsonData is List) {
return jsonData
.map((item) => fromJson(item as Map<String, dynamic>))
.toList();
}
return null;
} catch (e) {
if (kDebugMode) {
print('Error getting list cache for key $key: $e');
}
return null;
}
}
/// 检查缓存是否有效
static Future<bool> isCacheValid(
String key, {
Duration? duration,
}) async {
try {
final timestampKey = _timestampPrefix + key;
final timestamp = await StorageService.getInt(timestampKey);
if (timestamp == null) {
return false;
}
final cacheDuration = duration ?? _defaultCacheDuration;
final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now();
return now.difference(cacheTime) <= cacheDuration;
} catch (e) {
return false;
}
}
/// 移除缓存
static Future<void> removeCache(String key) async {
try {
final cacheKey = _cachePrefix + key;
final timestampKey = _timestampPrefix + key;
await StorageService.remove(cacheKey);
await StorageService.remove(timestampKey);
if (kDebugMode) {
print('Cache removed for key: $key');
}
} catch (e) {
if (kDebugMode) {
print('Error removing cache for key $key: $e');
}
}
}
/// 清空所有缓存
static Future<void> clearAllCache() async {
try {
final keys = StorageService.getKeys();
final cacheKeys = keys.where((key) =>
key.startsWith(_cachePrefix) || key.startsWith(_timestampPrefix));
for (final key in cacheKeys) {
await StorageService.remove(key);
}
if (kDebugMode) {
print('All cache cleared');
}
} catch (e) {
if (kDebugMode) {
print('Error clearing all cache: $e');
}
}
}
/// 获取缓存大小信息
static Future<Map<String, int>> getCacheInfo() async {
try {
final keys = StorageService.getKeys();
final cacheKeys = keys.where((key) => key.startsWith(_cachePrefix));
final timestampKeys = keys.where((key) => key.startsWith(_timestampPrefix));
int totalSize = 0;
for (final key in cacheKeys) {
final data = await StorageService.getString(key);
if (data != null) {
totalSize += data.length;
}
}
return {
'count': cacheKeys.length,
'size': totalSize,
'timestamps': timestampKeys.length,
};
} catch (e) {
return {
'count': 0,
'size': 0,
'timestamps': 0,
};
}
}
/// 清理过期缓存
static Future<void> cleanExpiredCache() async {
try {
final keys = StorageService.getKeys();
final timestampKeys = keys.where((key) => key.startsWith(_timestampPrefix));
for (final timestampKey in timestampKeys) {
final timestamp = await StorageService.getInt(timestampKey);
if (timestamp != null) {
final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now();
if (now.difference(cacheTime) > _defaultCacheDuration) {
final cacheKey = timestampKey.replaceFirst(_timestampPrefix, _cachePrefix);
await StorageService.remove(cacheKey);
await StorageService.remove(timestampKey);
}
}
}
if (kDebugMode) {
print('Expired cache cleaned');
}
} catch (e) {
if (kDebugMode) {
print('Error cleaning expired cache: $e');
}
}
}
}

View File

@@ -0,0 +1,288 @@
import 'package:flutter/foundation.dart';
import 'package:dio/dio.dart';
import '../network/api_client.dart';
import '../services/cache_service.dart';
import '../models/api_response.dart';
import '../storage/storage_service.dart';
/// 增强版API服务集成缓存功能
class EnhancedApiService {
final ApiClient _apiClient = ApiClient.instance;
/// GET请求支持缓存
Future<ApiResponse<T>> get<T>(
String endpoint, {
Map<String, dynamic>? queryParameters,
bool useCache = true,
Duration? cacheDuration,
T Function(Map<String, dynamic>)? fromJson,
}) async {
try {
// 生成缓存键
final cacheKey = _generateCacheKey('GET', endpoint, queryParameters);
// 尝试从缓存获取数据
if (useCache) {
final cachedData = await CacheService.getCache<T>(
cacheKey,
duration: cacheDuration,
fromJson: fromJson,
);
if (cachedData != null) {
if (kDebugMode) {
print('Cache hit for: $endpoint');
}
return ApiResponse.success(
data: cachedData,
message: 'Data from cache',
);
}
}
// 发起网络请求
final response = await _apiClient.get(
endpoint,
queryParameters: queryParameters,
);
if (response.statusCode == 200 && response.data != null) {
// 缓存成功响应的数据
if (useCache) {
await CacheService.setCache(
cacheKey,
response.data,
duration: cacheDuration,
);
}
return ApiResponse.success(
data: fromJson != null ? fromJson(response.data) : response.data,
message: 'Success',
);
}
return ApiResponse.error(
message: 'Request failed with status: ${response.statusCode}',
code: response.statusCode,
);
} catch (e) {
if (kDebugMode) {
print('Error in enhanced GET request: $e');
}
return ApiResponse.error(
message: e.toString(),
);
}
}
/// POST请求
Future<ApiResponse<T>> post<T>(
String endpoint, {
Map<String, dynamic>? data,
bool invalidateCache = true,
List<String>? cacheKeysToInvalidate,
T Function(Map<String, dynamic>)? fromJson,
}) async {
try {
final response = await _apiClient.post(
endpoint,
data: data,
);
if (response.statusCode == 200 || response.statusCode == 201) {
// POST请求成功后清除相关缓存
if (invalidateCache) {
await _invalidateRelatedCache(endpoint, cacheKeysToInvalidate);
}
return ApiResponse.success(
data: fromJson != null && response.data != null ? fromJson(response.data) : response.data,
message: 'Success',
);
}
return ApiResponse.error(
message: 'Request failed with status: ${response.statusCode}',
code: response.statusCode,
);
} catch (e) {
if (kDebugMode) {
print('Error in enhanced POST request: $e');
}
return ApiResponse.error(
message: e.toString(),
);
}
}
/// PUT请求
Future<ApiResponse<T>> put<T>(
String endpoint, {
Map<String, dynamic>? data,
bool invalidateCache = true,
List<String>? cacheKeysToInvalidate,
T Function(Map<String, dynamic>)? fromJson,
}) async {
try {
final response = await _apiClient.put(
endpoint,
data: data,
);
if (response.statusCode == 200) {
// PUT请求成功后清除相关缓存
if (invalidateCache) {
await _invalidateRelatedCache(endpoint, cacheKeysToInvalidate);
}
return ApiResponse.success(
data: fromJson != null && response.data != null ? fromJson(response.data) : response.data,
message: 'Success',
);
}
return ApiResponse.error(
message: 'Request failed with status: ${response.statusCode}',
code: response.statusCode,
);
} catch (e) {
if (kDebugMode) {
print('Error in enhanced PUT request: $e');
}
return ApiResponse.error(
message: e.toString(),
);
}
}
/// DELETE请求
Future<ApiResponse<T>> delete<T>(
String endpoint, {
bool invalidateCache = true,
List<String>? cacheKeysToInvalidate,
}) async {
try {
final response = await _apiClient.delete(
endpoint,
);
if (response.statusCode == 200 || response.statusCode == 204) {
// DELETE请求成功后清除相关缓存
if (invalidateCache) {
await _invalidateRelatedCache(endpoint, cacheKeysToInvalidate);
}
return ApiResponse.success(
data: null,
message: 'Success',
);
}
return ApiResponse.error(
message: 'Request failed with status: ${response.statusCode}',
code: response.statusCode,
);
} catch (e) {
if (kDebugMode) {
print('Error in enhanced DELETE request: $e');
}
return ApiResponse.error(
message: e.toString(),
);
}
}
/// 生成缓存键
String _generateCacheKey(
String method,
String endpoint,
Map<String, dynamic>? queryParameters,
) {
final buffer = StringBuffer();
buffer.write(method);
buffer.write('_');
buffer.write(endpoint.replaceAll('/', '_'));
if (queryParameters != null && queryParameters.isNotEmpty) {
final sortedKeys = queryParameters.keys.toList()..sort();
for (final key in sortedKeys) {
buffer.write('_${key}_${queryParameters[key]}');
}
}
return buffer.toString();
}
/// 清除相关缓存
Future<void> _invalidateRelatedCache(
String endpoint,
List<String>? specificKeys,
) async {
try {
// 清除指定的缓存键
if (specificKeys != null) {
for (final key in specificKeys) {
await CacheService.removeCache(key);
}
}
// 清除与当前端点相关的缓存
final endpointKey = endpoint.replaceAll('/', '_');
final keys = StorageService.getKeys();
final relatedKeys = keys.where((key) => key.contains(endpointKey));
for (final key in relatedKeys) {
final cacheKey = key.replaceFirst('api_cache_', '');
await CacheService.removeCache(cacheKey);
}
if (kDebugMode) {
print('Cache invalidated for endpoint: $endpoint');
}
} catch (e) {
if (kDebugMode) {
print('Error invalidating cache: $e');
}
}
}
/// 预加载数据到缓存
Future<void> preloadCache(
String endpoint, {
Map<String, dynamic>? queryParameters,
Duration? cacheDuration,
}) async {
try {
await get(
endpoint,
queryParameters: queryParameters,
useCache: true,
cacheDuration: cacheDuration,
);
if (kDebugMode) {
print('Cache preloaded for: $endpoint');
}
} catch (e) {
if (kDebugMode) {
print('Error preloading cache: $e');
}
}
}
/// 获取缓存信息
Future<Map<String, int>> getCacheInfo() async {
return await CacheService.getCacheInfo();
}
/// 清理过期缓存
Future<void> cleanExpiredCache() async {
await CacheService.cleanExpiredCache();
}
/// 清空所有缓存
Future<void> clearAllCache() async {
await CacheService.clearAllCache();
}
}

View File

@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
/// 全局导航服务
/// 用于在非Widget上下文中进行页面导航
class NavigationService {
static final NavigationService _instance = NavigationService._internal();
factory NavigationService() => _instance;
NavigationService._internal();
static NavigationService get instance => _instance;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
/// 获取当前上下文
BuildContext? get context => navigatorKey.currentContext;
/// 获取Navigator
NavigatorState? get navigator => navigatorKey.currentState;
/// 导航到指定路由
Future<T?>? navigateTo<T>(String routeName, {Object? arguments}) {
return navigator?.pushNamed<T>(routeName, arguments: arguments);
}
/// 替换当前路由
Future<T?>? replaceWith<T extends Object?>(String routeName, {Object? arguments}) {
return navigator?.pushReplacementNamed<T, T>(routeName, arguments: arguments);
}
/// 导航到指定路由并清除所有历史
Future<T?>? navigateToAndClearStack<T extends Object?>(String routeName, {Object? arguments}) {
return navigator?.pushNamedAndRemoveUntil<T>(
routeName,
(route) => false,
arguments: arguments,
);
}
/// 返回上一页
void goBack<T>([T? result]) {
if (navigator?.canPop() ?? false) {
navigator?.pop<T>(result);
}
}
/// 返回到指定路由
void popUntil(String routeName) {
navigator?.popUntil(ModalRoute.withName(routeName));
}
/// 显示SnackBar
void showSnackBar(String message, {
Duration duration = const Duration(seconds: 2),
SnackBarAction? action,
}) {
final scaffoldMessenger = ScaffoldMessenger.of(context!);
// 清除之前的所有SnackBar
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(message),
duration: duration,
action: action,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
/// 显示错误SnackBar
void showErrorSnackBar(String message) {
final scaffoldMessenger = ScaffoldMessenger.of(context!);
// 清除之前的所有SnackBar
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
/// 显示成功SnackBar
void showSuccessSnackBar(String message) {
final scaffoldMessenger = ScaffoldMessenger.of(context!);
// 清除之前的所有SnackBar
scaffoldMessenger.clearSnackBars();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.green,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
/// 显示对话框
Future<T?> showDialogWidget<T>(Widget dialog) {
return showDialog<T>(
context: context!,
builder: (context) => dialog,
);
}
/// 显示底部弹窗
Future<T?> showBottomSheetWidget<T>(Widget bottomSheet) {
return showModalBottomSheet<T>(
context: context!,
builder: (context) => bottomSheet,
);
}
}

View File

@@ -0,0 +1,342 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/exceptions.dart';
/// 存储服务
class StorageService {
static StorageService? _instance;
static SharedPreferences? _prefs;
StorageService._();
/// 获取单例实例
static Future<StorageService> getInstance() async {
if (_instance == null) {
_instance = StorageService._();
await _instance!._init();
}
return _instance!;
}
/// 获取已初始化的实例同步方法必须先调用getInstance
static StorageService get instance {
if (_instance == null) {
throw Exception('StorageService not initialized. Call getInstance() first.');
}
return _instance!;
}
/// 初始化
Future<void> _init() async {
try {
_prefs = await SharedPreferences.getInstance();
} catch (e) {
throw CacheException('初始化存储服务失败: $e');
}
}
// ==================== 普通存储 ====================
/// 存储字符串
Future<bool> setString(String key, String value) async {
try {
return await _prefs!.setString(key, value);
} catch (e) {
throw CacheException('存储字符串失败: $e');
}
}
/// 获取字符串
String? getString(String key, {String? defaultValue}) {
try {
return _prefs!.getString(key) ?? defaultValue;
} catch (e) {
throw CacheException('获取字符串失败: $e');
}
}
/// 存储整数
Future<bool> setInt(String key, int value) async {
try {
return await _prefs!.setInt(key, value);
} catch (e) {
throw CacheException('存储整数失败: $e');
}
}
/// 获取整数
int? getInt(String key, {int? defaultValue}) {
try {
return _prefs!.getInt(key) ?? defaultValue;
} catch (e) {
throw CacheException('获取整数失败: $e');
}
}
/// 存储布尔值
Future<bool> setBool(String key, bool value) async {
try {
return await _prefs!.setBool(key, value);
} catch (e) {
throw CacheException('存储布尔值失败: $e');
}
}
/// 获取布尔值
bool? getBool(String key, {bool? defaultValue}) {
try {
return _prefs!.getBool(key) ?? defaultValue;
} catch (e) {
throw CacheException('获取布尔值失败: $e');
}
}
/// 存储双精度浮点数
Future<bool> setDouble(String key, double value) async {
try {
return await _prefs!.setDouble(key, value);
} catch (e) {
throw CacheException('存储双精度浮点数失败: $e');
}
}
/// 获取双精度浮点数
double? getDouble(String key, {double? defaultValue}) {
try {
return _prefs!.getDouble(key) ?? defaultValue;
} catch (e) {
throw CacheException('获取双精度浮点数失败: $e');
}
}
/// 存储字符串列表
Future<bool> setStringList(String key, List<String> value) async {
try {
return await _prefs!.setStringList(key, value);
} catch (e) {
throw CacheException('存储字符串列表失败: $e');
}
}
/// 获取字符串列表
List<String>? getStringList(String key, {List<String>? defaultValue}) {
try {
return _prefs!.getStringList(key) ?? defaultValue;
} catch (e) {
throw CacheException('获取字符串列表失败: $e');
}
}
/// 存储JSON对象
Future<bool> setJson(String key, Map<String, dynamic> value) async {
try {
final jsonString = jsonEncode(value);
return await setString(key, jsonString);
} catch (e) {
throw CacheException('存储JSON对象失败: $e');
}
}
/// 获取JSON对象
Map<String, dynamic>? getJson(String key) {
try {
final jsonString = getString(key);
if (jsonString == null) return null;
return jsonDecode(jsonString) as Map<String, dynamic>;
} catch (e) {
throw CacheException('获取JSON对象失败: $e');
}
}
/// 删除指定键的数据
Future<bool> remove(String key) async {
try {
return await _prefs!.remove(key);
} catch (e) {
throw CacheException('删除数据失败: $e');
}
}
/// 清空所有数据
Future<bool> clear() async {
try {
return await _prefs!.clear();
} catch (e) {
throw CacheException('清空数据失败: $e');
}
}
/// 检查是否包含指定键
bool containsKey(String key) {
try {
return _prefs!.containsKey(key);
} catch (e) {
throw CacheException('检查键是否存在失败: $e');
}
}
/// 获取所有键
Set<String> getKeys() {
try {
return _prefs!.getKeys();
} catch (e) {
throw CacheException('获取所有键失败: $e');
}
}
// ==================== 安全存储 ====================
/// 安全存储字符串用于敏感信息如Token
Future<void> setSecureString(String key, String value) async {
try {
await _prefs!.setString('secure_$key', value);
} catch (e) {
throw CacheException('安全存储字符串失败: $e');
}
}
/// 安全获取字符串
Future<String?> getSecureString(String key) async {
try {
return _prefs!.getString('secure_$key');
} catch (e) {
throw CacheException('安全获取字符串失败: $e');
}
}
/// 安全存储JSON对象
Future<void> setSecureJson(String key, Map<String, dynamic> value) async {
try {
final jsonString = jsonEncode(value);
await setSecureString(key, jsonString);
} catch (e) {
throw CacheException('安全存储JSON对象失败: $e');
}
}
/// 安全获取JSON对象
Future<Map<String, dynamic>?> getSecureJson(String key) async {
try {
final jsonString = await getSecureString(key);
if (jsonString == null) return null;
return jsonDecode(jsonString) as Map<String, dynamic>;
} catch (e) {
throw CacheException('安全获取JSON对象失败: $e');
}
}
/// 安全删除指定键的数据
Future<void> removeSecure(String key) async {
try {
await _prefs!.remove('secure_$key');
} catch (e) {
throw CacheException('安全删除数据失败: $e');
}
}
/// 安全清空所有数据
Future<void> clearSecure() async {
try {
final keys = _prefs!.getKeys().where((key) => key.startsWith('secure_')).toList();
for (final key in keys) {
await _prefs!.remove(key);
}
} catch (e) {
throw CacheException('安全清空数据失败: $e');
}
}
/// 安全检查是否包含指定键
Future<bool> containsSecureKey(String key) async {
try {
return _prefs!.containsKey('secure_$key');
} catch (e) {
throw CacheException('安全检查键是否存在失败: $e');
}
}
/// 安全获取所有键
Future<Map<String, String>> getAllSecure() async {
try {
final result = <String, String>{};
final keys = _prefs!.getKeys().where((key) => key.startsWith('secure_'));
for (final key in keys) {
final value = _prefs!.getString(key);
if (value != null) {
result[key.substring(7)] = value; // 移除 'secure_' 前缀
}
}
return result;
} catch (e) {
throw CacheException('安全获取所有数据失败: $e');
}
}
// ==================== Token 相关便捷方法 ====================
/// 保存访问令牌
Future<void> saveToken(String token) async {
await setSecureString(StorageKeys.accessToken, token);
}
/// 获取访问令牌
Future<String?> getToken() async {
return await getSecureString(StorageKeys.accessToken);
}
/// 保存刷新令牌
Future<void> saveRefreshToken(String refreshToken) async {
await setSecureString(StorageKeys.refreshToken, refreshToken);
}
/// 获取刷新令牌
Future<String?> getRefreshToken() async {
return await getSecureString(StorageKeys.refreshToken);
}
/// 清除所有令牌
Future<void> clearTokens() async {
await removeSecure(StorageKeys.accessToken);
await removeSecure(StorageKeys.refreshToken);
}
}
/// 存储键常量
class StorageKeys {
// 用户相关
static const String accessToken = 'access_token';
static const String refreshToken = 'refresh_token';
static const String userInfo = 'user_info';
static const String isLoggedIn = 'is_logged_in';
static const String rememberMe = 'remember_me';
// 应用设置
static const String appLanguage = 'app_language';
static const String appTheme = 'app_theme';
static const String firstLaunch = 'first_launch';
static const String onboardingCompleted = 'onboarding_completed';
// 学习设置
static const String dailyGoal = 'daily_goal';
static const String reminderTimes = 'reminder_times';
static const String notificationsEnabled = 'notifications_enabled';
static const String soundEnabled = 'sound_enabled';
static const String vibrationEnabled = 'vibration_enabled';
// 学习数据
static const String learningProgress = 'learning_progress';
static const String vocabularyProgress = 'vocabulary_progress';
static const String listeningProgress = 'listening_progress';
static const String readingProgress = 'reading_progress';
static const String writingProgress = 'writing_progress';
static const String speakingProgress = 'speaking_progress';
// 缓存数据
static const String cachedWordBooks = 'cached_word_books';
static const String cachedArticles = 'cached_articles';
static const String cachedExercises = 'cached_exercises';
// 临时数据
static const String tempData = 'temp_data';
static const String draftData = 'draft_data';
}

View File

@@ -0,0 +1,151 @@
import 'package:flutter_tts/flutter_tts.dart';
/// TTS语音播报服务
class TTSService {
static final TTSService _instance = TTSService._internal();
factory TTSService() => _instance;
TTSService._internal();
final FlutterTts _flutterTts = FlutterTts();
bool _isInitialized = false;
/// 初始化TTS
Future<void> initialize() async {
if (_isInitialized) return;
try {
// 设置语言为美式英语
await _flutterTts.setLanguage("en-US");
// 设置语速 (0.0 - 1.0)
await _flutterTts.setSpeechRate(0.5);
// 设置音量 (0.0 - 1.0)
await _flutterTts.setVolume(1.0);
// 设置音调 (0.5 - 2.0)
await _flutterTts.setPitch(1.0);
_isInitialized = true;
print('TTS服务初始化成功');
} catch (e) {
print('TTS服务初始化失败: $e');
}
}
/// 播放单词发音
/// [word] 要播放的单词
/// [language] 语言代码,默认为美式英语 "en-US",英式英语为 "en-GB"
Future<void> speak(String word, {String language = "en-US"}) async {
if (!_isInitialized) {
await initialize();
}
try {
// 如果正在播放,先停止
await _flutterTts.stop();
// 设置语言
await _flutterTts.setLanguage(language);
// 播放单词
await _flutterTts.speak(word);
print('播放单词发音: $word ($language)');
} catch (e) {
print('播放单词发音失败: $e');
}
}
/// 停止播放
Future<void> stop() async {
try {
await _flutterTts.stop();
} catch (e) {
print('停止播放失败: $e');
}
}
/// 暂停播放
Future<void> pause() async {
try {
await _flutterTts.pause();
} catch (e) {
print('暂停播放失败: $e');
}
}
/// 设置语速
/// [rate] 语速 (0.0 - 1.0)
Future<void> setSpeechRate(double rate) async {
try {
await _flutterTts.setSpeechRate(rate);
} catch (e) {
print('设置语速失败: $e');
}
}
/// 设置音调
/// [pitch] 音调 (0.5 - 2.0)
Future<void> setPitch(double pitch) async {
try {
await _flutterTts.setPitch(pitch);
} catch (e) {
print('设置音调失败: $e');
}
}
/// 设置音量
/// [volume] 音量 (0.0 - 1.0)
Future<void> setVolume(double volume) async {
try {
await _flutterTts.setVolume(volume);
} catch (e) {
print('设置音量失败: $e');
}
}
/// 获取可用语言列表
Future<List<dynamic>> getLanguages() async {
try {
return await _flutterTts.getLanguages;
} catch (e) {
print('获取语言列表失败: $e');
return [];
}
}
/// 获取可用语音列表
Future<List<dynamic>> getVoices() async {
try {
return await _flutterTts.getVoices;
} catch (e) {
print('获取语音列表失败: $e');
return [];
}
}
/// 设置完成回调
void setCompletionHandler(Function callback) {
_flutterTts.setCompletionHandler(() {
callback();
});
}
/// 设置错误回调
void setErrorHandler(Function(dynamic) callback) {
_flutterTts.setErrorHandler((msg) {
callback(msg);
});
}
/// 释放资源
Future<void> dispose() async {
try {
await _flutterTts.stop();
_isInitialized = false;
} catch (e) {
print('释放TTS资源失败: $e');
}
}
}