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,806 @@
/// 时间: 2025-03-22
/// 功能: 诗词主页(参考微信小程序布局)
/// 介绍: 展示诗词内容支持点赞、收藏、分享等功能参考wxpm小程序的index页面布局
/// 最新变化: 2025-03-22 重构代码结构,使用组件化架构简化代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../constants/app_constants.dart';
import '../../../controllers/history_controller.dart';
import '../../../utils/http/poetry_api.dart';
import '../../../services/network_listener_service.dart';
import 'home_part.dart';
import 'home_components.dart';
import 'home-load.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with TickerProviderStateMixin, NetworkListenerMixin {
PoetryData? _poetryData;
List<String> _keywordList = [];
bool _loading = false;
bool _isLiked = false;
bool _isLoadingLike = false;
String _errorMessage = '';
String _starDisplay = '';
final String _historyKey = 'poetry_history';
List<Map<String, dynamic>> _historyList = [];
int _currentHistoryIndex = -1;
late AnimationController _fadeController;
late AnimationController _slideController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
// 动态加载状态
bool _isLoadingNext = false;
bool _isLoadingPrevious = false;
Map<String, bool> _sectionLoadingStates = {
'title': false,
'content': false,
'name': false,
'keywords': false,
'introduction': false,
};
@override
void initState() {
super.initState();
_initAnimations();
_loadHistory();
_initAutoRefresh();
_initDebugInfo();
_initOfflineDataManager();
// 延迟加载诗词,确保页面先显示
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadPoetry();
});
}
Future<void> _initOfflineDataManager() async {
final offlineDataManager = OfflineDataManager();
await offlineDataManager.init();
}
Future<void> _initAutoRefresh() async {
final autoRefreshManager = AutoRefreshManager();
await autoRefreshManager.init();
autoRefreshManager.setOnRefresh(() {
if (mounted) {
_loadNextPoetry();
}
});
if (autoRefreshManager.isEnabled) {
autoRefreshManager.setEnabled(true);
}
}
Future<void> _initDebugInfo() async {
final debugInfoManager = DebugInfoManager();
await debugInfoManager.init();
}
void _initAnimations() {
_fadeController = AnimationUtils.createFadeController(this);
_slideController = AnimationUtils.createSlideController(this);
_fadeAnimation = AnimationUtils.createFadeAnimation(_fadeController);
_slideAnimation = AnimationUtils.createSlideAnimation(_slideController);
}
@override
void dispose() {
_fadeController.dispose();
_slideController.dispose();
// 只停止定时器,不释放单例资源
AutoRefreshManager().stopTimer();
// 不调用DebugInfoManager的dispose因为它是单例其他页面可能还在使用
super.dispose();
}
Future<void> _loadPoetry() async {
if (_loading) return;
setState(() => _loading = true);
PoetryStateManager.triggerHapticFeedback();
try {
final offlineDataManager = OfflineDataManager();
final isOnline = await offlineDataManager.isOnline();
final hasCachedData = await offlineDataManager.hasCachedData();
if (isOnline) {
// 在线状态:从网络加载
final response = await PoetryApi.getRandomPoetry();
if (mounted && response.data != null) {
setState(() {
_poetryData = response.data;
_keywordList = PoetryDataUtils.extractKeywords(response.data);
_starDisplay = PoetryDataUtils.getStarDisplay(response.data);
_isLiked = false;
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
_checkIfLiked();
await _saveToHistory(response.data!);
DebugInfoManager().showRefreshSuccess();
} else {
// 数据为空时,显示默认内容
if (mounted) {
setState(() {
_poetryData = _createDefaultPoetryData();
_keywordList = [];
_starDisplay = '⭐⭐⭐⭐⭐';
_isLiked = false;
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
}
DebugInfoManager().showRefreshFailed();
}
} else {
// 离线状态:从本地缓存加载
if (hasCachedData) {
final poetryData = await offlineDataManager.getNextPoetry();
if (mounted && poetryData != null) {
setState(() {
_poetryData = poetryData;
_keywordList = PoetryDataUtils.extractKeywords(poetryData);
_starDisplay = PoetryDataUtils.getStarDisplay(poetryData);
_isLiked = false;
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
_checkIfLiked();
DebugInfoManager().showRefreshSuccess();
} else {
// 缓存为空时,显示错误信息
if (mounted) {
setState(() {
_loading = false;
_errorMessage = '离线模式下无缓存数据,请先在线下载';
});
}
DebugInfoManager().showRefreshFailed();
}
} else {
// 离线且无缓存时,显示错误信息
if (mounted) {
setState(() {
_loading = false;
_errorMessage = '离线模式下无缓存数据,请先在线下载';
});
}
DebugInfoManager().showRefreshFailed();
}
}
} catch (e) {
debugPrint('加载诗词失败: $e');
if (mounted) {
setState(() {
_loading = false;
_errorMessage = '加载失败,请检查网络连接';
});
}
DebugInfoManager().showRefreshFailed();
}
}
// 创建默认诗词数据,确保页面始终有内容显示
PoetryData _createDefaultPoetryData() {
final now = DateTime.now();
final dateStr = now.toString().substring(0, 10);
final monthStr = dateStr.substring(0, 7);
final timeStr = now.toString().substring(11, 19);
return PoetryData(
id: 1,
name: '静夜思',
alias: '李白',
keywords: '思乡,月亮,静夜',
introduce: '床前明月光,疑是地上霜。举头望明月,低头思故乡。',
drtime: '唐·李白《静夜思》',
like: 0,
url: '李白-静夜思',
tui: 1,
star: 5,
hitsTotal: 10000,
hitsMonth: 1000,
hitsDay: 100,
date: dateStr,
datem: monthStr,
time: timeStr,
createTime: timeStr,
updateTime: timeStr,
);
}
Future<void> _loadPoetryById(int poetryId) async {
if (_loading) return;
setState(() => _loading = true);
try {
final response = await PoetryApi.getPoetryById(poetryId);
if (mounted && response.data != null) {
setState(() {
_poetryData = response.data;
_keywordList = PoetryDataUtils.extractKeywords(response.data);
_starDisplay = PoetryDataUtils.getStarDisplay(response.data);
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
_checkIfLiked();
_updateCurrentHistoryIndex();
} else {
// 数据为空时,显示默认内容
if (mounted) {
setState(() {
_poetryData = _createDefaultPoetryData();
_keywordList = [];
_starDisplay = '⭐⭐⭐⭐⭐';
_isLiked = false;
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
}
}
} catch (e) {
debugPrint('加载指定诗词失败: $e');
if (mounted) {
setState(() {
_poetryData = _createDefaultPoetryData();
_keywordList = [];
_starDisplay = '⭐⭐⭐⭐⭐';
_isLiked = false;
_loading = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
}
}
}
Future<void> _toggleLike() async {
if (_poetryData == null || _isLoadingLike) return;
// 立即切换按钮状态和显示加载
setState(() {
_isLoadingLike = true;
});
// 发送网络加载开始事件
startNetworkLoading('toggle_like');
try {
final response = await PoetryApi.toggleLike(_poetryData!.id);
if (mounted) {
setState(() {
// 根据API响应消息直接判断状态
_isLiked = response.message.contains('点赞成功');
// 更新诗词数据
if (response.data != null) {
_poetryData = PoetryData(
id: _poetryData!.id,
name: _poetryData!.name,
alias: _poetryData!.alias,
keywords: _poetryData!.keywords,
introduce: _poetryData!.introduce,
drtime: _poetryData!.drtime,
like: response.data!.like,
url: _poetryData!.url,
tui: _poetryData!.tui,
star: _poetryData!.star,
hitsTotal: _poetryData!.hitsTotal,
hitsMonth: _poetryData!.hitsMonth,
hitsDay: _poetryData!.hitsDay,
date: _poetryData!.date,
datem: _poetryData!.datem,
time: _poetryData!.time,
createTime: _poetryData!.createTime,
updateTime: _poetryData!.updateTime,
);
}
});
// 管理点赞存储
if (_isLiked) {
// 添加到点赞列表
await HistoryController.addToLiked(_poetryData!.toJson());
} else {
// 从点赞列表移除
await HistoryController.removeLikedPoetry(_poetryData!.id.toString());
}
// 发送点赞事件通知其他页面
sendLikeEvent(_poetryData!.id.toString(), _isLiked);
if (_isLiked) {
DebugInfoManager().showLiked();
} else {
DebugInfoManager().showUnliked();
}
PoetryStateManager.showSnackBar(
context,
response.message,
backgroundColor: AppConstants.successColor,
duration: const Duration(milliseconds: 200),
);
}
} catch (e) {
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'操作失败,请重试',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
}
} finally {
if (mounted) {
setState(() => _isLoadingLike = false);
// 发送网络加载结束事件
endNetworkLoading('toggle_like');
}
}
}
Future<void> _loadHistory() async {
try {
final historyJson = await HistoryController.getHistory();
if (mounted) {
setState(() {
_historyList = historyJson;
// 重新计算当前诗词在历史记录中的索引
if (_poetryData != null && _historyList.isNotEmpty) {
_currentHistoryIndex = _historyList.indexWhere(
(item) => item['id'] == _poetryData!.id,
);
} else {
_currentHistoryIndex = -1;
}
});
}
} catch (e) {
print('加载历史记录失败: $e');
}
}
Future<void> _saveToHistory(PoetryData poetryData) async {
try {
final poetryMap = {
'id': poetryData.id,
'name': poetryData.name, // 诗句名称/标题
'alias': poetryData.alias, // 诗句朝代/作者
'introduce': poetryData.introduce, // 诗句译文/解释
'drtime': poetryData.drtime, // 诗句原文/内容
};
await HistoryController.addToHistory(poetryMap);
} catch (e) {
print('保存历史记录失败: $e');
}
}
void _updateCurrentHistoryIndex() {
if (_poetryData?.id != null) {
final index = _historyList.indexWhere(
(item) => item['id'] == _poetryData!.id,
);
_currentHistoryIndex = index >= 0 ? index : -1;
}
}
Future<void> _checkIfLiked() async {
// 这里可以实现检查收藏状态的逻辑
// 暂时使用简单的模拟逻辑
}
void _loadNextPoetry() async {
if (_isLoadingNext) return;
setState(() {
_isLoadingNext = true;
// 设置所有区域为加载状态
_sectionLoadingStates = {
'title': true,
'content': true,
'name': true,
'keywords': true,
'introduction': true,
};
});
try {
final offlineDataManager = OfflineDataManager();
final isOnline = await offlineDataManager.isOnline();
final hasCachedData = await offlineDataManager.hasCachedData();
PoetryData? newPoetryData;
if (isOnline) {
// 在线状态:从网络加载
// 确保历史记录已加载
if (_historyList.isEmpty) {
await _loadHistory();
}
if (_currentHistoryIndex < 0 ||
_currentHistoryIndex >= _historyList.length - 1) {
// 如果没有下一条了,加载新的诗词
final response = await PoetryApi.getRandomPoetry();
newPoetryData = response.data;
if (mounted && newPoetryData != null) {
await _saveToHistory(newPoetryData);
}
} else {
// 如果有下一条,加载下一条
final nextPoetry = _historyList[_currentHistoryIndex + 1];
final response = await PoetryApi.getPoetryById(nextPoetry['id']);
newPoetryData = response.data;
}
} else {
// 离线状态:从本地缓存加载
if (hasCachedData) {
newPoetryData = await offlineDataManager.getNextPoetry();
} else {
// 离线且无缓存时,显示错误信息
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'离线模式下无缓存数据,请先在线下载',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
// 重置所有加载状态
setState(() {
_sectionLoadingStates.updateAll((key, value) => false);
});
}
DebugInfoManager().showNextFailed();
return;
}
}
if (mounted && newPoetryData != null) {
// 模拟分步加载
await _simulateSectionLoading(newPoetryData);
DebugInfoManager().showNextSuccess();
}
} catch (e) {
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'加载下一条失败',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
// 重置所有加载状态
setState(() {
_sectionLoadingStates.updateAll((key, value) => false);
});
}
DebugInfoManager().showNextFailed();
} finally {
if (mounted) {
setState(() {
_isLoadingNext = false;
});
}
}
}
// 模拟分步加载过程
Future<void> _simulateSectionLoading(PoetryData newPoetryData) async {
// 1. 加载标题区域
setState(() {
_sectionLoadingStates['title'] = false;
_poetryData = newPoetryData;
});
await Future.delayed(const Duration(milliseconds: 200));
// 2. 加载诗句区域
setState(() {
_sectionLoadingStates['name'] = false;
});
await Future.delayed(const Duration(milliseconds: 200));
// 3. 加载原文区域
setState(() {
_sectionLoadingStates['content'] = false;
});
await Future.delayed(const Duration(milliseconds: 200));
// 4. 加载关键词区域
setState(() {
_sectionLoadingStates['keywords'] = false;
_keywordList = PoetryDataUtils.extractKeywords(newPoetryData);
});
await Future.delayed(const Duration(milliseconds: 200));
// 5. 加载译文区域
setState(() {
_sectionLoadingStates['introduction'] = false;
_starDisplay = PoetryDataUtils.getStarDisplay(newPoetryData);
_isLiked = false;
_errorMessage = '';
});
_checkIfLiked();
_updateCurrentHistoryIndex();
}
void _loadPreviousPoetry() async {
try {
final offlineDataManager = OfflineDataManager();
final isOnline = await offlineDataManager.isOnline();
final hasCachedData = await offlineDataManager.hasCachedData();
if (isOnline) {
// 在线状态:从历史记录加载
// 确保历史记录已加载
if (_historyList.isEmpty) {
await _loadHistory();
}
if (_currentHistoryIndex <= 0) {
// 如果当前索引无效或已经是第一条,则重新加载历史记录
await _loadHistory();
// 如果历史记录为空,显示提示
if (_historyList.isEmpty) {
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'暂无历史记录',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
}
return;
}
// 如果当前索引无效,设置为最新的一条
if (_currentHistoryIndex < 0) {
_currentHistoryIndex = 0;
}
}
// 检查是否有上一条
if (_currentHistoryIndex > 0) {
final previousPoetry = _historyList[_currentHistoryIndex - 1];
await _loadPoetryById(previousPoetry['id']);
} else {
// 如果没有上一条了,显示提示
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'已经是第一条了',
backgroundColor: AppConstants.infoColor,
duration: const Duration(milliseconds: 200),
);
}
}
} else {
// 离线状态:从本地缓存加载
if (hasCachedData) {
final poetryData = await offlineDataManager.getPreviousPoetry();
if (mounted && poetryData != null) {
setState(() {
_poetryData = poetryData;
_keywordList = PoetryDataUtils.extractKeywords(poetryData);
_starDisplay = PoetryDataUtils.getStarDisplay(poetryData);
_isLiked = false;
_errorMessage = '';
});
_fadeController.forward();
_slideController.forward();
_checkIfLiked();
DebugInfoManager().showPreviousSuccess();
} else {
// 缓存为空时,显示错误信息
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'离线模式下无缓存数据,请先在线下载',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
}
DebugInfoManager().showPreviousFailed();
}
} else {
// 离线且无缓存时,显示错误信息
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'离线模式下无缓存数据,请先在线下载',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
}
DebugInfoManager().showPreviousFailed();
}
}
} catch (e) {
if (mounted) {
PoetryStateManager.showSnackBar(
context,
'加载上一条失败',
backgroundColor: AppConstants.errorColor,
duration: const Duration(milliseconds: 200),
);
}
}
}
Future<void> _refreshPoetry() async {
if (_poetryData?.id != null) {
// 如果有当前诗词ID则重新加载当前诗词
await _loadPoetryById(_poetryData!.id);
} else {
// 如果没有当前诗词ID则加载新的诗词
await _loadPoetry();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
body: SafeArea(child: _buildBody()),
);
}
Widget _buildBody() {
if (_loading) {
return const LoadingWidget();
}
if (_errorMessage.isNotEmpty) {
return CustomErrorWidget(
errorMessage: _errorMessage,
onRetry: _loadPoetry,
);
}
if (!PoetryDataUtils.isValidPoetryData(_poetryData)) {
return const EmptyWidget();
}
return _buildContent();
}
Widget _buildContent() {
return FutureBuilder<bool>(
future: OfflineDataManager().isOnline(),
builder: (context, snapshot) {
final isOnline = snapshot.data ?? true;
return RefreshIndicator(
onRefresh: _refreshPoetry,
child: FadeTransition(
opacity: _fadeAnimation,
child: Stack(
children: [
SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), // 确保可以下拉刷新
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
PoetryCard(
poetryData: _poetryData!,
keywordList: _keywordList,
onTap: _loadNextPoetry,
sectionLoadingStates: _sectionLoadingStates,
),
const SizedBox(height: 160), // 为悬浮按钮留出更多空间
],
),
),
// 调试信息气泡
Positioned(
bottom: 66,
left: 0,
right: 0,
child: Center(child: _buildDebugInfoBubble()),
),
// 悬浮上一条按钮 - 左边上方
Positioned(
left: 16,
bottom: 100,
child: FloatingPreviousButton(
onPrevious: _loadPreviousPoetry,
),
),
// 悬浮下一条按钮 - 左边下方
Positioned(
left: 16,
bottom: 32,
child: FloatingNextButton(onNext: _loadNextPoetry),
),
// 悬浮点赞按钮 - 右边(仅在线状态显示)
if (isOnline)
Positioned(
right: 16,
bottom: 32,
child: FloatingLikeButton(
isLiked: _isLiked,
isLoadingLike: _isLoadingLike,
onToggleLike: _toggleLike,
),
),
],
),
),
);
},
);
}
Widget _buildDebugInfoBubble() {
return ValueListenableBuilder<String>(
valueListenable: DebugInfoManager().messageNotifier,
builder: (context, message, child) {
if (message.isEmpty) {
return const SizedBox.shrink();
}
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Text(
message,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
);
},
);
}
}