This commit is contained in:
sjk
2025-11-17 13:39:05 +08:00
commit d4cfe2b9de
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,331 @@
import '../../../core/models/api_response.dart';
import '../../../core/services/enhanced_api_service.dart';
import '../models/speaking_scenario.dart';
import '../models/conversation.dart';
import '../models/speaking_stats.dart';
import '../models/pronunciation_assessment.dart';
import '../models/pronunciation_item.dart';
/// 口语训练服务
class SpeakingService {
static final SpeakingService _instance = SpeakingService._internal();
factory SpeakingService() => _instance;
SpeakingService._internal();
final EnhancedApiService _enhancedApiService = EnhancedApiService();
// 缓存时长配置
static const Duration _shortCacheDuration = Duration(minutes: 5);
static const Duration _longCacheDuration = Duration(hours: 1);
/// 获取口语场景列表
Future<ApiResponse<List<SpeakingTask>>> getSpeakingScenarios({
SpeakingScenario? scenario,
SpeakingDifficulty? difficulty,
int page = 1,
int limit = 20,
}) async {
try {
final response = await _enhancedApiService.get<List<SpeakingTask>>(
'/speaking/scenarios',
queryParameters: {
'page': page,
'page_size': limit,
if (scenario != null) 'category': scenario.name,
if (difficulty != null) 'level': difficulty.name,
},
cacheDuration: _shortCacheDuration,
fromJson: (data) {
final scenarios = data['scenarios'] as List?;
if (scenarios == null) return [];
return scenarios.map((json) => SpeakingTask.fromJson(json)).toList();
},
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取口语场景失败: $e');
}
}
/// 获取单个口语场景详情
Future<ApiResponse<SpeakingTask>> getSpeakingScenario(String scenarioId) async {
try {
final response = await _enhancedApiService.get<SpeakingTask>(
'/speaking/scenarios/$scenarioId',
cacheDuration: _longCacheDuration,
fromJson: (data) => SpeakingTask.fromJson(data['data'] ?? data),
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取口语场景详情失败: $e');
}
}
/// 开始口语对话
Future<ApiResponse<Conversation>> startConversation(String scenarioId) async {
try {
final response = await _enhancedApiService.post<Conversation>(
'/speaking/records',
data: {'scenario_id': scenarioId},
fromJson: (data) => Conversation.fromJson(data['data'] ?? data),
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '对话开始成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '开始对话失败: $e');
}
}
/// 获取用户口语历史
Future<ApiResponse<List<Conversation>>> getUserSpeakingHistory({
int page = 1,
int limit = 20,
}) async {
try {
final response = await _enhancedApiService.get<List<Conversation>>(
'/speaking/records',
queryParameters: {
'page': page,
'page_size': limit,
},
cacheDuration: _shortCacheDuration,
fromJson: (data) {
final records = data['records'] as List?;
if (records == null) return [];
return records.map((json) => Conversation.fromJson(json)).toList();
},
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取历史记录成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取历史记录失败: $e');
}
}
/// 获取用户口语统计
Future<ApiResponse<SpeakingStats>> getUserSpeakingStatistics() async {
try {
final response = await _enhancedApiService.get<SpeakingStats>(
'/speaking/stats',
cacheDuration: _shortCacheDuration,
fromJson: (data) => SpeakingStats.fromJson(data['data'] ?? data),
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取统计数据成功', data: response.data!);
}
// API失败时返回默认统计数据
final stats = SpeakingStats(
totalSessions: 45,
totalMinutes: 320,
averageScore: 85.5,
scenarioStats: {
SpeakingScenario.dailyConversation: 8,
SpeakingScenario.businessMeeting: 5,
SpeakingScenario.jobInterview: 3,
SpeakingScenario.shopping: 4,
SpeakingScenario.restaurant: 3,
SpeakingScenario.travel: 2,
SpeakingScenario.academic: 2,
SpeakingScenario.socializing: 1,
},
difficultyStats: {
SpeakingDifficulty.beginner: 10,
SpeakingDifficulty.intermediate: 15,
SpeakingDifficulty.advanced: 8,
},
progressData: [],
skillAnalysis: SpeakingSkillAnalysis(
criteriaScores: {
PronunciationCriteria.accuracy: 85.0,
PronunciationCriteria.fluency: 82.0,
PronunciationCriteria.completeness: 88.0,
PronunciationCriteria.prosody: 80.0,
},
commonErrors: {},
strengths: ['发音准确'],
weaknesses: ['语调需要改进'],
recommendations: ['多练习语调'],
improvementRate: 0.05,
lastAnalyzed: DateTime.now(),
),
lastUpdated: DateTime.now(),
);
return ApiResponse.success(message: '获取统计数据成功', data: stats);
} catch (e) {
return ApiResponse.error(message: '获取统计数据失败: $e');
}
}
/// 获取推荐任务
Future<ApiResponse<List<SpeakingTask>>> getRecommendedTasks() async {
try {
final response = await _enhancedApiService.get<List<SpeakingTask>>(
'/speaking/scenarios/recommendations',
cacheDuration: _shortCacheDuration,
fromJson: (data) {
final scenarios = data['scenarios'] as List?;
if (scenarios == null) return [];
return scenarios.map((json) => SpeakingTask.fromJson(json)).toList();
},
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取推荐任务成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取推荐任务失败: $e');
}
}
/// 获取热门任务
Future<ApiResponse<List<SpeakingTask>>> getPopularTasks() async {
try {
// 热门任务可以通过获取场景列表并按某种排序获得
final response = await _enhancedApiService.get<List<SpeakingTask>>(
'/speaking/scenarios',
queryParameters: {
'page': 1,
'page_size': 10,
'sort': 'popular', // 如果后端支持排序
},
cacheDuration: _shortCacheDuration,
fromJson: (data) {
final scenarios = data['scenarios'] as List?;
if (scenarios == null) return [];
return scenarios.map((json) => SpeakingTask.fromJson(json)).toList();
},
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取热门任务成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取热门任务失败: $e');
}
}
/// 获取发音练习项目(通过词汇后端数据映射)
Future<ApiResponse<List<PronunciationItem>>> getPronunciationItems(
PronunciationType type, {
int limit = 50,
}) async {
try {
final response = await _enhancedApiService.get<List<PronunciationItem>>(
'/vocabulary/study/today',
queryParameters: {
'limit': limit,
},
cacheDuration: _shortCacheDuration,
fromJson: (data) {
dynamic root = data;
if (root is Map && root.containsKey('data')) {
root = root['data'];
}
List<dynamic> list = const [];
if (root is List) {
list = root;
} else if (root is Map) {
final words = root['words'];
if (words is List) {
list = words;
}
}
final items = <PronunciationItem>[];
for (final w in list) {
final id = w['id'].toString();
final level = (w['level'] ?? '').toString();
final audioUrl = (w['audio_url'] ?? w['audio_us_url'] ?? w['audio_uk_url'] ?? '').toString();
final phonetic = (w['phonetic'] ?? w['phonetic_us'] ?? w['phonetic_uk'] ?? '').toString();
final createdAtStr = (w['created_at'] ?? DateTime.now().toIso8601String()).toString();
final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
DifficultyLevel mapLevel(String l) {
switch (l) {
case 'beginner':
case 'elementary':
return DifficultyLevel.beginner;
case 'intermediate':
return DifficultyLevel.intermediate;
case 'advanced':
case 'expert':
return DifficultyLevel.advanced;
default:
return DifficultyLevel.intermediate;
}
}
if (type == PronunciationType.word) {
items.add(PronunciationItem(
id: id,
text: (w['word'] ?? '').toString(),
phonetic: phonetic,
audioUrl: audioUrl,
type: PronunciationType.word,
difficulty: mapLevel(level),
category: level.isEmpty ? '词汇练习' : level,
tips: const [],
createdAt: createdAt,
));
} else if (type == PronunciationType.sentence) {
final examples = (w['examples'] as List<dynamic>?) ?? [];
for (var i = 0; i < examples.length; i++) {
final ex = examples[i] as Map<String, dynamic>;
final exAudio = (ex['audio_url'] ?? '').toString();
final exCreated = DateTime.tryParse((ex['created_at'] ?? createdAtStr).toString()) ?? createdAt;
items.add(PronunciationItem(
id: '${id}_ex_$i',
text: (ex['sentence'] ?? ex['sentence_en'] ?? '').toString(),
phonetic: '',
audioUrl: exAudio,
type: PronunciationType.sentence,
difficulty: mapLevel(level),
category: '例句',
tips: const [],
createdAt: exCreated,
));
}
}
}
return items;
},
);
if (response.success && response.data != null) {
return ApiResponse.success(message: '获取发音练习成功', data: response.data!);
} else {
return ApiResponse.error(message: response.message);
}
} catch (e) {
return ApiResponse.error(message: '获取发音练习失败: $e');
}
}
}