Initial commit: Flutter 无书应用项目

This commit is contained in:
Developer
2026-03-30 02:35:31 +08:00
commit 9175ff9905
566 changed files with 103261 additions and 0 deletions

View File

@@ -0,0 +1,613 @@
import 'dart:convert';
import 'dart:math';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../utils/http/http_client.dart';
import '../../../services/network_listener_service.dart';
/// 时间: 2026-03-28
/// 功能: 诗词答题逻辑管理器
/// 介绍: 处理诗词答题的网络请求和业务逻辑
/// 最新变化: 添加离线模式支持,离线时从本地缓存加载题目
class PoetryLevelManager with NetworkListenerMixin {
int _currentIndex = 0;
int _total = 0;
List<int> _shuffledIds = [];
final Random _random = Random();
// 离线模式相关
bool _isOfflineMode = false;
List<Map<String, dynamic>> _offlineQuestions = [];
bool _isOnline = true;
/// 获取当前题目 ID
int get currentId =>
_shuffledIds.isNotEmpty ? _shuffledIds[_currentIndex] : 0;
/// 获取当前题目索引
int get currentIndex => _currentIndex;
/// 获取题目总数
int get total => _total;
/// 是否已初始化
bool get isInitialized =>
_shuffledIds.isNotEmpty || _offlineQuestions.isNotEmpty;
/// 是否为离线模式
bool get isOfflineMode => _isOfflineMode;
/// 检查网络状态
Future<bool> _checkNetworkStatus() async {
try {
final networkStatus = NetworkListenerService().currentStatus;
return networkStatus != NetworkStatus.error;
} catch (e) {
return false;
}
}
/// 检查是否为离线状态(用户设置)
Future<bool> _checkOfflineSetting() async {
try {
final prefs = await SharedPreferences.getInstance();
return !(prefs.getBool('personal_card_online') ?? true);
} catch (e) {
return false;
}
}
/// 检查是否有缓存数据
Future<bool> _hasCachedData() async {
try {
final prefs = await SharedPreferences.getInstance();
final quizData = prefs.getStringList('offline_quiz_data') ?? [];
return quizData.isNotEmpty;
} catch (e) {
return false;
}
}
/// 加载离线缓存数据
Future<void> _loadOfflineCache() async {
try {
final prefs = await SharedPreferences.getInstance();
final quizData = prefs.getStringList('offline_quiz_data') ?? [];
_offlineQuestions = [];
for (final item in quizData) {
try {
final map = _parseStringToMap(item);
if (map.isNotEmpty) {
_offlineQuestions.add(map);
}
} catch (e) {
print('解析离线数据失败: $e');
}
}
if (_offlineQuestions.isNotEmpty) {
_total = _offlineQuestions.length;
_shuffledIds = List.generate(_total, (index) => index);
_shuffleList(_shuffledIds);
_currentIndex = 0;
}
} catch (e) {
print('加载离线缓存失败: $e');
}
}
/// 解析字符串为Map
Map<String, dynamic> _parseStringToMap(String str) {
final result = <String, dynamic>{};
print('开始解析字符串: $str');
try {
// 尝试JSON解析
final jsonMap = jsonDecode(str);
if (jsonMap is Map<String, dynamic>) {
print('JSON解析成功: $jsonMap');
return jsonMap;
}
} catch (e) {
print('JSON解析失败: $e');
// 不是JSON格式尝试其他解析方式
}
// 尝试解析 Map 字符串格式
if (str.startsWith('{') && str.endsWith('}')) {
final content = str.substring(1, str.length - 1);
final pairs = content.split(', ');
for (final pair in pairs) {
final colonIndex = pair.indexOf(': ');
if (colonIndex > 0) {
final key = pair.substring(0, colonIndex).replaceAll('"', '');
var value = pair.substring(colonIndex + 2).replaceAll('"', '');
// 尝试转换数字
if (int.tryParse(value) != null) {
result[key] = int.parse(value);
} else if (double.tryParse(value) != null) {
result[key] = double.parse(value);
} else if (value == 'true') {
result[key] = true;
} else if (value == 'false') {
result[key] = false;
} else {
result[key] = value;
}
}
}
}
print('解析结果: $result');
return result;
}
/// 初始化题目列表(打乱顺序)
Future<PoetryInitResult> initializeQuestions() async {
try {
// 检查网络状态
_isOnline = await _checkNetworkStatus();
final isOfflineSetting = await _checkOfflineSetting();
// 判断是否使用离线模式
if (!_isOnline || isOfflineSetting) {
_isOfflineMode = true;
// 检查是否有缓存数据
final hasCache = await _hasCachedData();
if (!hasCache) {
return PoetryInitResult(
success: false,
message: '离线模式下无缓存数据,请先下载或检查网络连接',
needDownload: true,
);
}
// 加载离线缓存
await _loadOfflineCache();
if (_offlineQuestions.isEmpty) {
return PoetryInitResult(
success: false,
message: '离线缓存数据为空,请先下载数据',
needDownload: true,
);
}
return PoetryInitResult(
success: true,
message: '已加载离线缓存 ${_offlineQuestions.length} 条题目',
isOffline: true,
);
}
// 在线模式
_isOfflineMode = false;
// 先调用 fetch 获取新题
final fetchResponse = await HttpClient.get(
'poe/api.php',
queryParameters: {'action': 'fetch'},
);
if (fetchResponse.isSuccess) {
final fetchData = fetchResponse.jsonData;
if (fetchData['code'] == 0) {
_total = fetchData['data']['total'] ?? 0;
}
}
// 再调用 refresh 刷新缓存
final refreshResponse = await HttpClient.get(
'poe/api.php',
queryParameters: {'action': 'refresh'},
);
if (refreshResponse.isSuccess) {
final refreshData = refreshResponse.jsonData;
if (refreshData['code'] == 0) {
_total = refreshData['data']['total'] ?? _total;
}
}
// 生成并打乱题目 ID 列表
_generateShuffledIds();
_currentIndex = 0;
return PoetryInitResult(
success: true,
message: '已加载在线题库 $_total 条题目',
isOffline: false,
);
} catch (e) {
print('初始化题目失败: $e');
// 尝试加载离线缓存
final hasCache = await _hasCachedData();
if (hasCache) {
_isOfflineMode = true;
await _loadOfflineCache();
if (_offlineQuestions.isNotEmpty) {
return PoetryInitResult(
success: true,
message: '网络异常,已加载离线缓存 ${_offlineQuestions.length} 条题目',
isOffline: true,
);
}
}
return PoetryInitResult(
success: false,
message: '初始化题目失败: $e',
needDownload: !hasCache,
);
}
}
/// 生成并打乱题目 ID 列表
void _generateShuffledIds() {
_shuffledIds = List.generate(_total, (index) => index);
_shuffleList(_shuffledIds);
}
/// 打乱列表顺序Fisher-Yates 算法)
void _shuffleList(List<int> list) {
for (int i = list.length - 1; i > 0; i--) {
int j = _random.nextInt(i + 1);
int temp = list[i];
list[i] = list[j];
list[j] = temp;
}
}
/// 下一题
void nextQuestion() {
if (_shuffledIds.isEmpty && _offlineQuestions.isEmpty) return;
_currentIndex++;
if (_currentIndex >= _total) {
_currentIndex = 0;
// 循环时重新打乱顺序
if (_shuffledIds.isNotEmpty) {
_shuffleList(_shuffledIds);
}
}
}
/// 上一题
void previousQuestion() {
if (_shuffledIds.isEmpty && _offlineQuestions.isEmpty) return;
_currentIndex--;
if (_currentIndex < 0) {
_currentIndex = _total - 1;
}
}
/// 加载题目
Future<PoetryQuestionResult> loadQuestion() async {
if (isNetworkLoading('load_question')) {
return PoetryQuestionResult(success: false, message: '正在加载中...');
}
if (_shuffledIds.isEmpty && _offlineQuestions.isEmpty) {
return PoetryQuestionResult(success: false, message: '题目列表未初始化');
}
// 离线模式:从缓存加载
if (_isOfflineMode && _offlineQuestions.isNotEmpty) {
final questionIndex = _shuffledIds[_currentIndex];
if (questionIndex >= 0 && questionIndex < _offlineQuestions.length) {
final questionData = _offlineQuestions[questionIndex];
// 构建标准格式的题目数据
final formattedData = _formatOfflineQuestion(questionData);
return PoetryQuestionResult(
success: true,
data: formattedData,
questionId: questionIndex,
questionIndex: _currentIndex,
isOffline: true,
);
}
return PoetryQuestionResult(success: false, message: '题目索引越界');
}
startNetworkLoading('load_question');
try {
final id = currentId;
final response = await HttpClient.get(
'poe/api.php',
queryParameters: {'action': 'question', 'id': id.toString()},
);
if (response.isSuccess) {
final data = response.jsonData;
if (data['code'] == 0) {
final questionData = data['data'];
return PoetryQuestionResult(
success: true,
data: questionData,
questionId: id,
questionIndex: _currentIndex,
isOffline: false,
);
} else {
return PoetryQuestionResult(
success: false,
message: data['msg'] ?? '获取题目失败',
);
}
} else {
return PoetryQuestionResult(
success: false,
message: '网络错误: ${response.statusCode}',
);
}
} catch (e) {
return PoetryQuestionResult(success: false, message: '加载失败: $e');
} finally {
endNetworkLoading('load_question');
}
}
/// 格式化离线题目数据
Map<String, dynamic> _formatOfflineQuestion(Map<String, dynamic> data) {
print('格式化离线题目,原始数据: $data');
// 检查是否已经是标准格式
if (data.containsKey('question') && data.containsKey('options')) {
final options = data['options'];
print('发现options字段类型: ${options.runtimeType}, 值: $options');
// 确保options是List类型
if (options is List) {
print('options已经是List类型直接返回');
return data;
}
}
// 尝试从缓存数据中提取字段
final result = <String, dynamic>{
'id': data['id'] ?? 0,
'question': data['question'] ?? data['question_content'] ?? '未知题目',
'author': data['author'] ?? '未知作者',
'type': data['type'] ?? '',
'grade': data['grade'] ?? '',
'dynasty': data['dynasty'] ?? '',
'options': <Map<String, dynamic>>[],
};
print('构建基础数据: $result');
// 尝试解析options字段
dynamic optionsData = data['options'];
if (optionsData != null) {
print('开始解析options类型: ${optionsData.runtimeType}');
if (optionsData is List) {
// 已经是List直接使用
result['options'] = optionsData;
print('options是List直接使用');
} else if (optionsData is String) {
// 是String尝试解析为List
try {
print('options是String尝试解析: $optionsData');
final parsedOptions = jsonDecode(optionsData);
print('解析结果类型: ${parsedOptions.runtimeType}');
if (parsedOptions is List) {
result['options'] = parsedOptions;
print('options解析成功为List');
}
} catch (e) {
print('解析options字符串失败: $e');
}
}
}
print('当前options: ${result['options']}');
// 如果没有有效的options尝试从其他字段构建
if ((result['options'] as List).isEmpty) {
print('options为空尝试从其他字段构建');
final options = <Map<String, dynamic>>[];
for (int i = 1; i <= 4; i++) {
final optionKey = 'option_$i';
if (data.containsKey(optionKey)) {
options.add({'index': i, 'content': data[optionKey]});
print('$optionKey构建选项');
}
}
result['options'] = options;
}
print('最终格式化结果: $result');
return result;
}
/// 提交答案
Future<PoetryAnswerResult> submitAnswer(int questionId, int answer) async {
// 离线模式:本地验证答案
if (_isOfflineMode && _offlineQuestions.isNotEmpty) {
if (questionId >= 0 && questionId < _offlineQuestions.length) {
final questionData = _offlineQuestions[questionId];
final correctAnswer =
questionData['correct_answer'] ??
questionData['answer'] ??
questionData['correct'];
// 比较答案
bool isCorrect = false;
if (correctAnswer != null) {
if (correctAnswer is int) {
isCorrect = correctAnswer == answer;
} else if (correctAnswer is String) {
isCorrect = correctAnswer == answer.toString();
}
}
return PoetryAnswerResult(
success: true,
message: isCorrect ? '回答正确!' : '回答错误',
isCorrect: isCorrect,
nextQuestion: null,
);
}
return PoetryAnswerResult(success: false, message: '题目不存在');
}
startNetworkLoading('submit_answer');
try {
final response = await HttpClient.get(
'poe/api.php',
queryParameters: {
'action': 'answer',
'id': questionId.toString(),
'answer': answer.toString(),
},
);
if (response.isSuccess) {
final data = response.jsonData;
if (data['code'] == 0) {
return PoetryAnswerResult(
success: true,
message: data['msg'],
isCorrect: data['data']['correct'] == true,
nextQuestion: data['data']['has_next'] == true
? {'id': data['data']['next_id']}
: null,
);
} else {
return PoetryAnswerResult(
success: false,
message: data['msg'] ?? '提交失败',
);
}
} else {
return PoetryAnswerResult(
success: false,
message: '网络错误: ${response.statusCode}',
);
}
} catch (e) {
return PoetryAnswerResult(success: false, message: '提交失败: $e');
} finally {
endNetworkLoading('submit_answer');
}
}
/// 获取提示
Future<PoetryHintResult> getHint(int questionId) async {
// 离线模式:从缓存数据获取提示
if (_isOfflineMode && _offlineQuestions.isNotEmpty) {
if (questionId >= 0 && questionId < _offlineQuestions.length) {
final questionData = _offlineQuestions[questionId];
final hint =
questionData['hint'] ??
'作者: ${questionData['author'] ?? '未知'},朝代: ${questionData['dynasty'] ?? '未知'}';
return PoetryHintResult(success: true, message: hint);
}
return PoetryHintResult(success: false, message: '题目不存在');
}
startNetworkLoading('get_hint');
try {
final response = await HttpClient.get(
'poe/api.php',
queryParameters: {'action': 'hint', 'id': questionId.toString()},
);
if (response.isSuccess) {
final data = response.jsonData;
if (data['code'] == 0) {
return PoetryHintResult(success: true, message: data['data']['hint']);
} else {
return PoetryHintResult(
success: false,
message: data['msg'] ?? '获取提示失败',
);
}
} else {
return PoetryHintResult(
success: false,
message: '网络错误: ${response.statusCode}',
);
}
} catch (e) {
return PoetryHintResult(success: false, message: '获取提示失败: $e');
} finally {
endNetworkLoading('get_hint');
}
}
}
/// 初始化结果
class PoetryInitResult {
final bool success;
final String? message;
final bool isOffline;
final bool needDownload;
PoetryInitResult({
required this.success,
this.message,
this.isOffline = false,
this.needDownload = false,
});
}
/// 题目加载结果
class PoetryQuestionResult {
final bool success;
final String? message;
final Map<String, dynamic>? data;
final int questionId;
final int questionIndex;
final bool isOffline;
PoetryQuestionResult({
required this.success,
this.message,
this.data,
this.questionId = 0,
this.questionIndex = 0,
this.isOffline = false,
});
}
/// 答案提交结果
class PoetryAnswerResult {
final bool success;
final String? message;
final bool isCorrect;
final Map<String, dynamic>? nextQuestion;
PoetryAnswerResult({
required this.success,
this.message,
this.isCorrect = false,
this.nextQuestion,
});
}
/// 提示获取结果
class PoetryHintResult {
final bool success;
final String? message;
PoetryHintResult({required this.success, this.message});
}