import 'package:dio/dio.dart'; import '../../core/network/api_client.dart'; import '../../core/constants/app_constants.dart'; import '../../core/errors/app_error.dart'; import '../models/api_response.dart'; import '../models/vocabulary_model.dart'; /// 词汇服务 class VocabularyService { static final VocabularyService _instance = VocabularyService._internal(); factory VocabularyService() => _instance; VocabularyService._internal(); final ApiClient _apiClient = ApiClient.instance; /// 获取词库列表 Future>> getVocabularyBooks({ String? category, String? level, int page = 1, int limit = 20, }) async { try { final queryParams = { 'page': page, 'limit': limit, }; if (category != null) queryParams['category'] = category; if (level != null) queryParams['level'] = level; final response = await _apiClient.get( '/vocabulary/books', queryParameters: queryParams, ); if (response.statusCode == 200) { final List booksJson = response.data['data']['items']; final books = booksJson .map((json) => VocabularyBookModel.fromJson(json)) .toList(); return ApiResponse.success( message: 'Vocabulary books retrieved successfully', data: books, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get vocabulary books', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get vocabulary books: $e', error: e.toString(), ); } } /// 获取词汇列表 Future>> getVocabularies({ String? bookId, String? search, String? level, int page = 1, int limit = 20, }) async { try { final queryParams = { 'page': page, 'limit': limit, }; if (bookId != null) queryParams['book_id'] = bookId; if (search != null) queryParams['search'] = search; if (level != null) queryParams['level'] = level; final response = await _apiClient.get( '/vocabulary/words', queryParameters: queryParams, ); if (response.statusCode == 200) { final paginatedResponse = PaginatedResponse.fromJson( response.data['data'], (json) => VocabularyModel.fromJson(json), ); return ApiResponse.success( message: 'Vocabularies retrieved successfully', data: paginatedResponse, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get vocabularies', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get vocabularies: $e', error: e.toString(), ); } } /// 获取单词详情 Future> getWordDetail(String wordId) async { try { final response = await _apiClient.get('/vocabulary/words/$wordId'); if (response.statusCode == 200) { final word = VocabularyModel.fromJson(response.data['data']); return ApiResponse.success( message: 'Word detail retrieved successfully', data: word, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get word detail', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get word detail: $e', error: e.toString(), ); } } /// 获取用户词汇学习记录 Future>> getUserVocabularies({ LearningStatus? status, String? bookId, int page = 1, int limit = 20, }) async { try { final queryParams = { 'page': page, 'limit': limit, }; if (status != null) queryParams['status'] = status.name; if (bookId != null) queryParams['book_id'] = bookId; final response = await _apiClient.get( '/vocabulary/user-words', queryParameters: queryParams, ); if (response.statusCode == 200) { final paginatedResponse = PaginatedResponse.fromJson( response.data['data'], (json) => UserVocabularyModel.fromJson(json), ); return ApiResponse.success( message: 'User vocabularies retrieved successfully', data: paginatedResponse, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get user vocabularies', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get user vocabularies: $e', error: e.toString(), ); } } /// 更新单词学习状态 Future> updateWordStatus({ required String wordId, required LearningStatus status, int? correctCount, int? wrongCount, int? reviewCount, DateTime? nextReviewDate, Map? metadata, }) async { try { final data = { 'status': status.name, }; if (correctCount != null) data['correct_count'] = correctCount; if (wrongCount != null) data['wrong_count'] = wrongCount; if (reviewCount != null) data['review_count'] = reviewCount; if (nextReviewDate != null) { data['next_review_date'] = nextReviewDate.toIso8601String(); } if (metadata != null) data['metadata'] = metadata; final response = await _apiClient.put( '/vocabulary/user-words/$wordId', data: data, ); if (response.statusCode == 200) { final userWord = UserVocabularyModel.fromJson(response.data['data']); return ApiResponse.success( message: response.data['message'] ?? 'Word status updated successfully', data: userWord, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to update word status', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to update word status: $e', error: e.toString(), ); } } /// 添加单词到学习列表 Future> addWordToLearning(String wordId) async { try { final response = await _apiClient.post( '/vocabulary/user-words', data: {'word_id': wordId}, ); if (response.statusCode == 201) { final userWord = UserVocabularyModel.fromJson(response.data['data']); return ApiResponse.success( message: response.data['message'] ?? 'Word added to learning list', data: userWord, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to add word to learning list', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to add word to learning list: $e', error: e.toString(), ); } } /// 从学习列表移除单词 Future> removeWordFromLearning(String wordId) async { try { final response = await _apiClient.delete('/vocabulary/user-words/$wordId'); if (response.statusCode == 200) { return ApiResponse.success( message: response.data['message'] ?? 'Word removed from learning list', ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to remove word from learning list', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to remove word from learning list: $e', error: e.toString(), ); } } /// 获取今日复习单词 Future>> getTodayReviewWords() async { try { final response = await _apiClient.get('/vocabulary/today-review'); if (response.statusCode == 200) { final List wordsJson = response.data['data']; final words = wordsJson .map((json) => UserVocabularyModel.fromJson(json)) .toList(); return ApiResponse.success( message: 'Today review words retrieved successfully', data: words, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get today review words', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get today review words: $e', error: e.toString(), ); } } /// 获取新单词学习 Future>> getNewWordsForLearning({ String? bookId, int limit = 10, }) async { try { final queryParams = { 'limit': limit, }; if (bookId != null) queryParams['book_id'] = bookId; final response = await _apiClient.get( '/vocabulary/new-words', queryParameters: queryParams, ); if (response.statusCode == 200) { final List wordsJson = response.data['data']; final words = wordsJson .map((json) => VocabularyModel.fromJson(json)) .toList(); return ApiResponse.success( message: 'New words for learning retrieved successfully', data: words, ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get new words for learning', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get new words for learning: $e', error: e.toString(), ); } } /// 词汇量测试 Future>> vocabularyTest({ required List wordIds, required List answers, }) async { try { final response = await _apiClient.post( '/vocabulary/test', data: { 'word_ids': wordIds, 'answers': answers, }, ); if (response.statusCode == 200) { return ApiResponse.success( message: 'Vocabulary test completed successfully', data: response.data['data'], ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Vocabulary test failed', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Vocabulary test failed: $e', error: e.toString(), ); } } /// 获取学习统计 Future>> getLearningStats({ DateTime? startDate, DateTime? endDate, }) async { try { final queryParams = {}; if (startDate != null) { queryParams['start_date'] = startDate.toIso8601String(); } if (endDate != null) { queryParams['end_date'] = endDate.toIso8601String(); } final response = await _apiClient.get( '/vocabulary/stats', queryParameters: queryParams, ); if (response.statusCode == 200) { return ApiResponse.success( message: 'Learning stats retrieved successfully', data: response.data['data'], ); } else { return ApiResponse.failure( message: response.data['message'] ?? 'Failed to get learning stats', code: response.statusCode, ); } } on DioException catch (e) { return _handleDioError(e); } catch (e) { return ApiResponse.failure( message: 'Failed to get learning stats: $e', error: e.toString(), ); } } /// 处理Dio错误 ApiResponse _handleDioError(DioException e) { switch (e.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: return ApiResponse.failure( message: '请求超时,请检查网络连接', error: 'TIMEOUT', ); case DioExceptionType.badResponse: final statusCode = e.response?.statusCode; final message = e.response?.data?['message'] ?? '请求失败'; return ApiResponse.failure( message: message, code: statusCode, error: 'BAD_RESPONSE', ); case DioExceptionType.cancel: return ApiResponse.failure( message: '请求已取消', error: 'CANCELLED', ); case DioExceptionType.connectionError: return ApiResponse.failure( message: '网络连接失败,请检查网络设置', error: 'CONNECTION_ERROR', ); default: return ApiResponse.failure( message: '未知错误:${e.message}', error: 'UNKNOWN', ); } } }