release 1.3.1
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../../../constants/app_constants.dart';
|
||||
|
||||
/// 时间: 2026-03-27
|
||||
@@ -17,6 +20,13 @@ class AppDataPage extends StatefulWidget {
|
||||
class _AppDataPageState extends State<AppDataPage> {
|
||||
int _sharedPrefsCount = 0;
|
||||
bool _isLoading = true;
|
||||
String _packageSize = '计算中...';
|
||||
String _cacheSize = '计算中...';
|
||||
String _dataSize = '计算中...';
|
||||
String _totalSize = '计算中...';
|
||||
int _packageBytes = 0;
|
||||
int _cacheBytes = 0;
|
||||
int _dataBytes = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -27,12 +37,137 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
Future<void> _loadDataInfo() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final keys = prefs.getKeys();
|
||||
|
||||
await Future.wait([
|
||||
_calculatePackageSize(),
|
||||
_calculateCacheSize(),
|
||||
_calculateDataSize(),
|
||||
]);
|
||||
|
||||
_calculateTotalSize();
|
||||
|
||||
setState(() {
|
||||
_sharedPrefsCount = keys.length;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> _getDirectorySize(Directory dir) async {
|
||||
int totalSize = 0;
|
||||
try {
|
||||
if (await dir.exists()) {
|
||||
await for (FileSystemEntity entity in dir.list(recursive: true)) {
|
||||
if (entity is File) {
|
||||
try {
|
||||
totalSize += await entity.length();
|
||||
} catch (e) {
|
||||
debugPrint('获取文件大小失败: ${entity.path}, $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('计算目录大小失败: ${dir.path}, $e');
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
String _formatBytes(int bytes) {
|
||||
if (bytes <= 0) return '0 B';
|
||||
const suffixes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
int i = (bytes.bitLength - 1) ~/ 10;
|
||||
double size = bytes / (1 << (i * 10));
|
||||
return '${size.toStringAsFixed(i == 0 ? 0 : 2)} ${suffixes[i]}';
|
||||
}
|
||||
|
||||
Future<void> _calculatePackageSize() async {
|
||||
try {
|
||||
int totalSize = 0;
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
|
||||
final parentDir = tempDir.parent;
|
||||
if (await parentDir.exists()) {
|
||||
await for (FileSystemEntity entity in parentDir.list()) {
|
||||
if (entity is Directory) {
|
||||
final dirName = entity.path.split(Platform.pathSeparator).last;
|
||||
if (!dirName.startsWith('.')) {
|
||||
totalSize += await _getDirectorySize(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_packageBytes = totalSize;
|
||||
_packageSize = _formatBytes(totalSize);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('计算软件包大小失败: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_packageSize = '获取失败';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _calculateCacheSize() async {
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final size = await _getDirectorySize(tempDir);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_cacheBytes = size;
|
||||
_cacheSize = _formatBytes(size);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('计算缓存大小失败: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_cacheSize = '获取失败';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _calculateDataSize() async {
|
||||
try {
|
||||
int totalSize = 0;
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
|
||||
totalSize += await _getDirectorySize(appDocDir);
|
||||
totalSize += await _getDirectorySize(supportDir);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_dataBytes = totalSize;
|
||||
_dataSize = _formatBytes(totalSize);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('计算数据大小失败: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_dataSize = '获取失败';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _calculateTotalSize() {
|
||||
const int appBaseSize = 10 * 1024 * 1024; // 10MB
|
||||
final totalBytes = _packageBytes + _cacheBytes + _dataBytes + appBaseSize;
|
||||
setState(() {
|
||||
_totalSize = _formatBytes(totalBytes);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _clearSharedPreferences() async {
|
||||
final confirmed = await _showConfirmDialog(
|
||||
'清空配置数据',
|
||||
@@ -140,6 +275,55 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _nativeClearData() async {
|
||||
final confirmed = await _showConfirmDialog(
|
||||
'系统层清理数据',
|
||||
'⚠️ 危险操作 ⚠️\n\n确定要系统层清理数据吗?\n\n这将删除:\n• 应用文档目录所有文件\n• 应用支持目录所有文件\n• 应用缓存目录所有文件\n\n此操作将直接删除文件系统数据,不可撤销!',
|
||||
);
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
final doubleConfirmed = await _showConfirmDialog(
|
||||
'再次确认',
|
||||
'这是系统层面文件系统删除!\n\n删除后数据将无法恢复,确定继续吗?',
|
||||
);
|
||||
|
||||
if (doubleConfirmed != true) return;
|
||||
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
final supportDir = await getApplicationSupportDirectory();
|
||||
|
||||
debugPrint('开始清理...');
|
||||
debugPrint('临时目录: ${tempDir.path}');
|
||||
debugPrint('文档目录: ${appDocDir.path}');
|
||||
debugPrint('支持目录: ${supportDir.path}');
|
||||
|
||||
await _deleteDirectory(tempDir);
|
||||
await _deleteDirectory(appDocDir);
|
||||
await _deleteDirectory(supportDir);
|
||||
|
||||
debugPrint('原生清理完成');
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (mounted) {
|
||||
_showExitConfirmDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('原生清理失败: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('清理失败: $e'), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearCache() async {
|
||||
final confirmed = await _showConfirmDialog(
|
||||
'清空缓存',
|
||||
@@ -148,13 +332,49 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('缓存已清空'),
|
||||
backgroundColor: AppConstants.primaryColor,
|
||||
),
|
||||
);
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
if (await tempDir.exists()) {
|
||||
await _deleteDirectory(tempDir);
|
||||
}
|
||||
|
||||
await _calculateCacheSize();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('缓存已清空'),
|
||||
backgroundColor: AppConstants.primaryColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('清空缓存失败: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('清空失败: $e'), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteDirectory(Directory dir) async {
|
||||
try {
|
||||
if (await dir.exists()) {
|
||||
await for (FileSystemEntity entity in dir.list()) {
|
||||
if (entity is File) {
|
||||
try {
|
||||
await entity.delete();
|
||||
} catch (e) {
|
||||
debugPrint('删除文件失败: ${entity.path}, $e');
|
||||
}
|
||||
} else if (entity is Directory) {
|
||||
await _deleteDirectory(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('删除目录失败: ${dir.path}, $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +441,46 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showExitConfirmDialog() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.power_settings_new, color: Colors.deepOrange[700]),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(child: Text('关闭应用', style: TextStyle(fontSize: 18))),
|
||||
],
|
||||
),
|
||||
content: const Text('数据清理完成!\n\n建议关闭应用后重新启动,\n以确保应用正常运行。\n\n是否现在关闭应用?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text('稍后关闭', style: TextStyle(color: Colors.grey[600])),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.deepOrange[700],
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text('立即关闭'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
if (mounted) {
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -303,13 +563,15 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildDataItem('📦 软件包', '情景诗词 v1.2.19'),
|
||||
_buildDataItem('📦 软件包', _packageSize),
|
||||
_buildDivider(),
|
||||
_buildDataItem('📱 配置项数量', '$_sharedPrefsCount 项'),
|
||||
_buildDivider(),
|
||||
_buildDataItem('💾 缓存大小', '计算中...'),
|
||||
_buildDataItem('💾 缓存大小', _cacheSize),
|
||||
_buildDivider(),
|
||||
_buildDataItem('📊 数据库大小', '计算中...'),
|
||||
_buildDataItem('📊 数据大小', _dataSize),
|
||||
_buildDivider(),
|
||||
_buildDataItem('📁 占用空间', _totalSize),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -506,6 +768,14 @@ class _AppDataPageState extends State<AppDataPage> {
|
||||
Colors.red,
|
||||
_clearAllData,
|
||||
),
|
||||
_buildDivider(),
|
||||
_buildActionButton(
|
||||
'🔧 原生清理数据',
|
||||
'删除应用数据目录所有文件',
|
||||
Icons.system_security_update_warning,
|
||||
Colors.orange,
|
||||
_nativeClearData,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,860 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../constants/app_constants.dart';
|
||||
import 'sp-guide.dart';
|
||||
|
||||
class BeginnerPage extends StatefulWidget {
|
||||
const BeginnerPage({super.key});
|
||||
|
||||
@override
|
||||
State<BeginnerPage> createState() => _BeginnerPageState();
|
||||
}
|
||||
|
||||
class _BeginnerPageState extends State<BeginnerPage>
|
||||
with TickerProviderStateMixin {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
late AnimationController _fadeController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
double _progress = 0.0;
|
||||
|
||||
final List<Map<String, dynamic>> _tutorialSections = [
|
||||
{
|
||||
'title': '首页功能',
|
||||
'icon': Icons.home,
|
||||
'emoji': '🏠',
|
||||
'color': AppConstants.primaryColor,
|
||||
'features': [
|
||||
'精美卡片展示',
|
||||
'智能推荐:根据时间和情景推荐合适的诗词',
|
||||
'诗词详情:显示标题、作者、朝代、正文、注释和赏析',
|
||||
'精选诗句:突出显示经典名句',
|
||||
'下拉刷新:刷新诗词内容',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '发现页面',
|
||||
'icon': Icons.explore,
|
||||
'emoji': '🔍',
|
||||
'color': const Color(0xFF00BCD4),
|
||||
'features': [
|
||||
'分类浏览:按朝代、作者、主题等分类浏览',
|
||||
'热门诗词:查看最受欢迎的诗词',
|
||||
'活跃排行:查看活跃度最高的诗词',
|
||||
'搜索功能:通过关键词搜索诗词',
|
||||
'Tab切换:分类、热门、搜索标签页',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '足迹页面',
|
||||
'icon': Icons.history,
|
||||
'emoji': '📚',
|
||||
'color': const Color(0xFFFF9800),
|
||||
'features': [
|
||||
'收藏管理:查看和管理所有收藏的诗词',
|
||||
'笔记功能:创建和管理诗词笔记',
|
||||
'分类展示:全部、点赞、笔记等分类',
|
||||
'搜索功能:搜索收藏内容',
|
||||
'视图切换:网格和列表视图',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '个人中心',
|
||||
'icon': Icons.person,
|
||||
'emoji': '👤',
|
||||
'color': const Color(0xFF4CAF50),
|
||||
'features': [
|
||||
'个人信息:编辑昵称、头像等',
|
||||
'统计数据:今日浏览、累计浏览、今日点赞等',
|
||||
'数据隐藏:可隐藏统计和答题数据',
|
||||
'用户计划:加入用户体验计划',
|
||||
'功能入口:设置、了解我们、权限管理等',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '诗词阅读',
|
||||
'icon': Icons.menu_book,
|
||||
'emoji': '📖',
|
||||
'color': const Color(0xFF9C27B0),
|
||||
'features': [
|
||||
'加载诗词:点击卡片任意区域加载下一条',
|
||||
'查看详情:显示完整诗词信息',
|
||||
'点赞诗词:点击心形图标',
|
||||
'切换诗词:上一条/下一条按钮',
|
||||
'长按复制:长按复制诗词内容',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '收藏功能',
|
||||
'icon': Icons.favorite,
|
||||
'emoji': '❤️',
|
||||
'color': const Color(0xFFE91E63),
|
||||
'features': [
|
||||
'一键收藏:点击收藏图标',
|
||||
'收藏列表:在足迹页查看',
|
||||
'取消收藏:再次点击收藏图标',
|
||||
'收藏统计:显示收藏数量',
|
||||
'创建笔记:从诗词创建笔记',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '搜索功能',
|
||||
'icon': Icons.search,
|
||||
'emoji': '🔎',
|
||||
'color': const Color(0xFF009688),
|
||||
'features': [
|
||||
'关键词搜索:输入关键词搜索',
|
||||
'分类筛选:按分类筛选结果',
|
||||
'热门搜索:查看热门搜索词',
|
||||
'搜索历史:保留搜索记录',
|
||||
'实时搜索:输入即时搜索',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '答题挑战',
|
||||
'icon': Icons.quiz,
|
||||
'emoji': '🎮',
|
||||
'color': const Color(0xFFFF5722),
|
||||
'features': [
|
||||
'答题挑战:参与诗词答题',
|
||||
'题目随机化:使用Fisher-Yates算法',
|
||||
'答题记录:记录历史和成绩',
|
||||
'答题统计:正确率等数据',
|
||||
'提示功能:遇到困难可获取提示',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '离线模式',
|
||||
'icon': Icons.wifi_off,
|
||||
'emoji': '📴',
|
||||
'color': const Color(0xFF607D8B),
|
||||
'features': [
|
||||
'离线数据下载:支持下载诗词和答题',
|
||||
'下载选项:20/30/60/100条可选',
|
||||
'后台下载:返回上一页继续下载',
|
||||
'缓存管理:清空缓存可选择内容',
|
||||
'自动切换:无网络自动切换',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '个性化设置',
|
||||
'icon': Icons.palette,
|
||||
'emoji': '🎨',
|
||||
'color': const Color(0xFF3F51B5),
|
||||
'features': [
|
||||
'主题切换:浅色/深色主题',
|
||||
'卡片样式:三种样式可选',
|
||||
'颜色自定义:主题色和背景色',
|
||||
'圆角调整:卡片圆角大小',
|
||||
'字体大小:调整字体大小',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '投稿功能',
|
||||
'icon': Icons.send,
|
||||
'emoji': '📝',
|
||||
'color': const Color(0xFF00BCD4),
|
||||
'features': [
|
||||
'诗词投稿:向软件投稿诗词',
|
||||
'投稿表单:填写诗词信息',
|
||||
'投稿记录:查看历史投稿',
|
||||
'相似度检测:防止重复投稿',
|
||||
'清空记录:清空投稿历史',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '投票功能',
|
||||
'icon': Icons.how_to_vote,
|
||||
'emoji': '🗳️',
|
||||
'color': const Color(0xFF795548),
|
||||
'features': [
|
||||
'用户投票:参与功能投票',
|
||||
'投票结果:查看统计',
|
||||
'登录注册:用户身份',
|
||||
'自动注册:简化流程',
|
||||
'投票详情:查看选项',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '桌面卡片',
|
||||
'icon': Icons.wb_sunny,
|
||||
'emoji': '🌤️',
|
||||
'color': const Color(0xFFFFC107),
|
||||
'features': [
|
||||
'天气显示:显示当前天气',
|
||||
'城市显示:所在城市',
|
||||
'十二时辰:中国传统计时',
|
||||
'时间标签:子丑寅卯辰巳午未申酉戌亥',
|
||||
'智能更新:自动更新时间',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '图片分享',
|
||||
'icon': Icons.share,
|
||||
'emoji': '📸',
|
||||
'color': const Color(0xFF03A9F4),
|
||||
'features': [
|
||||
'诗词分享:生成图片分享',
|
||||
'软件分享:分享给朋友',
|
||||
'复制功能:复制内容',
|
||||
'高清图片:生成高质量图片',
|
||||
'跨平台:支持多平台分享',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '数据管理',
|
||||
'icon': Icons.storage,
|
||||
'emoji': '💾',
|
||||
'color': const Color(0xFF8BC34A),
|
||||
'features': [
|
||||
'数据统计:显示数据占用',
|
||||
'清空数据:清空应用数据',
|
||||
'数据备份:备份数据',
|
||||
'缓存管理:管理缓存',
|
||||
'离线数据:管理离线数据',
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': '帮助与反馈',
|
||||
'icon': Icons.help,
|
||||
'emoji': '❓',
|
||||
'color': const Color(0xFF9E9E9E),
|
||||
'features': [
|
||||
'使用指南:查看使用说明',
|
||||
'常见问题:FAQ解答',
|
||||
'用户反馈:反馈问题',
|
||||
'功能建议:提交建议',
|
||||
'已知Bug:查看Bug列表',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
_fadeAnimation = CurvedAnimation(
|
||||
parent: _fadeController,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
_fadeController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_scrollController.dispose();
|
||||
_fadeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.hasClients) {
|
||||
final maxScroll = _scrollController.position.maxScrollExtent;
|
||||
final currentScroll = _scrollController.position.pixels;
|
||||
final progress = maxScroll > 0 ? currentScroll / maxScroll : 0.0;
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_progress = progress;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
body: Stack(
|
||||
children: [
|
||||
FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
title: const Text(
|
||||
'软件功能',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 17,
|
||||
color: AppConstants.primaryColor,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: AppConstants.primaryColor,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
floating: true,
|
||||
snap: true,
|
||||
pinned: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline),
|
||||
color: AppConstants.primaryColor,
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const SpGuidePage(fromSettings: true),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 100),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return _buildSectionCard(_tutorialSections[index], index);
|
||||
}, childCount: _tutorialSections.length),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildProgressIndicator(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionCard(Map<String, dynamic> section, int index) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
duration: Duration(milliseconds: 300 + (index * 50)),
|
||||
curve: Curves.easeOut,
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, 20 * (1 - value)),
|
||||
child: Opacity(opacity: value, child: child),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: (section['color'] as Color).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
section['emoji'],
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
section['title'],
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
letterSpacing: -0.3,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'第 ${index + 1} 部分',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey[500],
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildSectionContent(section['features'], section['color']),
|
||||
const SizedBox(height: 16),
|
||||
if (index < 4) ...[
|
||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||
const SizedBox(height: 16),
|
||||
_buildPreviewSection(section['title']),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionContent(List<String> features, Color color) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: features.map((feature) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
margin: const EdgeInsets.only(top: 8, right: 12),
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
feature,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.grey[700],
|
||||
height: 1.5,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewSection(String title) {
|
||||
Widget preview;
|
||||
switch (title) {
|
||||
case '首页功能':
|
||||
preview = _buildHomePreview();
|
||||
break;
|
||||
case '发现页面':
|
||||
preview = _buildDiscoverPreview();
|
||||
break;
|
||||
case '足迹页面':
|
||||
preview = _buildFootprintPreview();
|
||||
break;
|
||||
case '个人中心':
|
||||
preview = _buildProfilePreview();
|
||||
break;
|
||||
default:
|
||||
preview = const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.visibility_outlined,
|
||||
size: 16,
|
||||
color: AppConstants.primaryColor,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'界面预览',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppConstants.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
preview,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHomePreview() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppConstants.primaryColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.book,
|
||||
size: 18,
|
||||
color: AppConstants.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'静夜思',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'唐·李白',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'床前明月光,疑是地上霜。\n举头望明月,低头思故乡。',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
height: 1.6,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildPreviewAction(Icons.favorite_border, '点赞'),
|
||||
_buildPreviewAction(Icons.bookmark_border, '收藏'),
|
||||
_buildPreviewAction(Icons.share_outlined, '分享'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDiscoverPreview() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.search, size: 18, color: Colors.grey[400]),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'搜索诗词...',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
_buildPreviewTag('唐诗'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPreviewTag('宋词'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPreviewTag('元曲'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildPreviewListItem('将进酒', '李白'),
|
||||
const SizedBox(height: 8),
|
||||
_buildPreviewListItem('水调歌头', '苏轼'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFootprintPreview() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildPreviewStat('128', '收藏'),
|
||||
_buildPreviewStat('45', '笔记'),
|
||||
_buildPreviewStat('89', '点赞'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildPreviewListItem('静夜思', '已收藏'),
|
||||
const SizedBox(height: 8),
|
||||
_buildPreviewListItem('春晓', '有笔记'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfilePreview() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppConstants.primaryColor.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('👤', style: TextStyle(fontSize: 24)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'诗词爱好者',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Lv.12 · 诗意生活',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildPreviewStat('156', '浏览'),
|
||||
_buildPreviewStat('89', '点赞'),
|
||||
_buildPreviewStat('45', '答题'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewAction(IconData icon, String label) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: Colors.grey[600]),
|
||||
const SizedBox(height: 4),
|
||||
Text(label, style: TextStyle(fontSize: 11, color: Colors.grey[600])),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewTag(String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppConstants.primaryColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppConstants.primaryColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewListItem(String title, String subtitle) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewStat(String value, String label) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppConstants.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(label, style: TextStyle(fontSize: 11, color: Colors.grey[500])),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProgressIndicator() {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: 60,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Colors.white.withValues(alpha: 0.95),
|
||||
Colors.white.withValues(alpha: 0.85),
|
||||
Colors.white.withValues(alpha: 0.0),
|
||||
],
|
||||
stops: const [0.0, 0.7, 1.0],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
top: _progress * 160,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppConstants.primaryColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppConstants.primaryColor.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppConstants.primaryColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppConstants.primaryColor.withValues(alpha: 0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
'${(_progress * 100).toInt()}%',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ class _SpGuidePageState extends State<SpGuidePage>
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildShowGuideCheckbox(),
|
||||
// _buildShowGuideCheckbox(),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user