From cba04235c8cfacb0518e5b5ff3398953dd26ccc2 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 3 Apr 2026 03:26:06 +0800 Subject: [PATCH] release --- CHANGELOG.md | 321 +++++++++ .../2026-04-03-home-poetry-card-skeleton.md | 335 +++++++++ .../plans/2026-04-03-poetry-theme-refactor.md | 663 ++++++++++++++++++ ...-04-03-home-poetry-card-skeleton-design.md | 59 ++ ...2026-04-03-poetry-theme-refactor-design.md | 185 +++++ lib/config/app_config.dart | 43 +- lib/constants/app_constants.dart | 1 - lib/controllers/settings/suggestions.dart | 651 +++++++++++++---- lib/main.dart | 3 + lib/models/colors/theme_colors.dart | 8 +- lib/services/document/stats.php | 407 +++++++++-- lib/services/get/care_controller.dart | 37 + lib/services/get/discover_controller.dart | 4 +- lib/services/get/favorites_controller.dart | 18 +- lib/services/get/home_controller.dart | 64 +- lib/services/get/profile_controller.dart | 23 +- .../get/tap_liquid_glass_controller.dart | 4 +- lib/services/get/theme_controller.dart | 3 + lib/utils/audio_manager.dart | 15 +- lib/views/active/category_page.dart | 49 +- lib/views/discover_page.dart | 97 ++- lib/views/home/care/care-page.dart | 44 +- lib/views/home/care/care_poetry_page.dart | 459 ++++++++---- lib/views/home/care/care_widgets.dart | 71 +- lib/views/home/home_page.dart | 54 +- lib/views/home/home_part.dart | 377 +++++----- lib/views/home/set/home-set.dart | 23 +- lib/views/profile/app-info.dart | 159 +++-- lib/views/profile/components/entire_page.dart | 83 +-- lib/views/profile/guide/app-data.dart | 2 +- lib/views/profile/guide/permission.dart | 17 +- lib/views/profile/history_page.dart | 6 +- lib/views/profile/level/poetry.dart | 62 +- lib/views/profile/per_card.dart | 2 + lib/views/profile/profile_page.dart | 2 +- lib/views/profile/settings/app_fun.dart | 90 ++- lib/views/profile/settings/learn-us.dart | 144 ++-- lib/views/profile/settings/offline-data.dart | 215 +++--- lib/views/profile/theme/app-diy.dart | 202 ++++-- lib/widgets/care/care_mode_navigation.dart | 177 +++-- lib/widgets/main_navigation.dart | 5 +- lib/widgets/tap-liquid-glass.dart | 2 +- ohos/entry/src/main/module.json5 | 7 +- pubspec.lock | 2 +- pubspec.yaml | 1 + resize_android_icons.py | 40 -- resize_icons.py | 52 -- update_android_icons.py | 40 -- update_harmony_icons.py | 48 -- 49 files changed, 3955 insertions(+), 1421 deletions(-) create mode 100644 docs/superpowers/plans/2026-04-03-home-poetry-card-skeleton.md create mode 100644 docs/superpowers/plans/2026-04-03-poetry-theme-refactor.md create mode 100644 docs/superpowers/specs/2026-04-03-home-poetry-card-skeleton-design.md create mode 100644 docs/superpowers/specs/2026-04-03-poetry-theme-refactor-design.md delete mode 100644 resize_android_icons.py delete mode 100644 resize_icons.py delete mode 100644 update_android_icons.py delete mode 100644 update_harmony_icons.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5859960..5ac973d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,327 @@ All notable changes to this project will be documented in this file. --- +## [1.4.1] - 2026-04-03 + +### 新增 +- ✨ **从 pubspec.yaml 动态获取版本号** + - 使用 package_info_plus 插件动态获取应用版本号和版本代码 + - 在 AppConfig 中添加 init() 初始化方法和 appVersion、appVersionCode getter + - 在 main.dart 中应用启动时初始化 AppConfig + - 在 app-info.dart 中动态显示 appVersion 和 appVersionCode + - 涉及文件: + - `pubspec.yaml` - 使用官方 package_info_plus: ^9.0.1 + - `lib/config/app_config.dart` - 添加动态版本号获取 + - `lib/main.dart` - 初始化 AppConfig + - `lib/views/profile/app-info.dart` - 动态显示版本号 +- ✨ **软件更新日志卡片** + - 启用应用信息页面的软件更新日志卡片 + - 添加了版本 1.4.1 和 1.3.59 的更新内容 + - 涉及文件: + - `lib/views/profile/app-info.dart` - 启用更新日志 +- ✨ **开源框架列表添加 GetX** + - 在应用信息页面的开源框架列表中添加了 GetX + - GetX 协议:MIT + - 涉及文件: + - `lib/views/profile/app-info.dart` - 添加 GetX 到开源框架列表 +- ✨ **了解我们页面网站列表优化** + - 在官方网站卡片中添加了情景诗词在线版 + - 两个网站都添加了独立的复制按钮 + - 添加了网站标签显示(官方APP页/情景诗词在线版) + - 涉及文件: + - `lib/views/profile/settings/learn-us.dart` - 新增网站列表和复制按钮 + +### 优化 +- ⚡ **解决 package_info_plus 依赖冲突** + - 解决 wakelock_plus 与 package_info_plus 的版本冲突 + - 使用官方版本 package_info_plus: ^9.0.1 替代 git 版本 + - 同时保留鸿蒙适配版代码在 packages/flutter_plus_plugins/packages/package_info_plus 以备需要 + - 鸿蒙适配版包含完整的 ohos 平台实现 + - 涉及文件: + - `pubspec.yaml` - 移除 dependency_overrides,使用官方版本 + - `packages/flutter_plus_plugins/packages/package_info_plus` - 鸿蒙适配版本地备份 +- ⚡ **使用 Get.snackbar 替代 ScaffoldMessenger** + - 将应用信息页面中的所有 SnackBar 消息改为 Get.snackbar + - 背景色使用 Get.snackbar 默认颜色 + - 文字颜色使用动态主题色 + - 图标颜色也使用动态主题色(仅复制提示) + - 添加圆角和边距样式 + - 涉及文件: + - `lib/views/profile/app-info.dart` - SnackBar 改为 Get.snackbar +- ⚡ **了解我们页面消息提示优化** + - 将了解我们页面中的所有 SnackBar 消息改为 Get.snackbar + - 包括:QQ群号复制、微信公众号复制、网站链接复制 + - 背景色使用 Get.snackbar 默认颜色 + - 文字颜色使用动态主题色 + - 涉及文件: + - `lib/views/profile/settings/learn-us.dart` - 所有消息改为 Get.snackbar + +### 修复 +- 🐛 **修复版本号不显示的问题** + - 将 AppConfig 中的版本号改为 GetX 响应式变量 + - 在 app-info.dart 中使用 Obx 观察版本号变化 + - 更新默认版本号为 1.4.1 + - 涉及文件: + - `lib/config/app_config.dart` - 响应式版本号 + - `lib/views/profile/app-info.dart` - Obx 包裹版本号显示 + +### 删除 +- 🗑️ **移除设计风格卡片** + - 从应用信息页面移除了设计风格卡片 + - 涉及文件: + - `lib/views/profile/app-info.dart` - 移除 _buildDesignStyleCard 调用 + +## [1.3.59] - 2026-04-03 + +### 修复 +- 🐛 **修复出处字段被时间提示语遮挡的问题** + - 给诗词卡片内容添加了顶部内边距(60) + - 确保出处等内容不被浮动的时间提示语遮挡 + - 涉及文件: + - `lib/views/home/home_part.dart` - 添加顶部内边距 + +## [1.3.58] - 2026-04-03 + +### 优化 +- ✨ **优化主页诗词卡片布局,去除时间提示语下方的空白** + - 将时间提示语("夜深了,注意身体")改为 Positioned 在上层单独占位 + - 时间提示语不再占用布局空间,下方内容自动顶上来 + - 调整了复制提示框的位置,避免与时间提示语重叠 + - 涉及文件: + - `lib/views/home/home_part.dart` - 优化时间提示语布局 + +## [1.3.57] - 2026-04-03 + +### 修复 +- 🐛 **修复诗词答题页面底部操作按钮容器的深色模式适配** + - 修复了底部"上一题 提示 下一题"容器在深色模式下背景色不匹配的问题 + - 适配了按钮渐变、文字颜色、阴影等元素在深色模式下的显示 + - 将静态颜色 `AppConstants.primaryColor` 改为动态的 `primaryColor`,跟随主题色变化 + - 涉及文件: + - `lib/views/profile/level/poetry.dart` - 修复深色模式适配 + +## [1.3.56] - 2026-04-03 + +### 优化 +- 🎨 **修改主题颜色默认值为中国传统风格** + - 默认主题颜色从紫色改为书褐色 (0xFF8B4513),更符合中国传统诗词风格 + - 默认强调色从紫色改为明黄色 (Colors.yellow) + - 涉及文件: + - `lib/models/colors/theme_colors.dart` - 调整主题颜色和强调色顺序 +- 📋 **搜索项目中使用静态主题色的地方** + - 发现项目中多个文件使用 AppConstants.primaryColor 等静态颜色 + - 建议后续逐步改用 ThemeController 动态获取主题色 + +## [1.3.55] - 2026-04-03 + +### 修复 +- 🐛 **修复发现页面动画导致的卡死问题** + - 修复了使用 SingleTickerProviderStateMixin 但创建多个 AnimationController 导致的异常 + - 将 SingleTickerProviderStateMixin 改为 TickerProviderStateMixin + - 涉及文件: + - `lib/views/discover_page.dart` - 修复 ticker 提供者 + +## [1.3.54] - 2026-04-03 + +### 优化 +- ✨ **优化发现页面提示框的关闭动画** + - 点击关闭按钮时,提示框会有平滑的淡出和收缩动画 + - 下方的内容会自动顶上来占位,实现流畅的过渡效果 + - 涉及文件: + - `lib/views/discover_page.dart` - 添加动画控制器和过渡效果 + +## [1.3.53] - 2026-04-03 + +### 修复 +- 🐛 **修复声音设置不一致的问题** + - 修复了声音关闭时依旧有声音、有时候关闭时没声音的问题 + - 统一了 AudioManager 和 app_fun.dart 的默认值(都默认为 false,即静音) + - 修复了 AudioManager.setMuted() 没有保存到 SharedPreferences 的问题 + - 在 app_fun.dart 中加载设置时同步到 AudioManager + - 涉及文件: + - `lib/utils/audio_manager.dart` - 修复默认值和 setMuted 方法 + - `lib/views/profile/settings/app_fun.dart` - 修复设置加载和同步逻辑 + +## [1.3.52] - 2026-04-03 + +### 修复 +- 🐛 **彻底修复主页加载下一条诗词卡片时的闪白问题** + - 找到并修复了闪白的根本原因:home_page.dart 中每次 build 都会触发的 FadeTransition 动画 + - 移除了导致页面每次重新渲染都闪烁的动画代码 + - 简化了数据加载逻辑,移除了复杂的分步加载,改为一次性加载 + - 清理了不再需要的 AnimationController 和 SingleTickerProviderStateMixin + - 涉及文件: + - `lib/views/home/home_page.dart` - 移除 FadeTransition 动画和 AnimationController + - `lib/services/get/home_controller.dart` - 简化 `simulateSectionLoading()` 方法 + +## [1.3.51] - 2026-04-03 + +### 修复 +- 🐛 **主页加载下一条诗词卡片时的闪白问题** + - 修复切换诗词卡片时页面闪白的问题 + - 优化数据加载顺序,确保在显示骨架屏时旧数据仍然可见 + - 优化分步加载动画,将延迟从200ms缩短为150ms + - 在设置加载状态后立即调用update()通知UI更新 + - 涉及文件: + - `lib/services/get/home_controller.dart` - 优化 `loadNextPoetry()` 和 `simulateSectionLoading()` 方法 + +## [1.3.50] - 2026-04-03 + +### 新增 +- ✨ **分类页面底部提示** + - 在分类列表底部添加"到底了"提示 + - 防止列表项被全局底部导航栏遮住 + - 支持深色模式适配 + - 涉及文件: + - `lib/views/active/category_page.dart` - 添加 `_buildEndIndicator` 方法 + +### 优化 +- ⚡ **版本号管理优化** + - 删除 `AppConstants` 中重复的 `appVersion` 定义 + - 统一使用 `AppConfig.appVersion` 管理版本号 + - 更新版本号至 1.3.50 + - 涉及文件: + - `lib/constants/app_constants.dart` - 删除 `appVersion` 定义 + - `lib/config/app_config.dart` - 更新版本号 + - `lib/views/profile/profile_page.dart` - 更新引用 + +- ⚡ **Get.snackbar 消息样式优化** + - 字体颜色使用动态主题色,背景使用默认样式 + - 调用时重新获取 `ThemeController` 确保获取最新主题色 + - 涉及文件: + - `lib/views/profile/settings/learn-us.dart` - 复制成功提示 + - `lib/views/profile/per_card.dart` - 头像切换提示 + - `lib/views/profile/history_page.dart` - 分享和查看详情提示 + - `lib/views/profile/settings/offline-data.dart` - 离线数据页面所有消息提示 + - `lib/services/get/profile_controller.dart` - 开发计划、屏幕常亮等提示 + - `lib/services/get/favorites_controller.dart` - 收藏页面排序提示 + - `lib/services/get/discover_controller.dart` - 发现页面刷新提示 + - `lib/services/get/theme_controller.dart` - 主题切换提示 + +## [1.3.49] - 2026-04-03 + +### 修复 +- 🐛 **悬浮按钮管理器状态更新时机修复** + - 修复 `setState() or markNeedsBuild() called when widget tree was locked` 错误 + - 使用 `WidgetsBinding.instance.addPostFrameCallback` 延迟状态更新 + - 涉及文件: + - `lib/views/home/set/home-set.dart` - `init()`、`_restoreFromFlashing()`、`dispose()` 方法添加延迟更新 + +## [1.3.48] - 2026-04-03 + +### 修复 +- 🐛 **关怀页面用户类型主题色跟随修复** + - 修复用户类型选择按钮的主题色不跟随问题 + - 使用 `Obx` 包裹按钮,实时响应主题色变化 + - 涉及文件: + - `lib/views/home/care/care-page.dart` - `_buildUserTypeSelector` 方法添加响应式主题色 + +## [1.3.47] - 2026-04-03 + +### 优化 +- ⚡ **关怀模式开关点击区域优化** + - 点击关怀开关卡片的空白区域也可跳转至关怀页面 + - Switch 开关保持独立的开关功能 + - 涉及文件: + - `lib/views/home/care/care_widgets.dart` - `CareModeToggle` 组件添加整体点击跳转 + +## [1.3.46] - 2026-04-03 + +### 新增 +- ✨ **设置建议卡片新增统计跳转选项** + - 新增统计选项,跳转至 `EntirePage`(全站数据统计) + - 选项总数从8个增加到9个,布局调整为3页 + - 每页显示4个选项,第三页显示最后1个选项 + - 页面指示器更新为3个圆点 + - 涉及文件: + - `lib/controllers/settings/suggestions.dart` - 添加统计选项并调整布局 + +## [1.3.45] - 2026-04-03 + +### 新增 +- ✨ **设置建议卡片新增跳转选项** + - 新增诗词搜索选项,跳转至 `ActiveSearchPage` + - 新增调试页面选项,跳转至 `RatePage` + - 选项总数从6个增加到8个,每页显示4个选项 + - 涉及文件: + - `lib/controllers/settings/suggestions.dart` - 添加新选项并调整布局 + +## [1.3.44] - 2026-04-03 + +### 新增 +- ✨ **权限管理页面添加设置建议卡片** + - 在权限管理页面底部显示 `SuggestionsCard` 组件 + - 用户可在权限页面快速访问主题风格、关怀模式、使用教程等功能 + - 涉及文件: + - `lib/views/profile/guide/permission.dart` - 导入并添加 `SuggestionsCard` + +## [1.3.43] - 2026-04-03 + +### 新增 +- ✨ **设置建议卡片功能扩展** + - 新增关怀模式选项,跳转至 `CarePage` + - 新增使用教程选项,跳转至 `BeginnerPage` + - 选项支持左右滑动切换页面(PageView) + - 选项支持随机排序,每次展示卡片时自动重新排序 + - 添加页面指示器,显示当前所在页面 + - 涉及文件: + - `lib/controllers/settings/suggestions.dart` - 重构为 PageView 布局,添加随机排序功能 + +## [1.3.42] - 2026-04-03 + +### 新增 +- ✨ **设置建议卡片页面返回悬浮按钮** + - 从卡片进入的页面自动添加左侧返回悬浮长条按钮 + - 按钮位置在屏幕左侧中间(45%高度处) + - 5秒后自动消失,带有淡出动画 + - 支持深色/浅色模式,颜色跟随主题色 + - 涉及文件: + - `lib/controllers/settings/suggestions.dart` - 添加 `_PageWithBackButton` 包装组件 + +## [1.3.41] - 2026-04-03 + +### 优化 +- ⚡ **设置建议卡片布局与主题色支持** + - 修改卡片内选项布局:icon 和标题改为同一行,左右对齐 + - 卡片整体支持主题色,icon 颜色跟随主题色变化 + - 使用 `AppColors.primary` 动态获取当前主题色 + - 按钮背景使用主题色边框和半透明背景 + - 涉及文件: + - `lib/controllers/settings/suggestions.dart` - 布局重构,添加主题色支持 + - `lib/models/colors/app_colors.dart` - 使用动态颜色 + +## [1.3.40] - 2026-04-03 + +### 优化 +- ⚡ **个性化设置页面对话框优化** + - 开发中提示对话框添加"不再提醒"复选框,勾选后下次进入页面不再自动显示 + - 设置保存到 SharedPreferences,持久化存储用户选择 + - AppBar 右侧添加信息图标,点击可手动显示开发中提示对话框 + - 涉及文件: + - `lib/views/profile/theme/app-diy.dart` - 对话框优化,添加复选框和图标按钮 + +## [1.3.39] - 2026-04-03 + +### 新增 +- ✨ **主页诗词卡片骨架屏加载动画** + - 创建 `skeleton_widgets.dart`,实现骨架屏基础组件(SkeletonContainer) + - 修改 `home_part.dart`,所有区块(标题、诗词名、内容、关键词、简介)支持骨架屏加载状态 + - 修改 `care_poetry_page.dart`,关怀模式诗词页面支持骨架屏加载 + - 骨架屏采用渐变动画效果,防止加载时闪白 + - 支持深色/浅色模式,骨架颜色自动适配 + - 涉及文件: + - `lib/views/home/components/skeleton_widgets.dart` - 新建骨架屏组件 + - `lib/views/home/home_part.dart` - 集成骨架屏到各区块 + - `lib/views/home/care/care_poetry_page.dart` - 关怀模式骨架屏支持 + +### 优化 +- ⚡ **关怀开关自动隐藏功能** + - 主页左上角关怀按钮点击后,开关面板3秒内无操作自动消失 + - 用户与开关交互时重置定时器,延长显示时间 + - 涉及文件: + - `lib/services/get/care_controller.dart` - 添加自动隐藏定时器逻辑 + - `lib/views/home/home_page.dart` - 交互时重置定时器 + ## [1.3.38] - 2026-04-03 ### 重构 diff --git a/docs/superpowers/plans/2026-04-03-home-poetry-card-skeleton.md b/docs/superpowers/plans/2026-04-03-home-poetry-card-skeleton.md new file mode 100644 index 0000000..a4b5df4 --- /dev/null +++ b/docs/superpowers/plans/2026-04-03-home-poetry-card-skeleton.md @@ -0,0 +1,335 @@ +# 主页诗词卡片骨架屏实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 修复主页诗词卡片点击下一条时的闪白问题,使用骨架屏实现平滑过渡 + +**Architecture:** 修改 PoetryCard 组件,在加载新数据时显示骨架屏动画,保持卡片容器不销毁重建,分区域逐步显示内容 + +**Tech Stack:** Flutter, GetX, Shimmer 效果 + +--- + +## 文件结构 + +| 文件 | 职责 | +|------|------| +| `lib/views/home/home_part.dart` | 修改 PoetryCard 组件,添加骨架屏支持 | +| `lib/views/home/set/home_components.dart` | 添加骨架屏 Widget 组件 | +| `lib/services/get/home_controller.dart` | 调整加载状态管理(如有需要) | + +--- + +## Task 1: 创建骨架屏组件 + +**Files:** +- Create: `lib/views/home/components/skeleton_widgets.dart` + +- [ ] **Step 1: 创建骨架屏基础组件** + +```dart +import 'package:flutter/material.dart'; + +/// 骨架屏基础组件 +class SkeletonContainer extends StatefulWidget { + final double width; + final double height; + final double borderRadius; + final Color? baseColor; + final Color? highlightColor; + + const SkeletonContainer({ + super.key, + required this.width, + required this.height, + this.borderRadius = 8, + this.baseColor, + this.highlightColor, + }); + + @override + State createState() => _SkeletonContainerState(); +} + +class _SkeletonContainerState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + )..repeat(); + _animation = Tween(begin: -1, end: 2).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeInOutSine), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Container( + width: widget.width, + height: widget.height, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(widget.borderRadius), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + widget.baseColor ?? Colors.grey[300]!, + widget.highlightColor ?? Colors.grey[100]!, + widget.baseColor ?? Colors.grey[300]!, + ], + stops: [ + _animation.value - 0.3, + _animation.value, + _animation.value + 0.3, + ], + ), + ), + ); + }, + ); + } +} +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add lib/views/home/components/skeleton_widgets.dart +git commit -m "feat: 添加骨架屏基础组件 SkeletonContainer" +``` + +--- + +## Task 2: 修改 PoetryCard 组件 + +**Files:** +- Modify: `lib/views/home/home_part.dart` + +- [ ] **Step 1: 导入骨架屏组件** + +在文件顶部添加导入: +```dart +import 'components/skeleton_widgets.dart'; +``` + +- [ ] **Step 2: 修改 _buildTitleSection 方法** + +找到 `_buildTitleSection` 方法,修改加载状态的显示逻辑: + +```dart +Widget _buildTitleSection() { + final isLoading = widget.sectionLoadingStates?['title'] ?? false; + + if (isLoading) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: SkeletonContainer( + width: double.infinity, + height: 24, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + ); + } + + // 原有的非加载状态代码保持不变 + // ... +} +``` + +- [ ] **Step 3: 修改 _buildNameSection 方法** + +```dart +Widget _buildNameSection() { + final isLoading = widget.sectionLoadingStates?['name'] ?? false; + + if (isLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + children: [ + SkeletonContainer( + width: 120, + height: 28, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 80, + height: 16, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + ], + ), + ); + } + + // 原有的非加载状态代码保持不变 + // ... +} +``` + +- [ ] **Step 4: 修改 _buildContentSection 方法** + +```dart +Widget _buildContentSection() { + final isLoading = widget.sectionLoadingStates?['content'] ?? false; + + if (isLoading) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: List.generate(4, (index) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: SkeletonContainer( + width: index == 3 ? double.infinity * 0.6 : double.infinity, + height: 20, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + )), + ), + ); + } + + // 原有的非加载状态代码保持不变 + // ... +} +``` + +- [ ] **Step 5: 修改 _buildKeywordsSection 方法** + +```dart +Widget _buildKeywordsSection() { + final isLoading = widget.sectionLoadingStates?['keywords'] ?? false; + + if (isLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(3, (index) => SkeletonContainer( + width: 60, + height: 24, + borderRadius: 12, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + )), + ), + ); + } + + // 原有的非加载状态代码保持不变 + // ... +} +``` + +- [ ] **Step 6: 修改 _buildIntroductionSection 方法** + +```dart +Widget _buildIntroductionSection() { + final isLoading = widget.sectionLoadingStates?['introduction'] ?? false; + + if (isLoading) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SkeletonContainer( + width: 80, + height: 18, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + const SizedBox(height: 12), + ...List.generate(3, (index) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: SkeletonContainer( + width: double.infinity, + height: 16, + borderRadius: 4, + baseColor: widget.isDark ? Colors.grey[800] : Colors.grey[300], + highlightColor: widget.isDark ? Colors.grey[700] : Colors.grey[100], + ), + )), + ], + ), + ); + } + + // 原有的非加载状态代码保持不变 + // ... +} +``` + +- [ ] **Step 7: 提交代码** + +```bash +git add lib/views/home/home_part.dart +git commit -m "feat: PoetryCard 组件添加骨架屏支持,修复闪白问题" +``` + +--- + +## Task 3: 测试验证 + +**Files:** +- Test: 运行应用验证 + +- [ ] **Step 1: 运行 Flutter 分析** + +```bash +flutter analyze lib/views/home/home_part.dart lib/views/home/components/skeleton_widgets.dart +``` + +Expected: 无错误 + +- [ ] **Step 2: 测试功能** + +1. 打开主页 +2. 点击"下一条"按钮 +3. 验证: + - 卡片不出现闪白 + - 各区域显示骨架屏动画 + - 内容加载完成后平滑显示 + - 深色模式下骨架屏颜色正确 + +- [ ] **Step 3: 提交代码** + +```bash +git add . +git commit -m "test: 验证骨架屏功能正常" +``` + +--- + +## 实现要点总结 + +1. **骨架屏动画**:使用渐变动画实现脉冲效果 +2. **深色模式支持**:根据 `isDark` 参数调整骨架屏颜色 +3. **分区域加载**:每个区域独立控制骨架屏显示 +4. **平滑过渡**:骨架屏到实际内容的切换无闪烁 diff --git a/docs/superpowers/plans/2026-04-03-poetry-theme-refactor.md b/docs/superpowers/plans/2026-04-03-poetry-theme-refactor.md new file mode 100644 index 0000000..5845b3a --- /dev/null +++ b/docs/superpowers/plans/2026-04-03-poetry-theme-refactor.md @@ -0,0 +1,663 @@ +# 诗词答题页面主题色支持与代码重构实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 将诗词答题相关页面重构以支持动态主题色,并将代码分流到 `poetry-page.dart`,实现 UI 和逻辑的分离。 + +**Architecture:** 采用组件化分流策略,将 UI 组件(选项、标签、布局)提取到 `poetry-page.dart`,主页面保留状态管理和业务逻辑。使用 GetX 的 `Obx` 进行响应式主题色更新。 + +**Tech Stack:** Flutter, GetX, ThemeController, ThemeColors + +--- + +## 文件结构 + +**创建文件:** +- `lib/views/profile/level/poetry-page.dart` - UI 组件(选项、标签、布局) + +**修改文件:** +- `lib/views/profile/level/poetry.dart` - 主页面(移除 UI 组件方法,使用新组件) +- `lib/views/profile/level/flow-anim.dart` - 流动边框动画(添加主题色支持) +- `lib/views/profile/level/distinguish.dart` - 答题记录页面(添加主题色支持) + +--- + +## Task 1: 创建 poetry-page.dart 文件并实现 PoetryOptionItem 组件 + +**Files:** +- Create: `lib/views/profile/level/poetry-page.dart` + +- [ ] **Step 1: 创建 poetry-page.dart 文件并添加导入** + +```dart +/// 诗词答题页面组件 +library; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../../services/get/theme_controller.dart'; +import '../../../models/colors/theme_colors.dart'; +import '../../../constants/app_constants.dart'; +``` + +- [ ] **Step 2: 实现 PoetryOptionItem 组件** + +```dart +/// 单个选项组件 +class PoetryOptionItem extends StatelessWidget { + final dynamic option; + final bool isSelected; + final bool isCorrect; + final bool isWrong; + final bool isSubmitting; + final bool showFeedback; + final bool isAnswerCorrect; + final Function(int) onTap; + + const PoetryOptionItem({ + super.key, + required this.option, + required this.isSelected, + required this.isCorrect, + required this.isWrong, + required this.isSubmitting, + required this.showFeedback, + required this.isAnswerCorrect, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final primaryColor = ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, + ); + + final optionNum = option['index'] ?? option['num'] ?? 0; + + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + child: Container( + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + colors: isCorrect + ? [Colors.green[400]!, Colors.green[300]!] + : isWrong + ? [Colors.red[400]!, Colors.red[300]!] + : [primaryColor, primaryColor.withAlpha(200)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + color: isSelected + ? null + : isDark + ? const Color(0xFF2A2A2A) + : Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected + ? Colors.transparent + : primaryColor.withAlpha(50), + width: 2, + ), + boxShadow: isSelected + ? [ + BoxShadow( + color: (isCorrect + ? Colors.green + : isWrong + ? Colors.red + : primaryColor) + .withAlpha(80), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ] + : [ + BoxShadow( + color: isDark + ? Colors.white.withAlpha(5) + : Colors.black.withAlpha(5), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: isSubmitting || (showFeedback && isAnswerCorrect) + ? null + : () { + if (showFeedback) { + onTap(-1); // -1 表示重置状态 + } else { + onTap(optionNum); + } + }, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 300), + width: 32, + height: 32, + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + colors: [ + Colors.white, + Colors.white.withAlpha(230), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + color: isSelected + ? null + : primaryColor.withAlpha(20), + shape: BoxShape.circle, + boxShadow: isSelected + ? [ + BoxShadow( + color: Colors.black.withAlpha(20), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ] + : null, + ), + child: Center( + child: Text( + '$optionNum', + style: TextStyle( + color: isSelected + ? (isCorrect + ? Colors.green + : isWrong + ? Colors.red + : primaryColor) + : primaryColor, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + option['content'] ?? option['text'] ?? '', + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: isSelected + ? Colors.white + : isDark + ? Colors.grey[300] + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + isCorrect + ? Icons.check_circle + : isWrong + ? Icons.cancel + : Icons.radio_button_checked, + color: Colors.white, + size: 28, + ), + ], + ), + ), + ), + ), + ), + ); + }); + } +} +``` + +- [ ] **Step 3: 保存文件** + +--- + +## Task 2: 实现 PoetryOptionsLayout 组件 + +**Files:** +- Modify: `lib/views/profile/level/poetry-page.dart` + +- [ ] **Step 1: 在 poetry-page.dart 中添加 PoetryOptionsLayout 组件** + +```dart +/// 选项布局组件 +class PoetryOptionsLayout extends StatelessWidget { + final List options; + final int? selectedAnswer; + final bool showFeedback; + final bool isAnswerCorrect; + final bool isSubmitting; + final Function(int) onTap; + + const PoetryOptionsLayout({ + super.key, + required this.options, + required this.selectedAnswer, + required this.showFeedback, + required this.isAnswerCorrect, + required this.isSubmitting, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (options.isEmpty) { + return const SizedBox(); + } + + // 检查是否所有选项都少于等于4个字 + bool allShortOptions = options.every((option) { + final text = option['content'] ?? ''; + return text.length <= 4; + }); + + if (allShortOptions && options.length >= 4) { + // 2*2布局 + return Column( + children: [ + Row( + children: [ + Expanded( + child: _buildOptionItem(options[0]), + ), + const SizedBox(width: 12), + Expanded( + child: _buildOptionItem(options[1]), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildOptionItem(options[2]), + ), + const SizedBox(width: 12), + Expanded( + child: _buildOptionItem(options[3]), + ), + ], + ), + ], + ); + } else { + // 1*4布局 + final List optionWidgets = []; + for (int i = 0; i < options.length; i++) { + optionWidgets.add(_buildOptionItem(options[i])); + if (i < options.length - 1) { + optionWidgets.add(const SizedBox(height: 12)); + } + } + return Column(children: optionWidgets); + } + } + + Widget _buildOptionItem(dynamic option) { + final optionNum = option['index'] ?? option['num'] ?? 0; + final isSelected = selectedAnswer == optionNum; + final isCorrect = showFeedback && isAnswerCorrect && selectedAnswer == optionNum; + final isWrong = showFeedback && !isAnswerCorrect && selectedAnswer == optionNum; + + return PoetryOptionItem( + option: option, + isSelected: isSelected, + isCorrect: isCorrect, + isWrong: isWrong, + isSubmitting: isSubmitting, + showFeedback: showFeedback, + isAnswerCorrect: isAnswerCorrect, + onTap: onTap, + ); + } +} +``` + +- [ ] **Step 2: 保存文件** + +--- + +## Task 3: 实现 PoetryTag 组件 + +**Files:** +- Modify: `lib/views/profile/level/poetry-page.dart` + +- [ ] **Step 1: 在 poetry-page.dart 中添加 PoetryTag 组件** + +```dart +/// 标签组件 +class PoetryTag extends StatelessWidget { + final String label; + final String value; + + const PoetryTag({ + super.key, + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + if (value.isEmpty) return const SizedBox(); + + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final primaryColor = ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, + ); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + label, + style: TextStyle( + fontSize: 10, + color: primaryColor, + fontWeight: FontWeight.w600, + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[300] : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + }); + } +} +``` + +- [ ] **Step 2: 保存文件** + +--- + +## Task 4: 修改 poetry.dart 主页面 + +**Files:** +- Modify: `lib/views/profile/level/poetry.dart` + +- [ ] **Step 1: 在 poetry.dart 顶部添加导入** + +在文件顶部的导入部分添加: + +```dart +import 'poetry-page.dart'; +``` + +- [ ] **Step 2: 移除 _buildOptionItem 方法** + +删除 `_PoetryLevelPageState` 类中的 `_buildOptionItem` 方法(约 100 行代码)。 + +- [ ] **Step 3: 移除 _buildOptionsLayout 方法** + +删除 `_PoetryLevelPageState` 类中的 `_buildOptionsLayout` 方法(约 40 行代码)。 + +- [ ] **Step 4: 移除 _buildTag 方法** + +删除 `_PoetryLevelPageState` 类中的 `_buildTag` 方法(约 30 行代码)。 + +- [ ] **Step 5: 替换 _buildOptionsLayout 调用** + +在 `build` 方法中,找到原来的 `_buildOptionsLayout()` 调用,替换为: + +```dart +PoetryOptionsLayout( + options: _currentQuestion!['options'] as List, + selectedAnswer: _selectedAnswer, + showFeedback: _showFeedback, + isAnswerCorrect: _isAnswerCorrect, + isSubmitting: _isSubmitting, + onTap: (optionNum) { + if (optionNum == -1) { + // 重置状态 + setState(() { + _showFeedback = false; + _selectedAnswer = null; + _feedbackMessage = null; + }); + } else { + _submitAnswer(optionNum); + } + }, +) +``` + +- [ ] **Step 6: 替换 _buildTag 调用** + +在 `build` 方法中,找到原来的 `_buildTag()` 调用,替换为: + +```dart +PoetryTag( + label: '类型', + value: _currentQuestion!['type']?.toString() ?? '', +) +``` + +类似地替换其他标签调用。 + +- [ ] **Step 7: 添加主题色支持到分数显示** + +找到分数显示的 `Container`,将 `AppConstants.primaryColor` 替换为: + +```dart +final themeController = Get.find(); +final primaryColor = ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, +); +``` + +然后在 `Container` 的 `decoration` 中使用 `primaryColor`。 + +- [ ] **Step 8: 保存文件** + +--- + +## Task 5: 修改 flow-anim.dart 添加主题色支持 + +**Files:** +- Modify: `lib/views/profile/level/flow-anim.dart` + +- [ ] **Step 1: 在 flow-anim.dart 顶部添加导入** + +```dart +import '../../../models/colors/theme_colors.dart'; +``` + +- [ ] **Step 2: 修改 FlowingBorderContainer 的 build 方法** + +将 `build` 方法修改为: + +```dart +@override +Widget build(BuildContext context) { + final themeController = Get.find(); + + return Obx(() { + final color = widget.color ?? ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, + ); + + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Container( + padding: EdgeInsets.all(widget.width), + decoration: FlowingBorderDecoration( + animation: _animation, + color: color, + width: widget.width, + ), + child: widget.child, + ); + }, + ); + }); +} +``` + +- [ ] **Step 3: 保存文件** + +--- + +## Task 6: 修改 distinguish.dart 添加主题色支持 + +**Files:** +- Modify: `lib/views/profile/level/distinguish.dart` + +- [ ] **Step 1: 在 distinguish.dart 顶部添加导入** + +```dart +import '../../../models/colors/theme_colors.dart'; +``` + +- [ ] **Step 2: 在 _DistinguishPageState 类中添加 ThemeController** + +在 `_DistinguishPageState` 类中添加: + +```dart +final ThemeController _themeController = Get.find(); +``` + +- [ ] **Step 3: 替换所有 AppConstants.primaryColor 为动态主题色** + +在 `build` 方法中,将所有 `AppConstants.primaryColor` 替换为: + +```dart +final primaryColor = ThemeColors.getThemeColor( + _themeController.themeColorIndexRx.value, +); +``` + +然后在需要的地方使用 `primaryColor`。 + +- [ ] **Step 4: 添加深色模式支持** + +在 `build` 方法中添加: + +```dart +final isDark = _themeController.isDarkModeRx.value; +``` + +然后根据 `isDark` 调整背景色、文字颜色等。 + +- [ ] **Step 5: 保存文件** + +--- + +## Task 7: 测试功能 + +**Files:** +- Test: 运行应用并测试所有功能 + +- [ ] **Step 1: 运行应用** + +```bash +flutter run +``` + +- [ ] **Step 2: 测试答题页面** + +1. 进入诗词答题页面 +2. 点击选项,验证选项点击正常 +3. 提交答案,验证反馈显示正常 +4. 测试 2x2 和 1x4 布局切换 +5. 测试上一题、下一题功能 + +- [ ] **Step 3: 测试主题色切换** + +1. 进入设置页面 +2. 切换主题色 +3. 返回答题页面,验证所有组件颜色同步更新 + +- [ ] **Step 4: 测试深色模式** + +1. 切换深色模式 +2. 验证答题页面颜色正常 +3. 验证选项、标签等组件颜色正常 + +- [ ] **Step 5: 测试关怀模式** + +1. 开启关怀模式 +2. 进入答题页面 +3. 验证底部导航栏不遮挡内容 +4. 验证答题功能正常 + +--- + +## Task 8: 提交代码 + +**Files:** +- Commit: 所有修改的文件 + +- [ ] **Step 1: 检查修改的文件** + +```bash +git status +``` + +- [ ] **Step 2: 添加所有修改的文件** + +```bash +git add lib/views/profile/level/poetry.dart +git add lib/views/profile/level/poetry-page.dart +git add lib/views/profile/level/flow-anim.dart +git add lib/views/profile/level/distinguish.dart +``` + +- [ ] **Step 3: 提交代码** + +```bash +git commit -m "feat: 诗词答题页面主题色支持与代码重构 + +- 创建 poetry-page.dart,提取 UI 组件(PoetryOptionItem、PoetryOptionsLayout、PoetryTag) +- 修改 poetry.dart,使用新组件,添加主题色支持 +- 修改 flow-anim.dart,添加主题色支持 +- 修改 distinguish.dart,添加主题色支持 +- 支持动态主题色切换 +- 支持深色模式 +- 保持页面布局不变" +``` + +--- + +## 注意事项 + +1. **保持布局不变**:重构过程中不修改页面布局 +2. **代码平衡**:确保两个文件代码量相近 +3. **性能优化**:使用 `Obx` 进行响应式更新,避免不必要的重建 +4. **向后兼容**:确保现有功能不受影响 +5. **测试充分**:测试所有功能,包括主题色切换、深色模式、关怀模式 diff --git a/docs/superpowers/specs/2026-04-03-home-poetry-card-skeleton-design.md b/docs/superpowers/specs/2026-04-03-home-poetry-card-skeleton-design.md new file mode 100644 index 0000000..f719d07 --- /dev/null +++ b/docs/superpowers/specs/2026-04-03-home-poetry-card-skeleton-design.md @@ -0,0 +1,59 @@ +# 主页诗词卡片骨架屏设计文档 + +## 问题描述 + +当前主页诗词卡片在点击加载下一条时会出现闪白现象,原因是: +1. `poetryData.value = newPoetryData` 触发整个卡片重建 +2. 重建过程中卡片内容短暂消失,显示白色背景 +3. 用户体验不佳,应该有平滑的过渡效果 + +## 解决方案 + +### 方案:骨架屏过渡(推荐) + +在加载新数据时,保持卡片容器显示,使用骨架屏(Skeleton)代替实际内容,实现平滑过渡。 + +**核心思路:** +1. 卡片容器始终存在,不随数据变化而销毁重建 +2. 每个区域(标题、诗句、原文、关键词、译文)独立控制加载状态 +3. 加载时使用骨架屏动画,加载完成后显示实际内容 +4. 避免整个卡片的闪白现象 + +**技术实现:** +1. 修改 `PoetryCard` 组件,添加骨架屏支持 +2. 使用 `Shimmer` 效果或自定义骨架动画 +3. 保持卡片背景色在加载过程中不变 +4. 分区域加载,逐步显示内容 + +## 文件修改 + +### 1. home_part.dart +- 修改 `PoetryCard` 组件 +- 添加骨架屏 Widget +- 优化加载状态的显示逻辑 + +### 2. home_controller.dart(如有需要) +- 调整加载状态的管理 +- 确保骨架屏在正确时机显示 + +## 设计要点 + +1. **骨架屏样式** + - 使用与卡片背景对比的颜色 + - 添加脉冲动画效果 + - 保持与内容区域相同的尺寸 + +2. **过渡动画** + - 骨架屏到实际内容的淡入淡出 + - 避免突兀的切换 + +3. **深色模式支持** + - 骨架屏颜色适配深色/浅色模式 + - 保持视觉一致性 + +## 预期效果 + +- 点击下一条时,卡片保持显示 +- 各区域显示骨架屏动画 +- 数据加载完成后,内容平滑过渡显示 +- 无闪白现象,用户体验流畅 diff --git a/docs/superpowers/specs/2026-04-03-poetry-theme-refactor-design.md b/docs/superpowers/specs/2026-04-03-poetry-theme-refactor-design.md new file mode 100644 index 0000000..53e54d7 --- /dev/null +++ b/docs/superpowers/specs/2026-04-03-poetry-theme-refactor-design.md @@ -0,0 +1,185 @@ +# 诗词答题页面主题色支持与代码重构设计文档 + +**日期:** 2026-04-03 +**状态:** 待实现 +**作者:** AI Assistant + +## 1. 概述 + +### 1.1 目标 + +将诗词答题相关页面重构以支持动态主题色,并将代码分流到 `poetry-page.dart`,实现 UI 和逻辑的分离。 + +### 1.2 范围 + +涉及文件: +- `lib/views/profile/level/poetry.dart` - 主页面(状态管理、网络请求) +- `lib/views/profile/level/poetry-page.dart` - UI 组件(选项、标签、布局) +- `lib/views/profile/level/flow-anim.dart` - 流动边框动画 +- `lib/views/profile/level/distinguish.dart` - 答题记录页面 +- `lib/views/profile/level/level-jilu.dart` - 业务逻辑管理器(无需修改) + +## 2. 架构设计 + +### 2.1 文件结构 + +``` +lib/views/profile/level/ +├── poetry.dart # 主页面(状态管理、网络请求) +├── poetry-page.dart # UI 组件(选项、标签、布局) +├── level-jilu.dart # 业务逻辑管理器 +├── flow-anim.dart # 流动边框动画 +└── distinguish.dart # 答题记录页面 +``` + +### 2.2 职责划分 + +**poetry.dart 职责:** +- `PoetryLevelPage` 类(StatefulWidget) +- `_PoetryLevelPageState` 类 +- 状态变量管理 +- 生命周期方法 +- 业务逻辑方法(网络请求、数据处理) +- `build` 方法的主框架 + +**poetry-page.dart 职责:** +- UI 组件定义 +- 主题色支持 +- 深色模式适配 +- 组件复用 + +## 3. 组件设计 + +### 3.1 PoetryOptionItem 组件 + +**功能:** 单个答题选项组件 + +**参数:** +- `option`: dynamic - 选项数据 +- `isSelected`: bool - 是否被选中 +- `isCorrect`: bool - 是否正确 +- `isWrong`: bool - 是否错误 +- `isSubmitting`: bool - 是否正在提交 +- `showFeedback`: bool - 是否显示反馈 +- `isAnswerCorrect`: bool - 答案是否正确 +- `onTap`: Function(int) - 点击回调 + +**主题色支持:** +- 使用 `ThemeColors.getThemeColor(themeController.themeColorIndexRx.value)` 获取主题色 +- 正确答案使用 `Colors.green` +- 错误答案使用 `Colors.red` +- 背景色根据深色模式动态调整 + +### 3.2 PoetryOptionsLayout 组件 + +**功能:** 选项布局组件(支持 2x2 和 1x4 布局) + +**参数:** +- `options`: List - 选项列表 +- `selectedAnswer`: int? - 选中的答案 +- `showFeedback`: bool - 是否显示反馈 +- `isAnswerCorrect`: bool - 答案是否正确 +- `isSubmitting`: bool - 是否正在提交 +- `onTap`: Function(int) - 点击回调 + +**布局逻辑:** +- 检查所有选项是否都少于等于4个字 +- 如果是且选项数>=4,使用 2x2 布局 +- 否则使用 1x4 布局 + +### 3.3 PoetryTag 组件 + +**功能:** 标签组件(显示题目类型、年级、朝代) + +**参数:** +- `label`: String - 标签名称 +- `value`: String - 标签值 + +**主题色支持:** +- 使用主题色作为背景色和文字颜色 + +## 4. 主题色支持策略 + +### 4.1 颜色替换规则 + +**替换:** +- `AppConstants.primaryColor` → `ThemeColors.getThemeColor(themeController.themeColorIndexRx.value)` + +**保留:** +- 正确答案:`Colors.green` / `AppColors.iosGreen` +- 错误答案:`Colors.red` / `AppColors.iosRed` +- 背景色:根据深色模式动态调整 + +### 4.2 深色模式支持 + +**判断方式:** +- 使用 `themeController.isDarkModeRx.value` 判断深色模式 + +**颜色调整:** +- 背景色:`isDark ? Color(0xFF1A1A1A) : Colors.white` +- 文字颜色:`isDark ? Colors.grey[300] : Colors.grey[700]` +- 边框颜色:`isDark ? Colors.white.withAlpha(20) : Colors.black.withAlpha(10)` + +## 5. 实现步骤 + +### 5.1 创建 poetry-page.dart + +1. 创建 `PoetryOptionItem` 组件 +2. 创建 `PoetryOptionsLayout` 组件 +3. 创建 `PoetryTag` 组件 +4. 添加主题色支持 +5. 添加深色模式支持 + +### 5.2 修改 poetry.dart + +1. 移除 `_buildOptionItem` 方法 +2. 移除 `_buildOptionsLayout` 方法 +3. 移除 `_buildTag` 方法 +4. 导入 `poetry-page.dart` +5. 使用新组件替换原有方法 +6. 添加主题色支持 + +### 5.3 修改 flow-anim.dart + +1. 添加主题色支持 +2. 使用 `ThemeColors.getThemeColor()` 替换硬编码颜色 + +### 5.4 修改 distinguish.dart + +1. 添加主题色支持 +2. 使用 `ThemeColors.getThemeColor()` 替换硬编码颜色 + +## 6. 测试要点 + +### 6.1 功能测试 + +- 选项点击正常 +- 答案提交正常 +- 反馈显示正常 +- 布局切换正常(2x2 和 1x4) + +### 6.2 主题色测试 + +- 主题色切换正常 +- 所有组件颜色同步更新 +- 深色模式切换正常 + +### 6.3 兼容性测试 + +- 关怀模式正常显示 +- 底部导航栏不遮挡内容 +- 动画效果正常 + +## 7. 注意事项 + +1. **保持布局不变**:重构过程中不修改页面布局 +2. **代码平衡**:确保两个文件代码量相近 +3. **性能优化**:使用 `Obx` 进行响应式更新,避免不必要的重建 +4. **向后兼容**:确保现有功能不受影响 + +## 8. 后续优化 + +1. 考虑将更多组件提取到 `poetry-page.dart` +2. 添加单元测试 +3. 优化性能,减少不必要的重建 +4. 添加更多主题色选项 diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index bad5b72..e6c97a7 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -1,14 +1,49 @@ /// 时间: 2025-03-21 /// 功能: 应用配置文件 /// 介绍: 包含应用的所有配置常量,包括响应式布局、主题、API等配置 -/// 最新变化: 从根目录config移动到lib/config目录 +/// 最新变化: 从根目录config移动到lib/config目录,使用package_info_plus动态获取版本号 + +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:get/get.dart'; class AppConfig { // 应用基础配置 static const String appName = '情景诗词'; - static const String appVersion = '1.3.31'; - static const String appDescription = 'A new Flutter project'; - static const int appVersionCode = 26040206; + static const String _fallbackAppVersion = '1.4.1'; + static const int _fallbackAppVersionCode = 26040202; + static const String appDescription = '腹有诗书气自华'; + + static PackageInfo? _packageInfo; + static bool _isInitialized = false; + static final RxString _rxAppVersion = _fallbackAppVersion.obs; + static final RxInt _rxAppVersionCode = _fallbackAppVersionCode.obs; + + static Future init() async { + if (_isInitialized) return; + try { + _packageInfo = await PackageInfo.fromPlatform(); + _rxAppVersion.value = _packageInfo?.version ?? _fallbackAppVersion; + _rxAppVersionCode.value = + int.tryParse(_packageInfo?.buildNumber ?? '') ?? + _fallbackAppVersionCode; + _isInitialized = true; + } catch (e) { + print('获取版本信息失败: $e'); + _packageInfo = null; + _isInitialized = false; + } + } + + static String get appVersion { + return _rxAppVersion.value; + } + + static int get appVersionCode { + return _rxAppVersionCode.value; + } + + static RxString get rxAppVersion => _rxAppVersion; + static RxInt get rxAppVersionCode => _rxAppVersionCode; // 响应式布局断点 static const double mobileBreakpoint = 768.0; diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index 7a03165..b59043c 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; class AppConstants { // 应用信息 static const String appName = '情景诗词'; - static const String appVersion = '1.3.25'; // 响应式断点 static const double mobileBreakpoint = 768.0; diff --git a/lib/controllers/settings/suggestions.dart b/lib/controllers/settings/suggestions.dart index 3741f1b..a297a74 100644 --- a/lib/controllers/settings/suggestions.dart +++ b/lib/controllers/settings/suggestions.dart @@ -1,10 +1,17 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; import '../../services/get/theme_controller.dart'; +import '../../models/colors/app_colors.dart'; import '../../views/profile/theme/app-diy.dart'; import '../../views/footprint/collect_notes.dart'; import '../../views/profile/app-info.dart'; +import '../../views/home/care/care-page.dart'; +import '../../views/profile/guide/beginner_page.dart'; +import '../../views/active/active_search_page.dart'; +import '../../views/active/rate.dart'; +import '../../views/profile/components/entire_page.dart'; class SuggestionsCard extends StatefulWidget { const SuggestionsCard({super.key}); @@ -16,159 +23,389 @@ class SuggestionsCard extends StatefulWidget { class _SuggestionsCardState extends State { final ThemeController _themeController = Get.find(); bool _showAlternativeContent = false; + final PageController _pageController = PageController(); + int _currentPage = 0; + late List> _actionItems; + + @override + void initState() { + super.initState(); + _shuffleActionItems(); + } + + /// 随机排序选项 + void _shuffleActionItems() { + _actionItems = [ + { + 'title': '主题风格', + 'subtitle': '自定义应用主题', + 'icon': Icons.palette, + 'page': const AppDiyPage(), + }, + { + 'title': '创建笔记', + 'subtitle': '快速创建新笔记', + 'icon': Icons.note_add, + 'page': const CollectNotesPage(), + }, + { + 'title': '关怀模式', + 'subtitle': '大字简洁模式', + 'icon': Icons.favorite, + 'page': const CarePage(), + }, + { + 'title': '使用教程', + 'subtitle': '软件功能指南', + 'icon': Icons.menu_book, + 'page': const BeginnerPage(), + }, + { + 'title': '诗词搜索', + 'subtitle': '全站诗词搜索', + 'icon': Icons.search, + 'page': const ActiveSearchPage(), + }, + { + 'title': '调试页面', + 'subtitle': '活跃数据调试', + 'icon': Icons.bug_report, + 'page': const RatePage(), + }, + { + 'title': ' 搜索设置', + 'subtitle': '搜索软件内的设置', + 'icon': Icons.search, + 'page': const _PlaceholderPage(title: '搜索设置'), + }, + { + 'title': '统计', + 'subtitle': '全站数据统计', + 'icon': Icons.bar_chart, + 'page': const EntirePage(), + }, + { + 'title': '了解软件', + 'subtitle': '查看应用信息', + 'icon': Icons.info, + 'page': const AppInfoPage(), + }, + ]; + _actionItems.shuffle(); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + /// 导航到页面并添加返回悬浮按钮 + void _navigateToPage(Widget page) { + Get.to(() => _PageWithBackButton(child: page)); + } @override Widget build(BuildContext context) { - final isDark = _themeController.isDarkMode; + return Obx(() { + final isDark = _themeController.isDarkModeRx.value; + final themeColor = AppColors.primary; - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: isDark ? const Color(0xFF2A2A2A) : Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - GestureDetector( - onTap: () { - setState(() { - _showAlternativeContent = !_showAlternativeContent; - }); - }, - child: Row( - children: [ - Icon( - Icons.settings_suggest, - size: 40, - color: AppConstants.primaryColor, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + GestureDetector( + onTap: () { + setState(() { + _showAlternativeContent = !_showAlternativeContent; + }); + }, + child: Row( + children: [ + Icon(Icons.settings_suggest, size: 40, color: themeColor), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '设置建议', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black, + ), + ), + const SizedBox(height: 4), + Text( + _showAlternativeContent ? '是否在找,清理数据' : '是否在找,主题风格', + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], + ), + ), + GestureDetector( + onTap: () { + final nextPage = (_currentPage + 1) % 3; + _pageController.animateToPage( + nextPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + child: Icon(Icons.refresh, color: themeColor), + ), + ], + ), + ), + const SizedBox(height: 16), + SizedBox( + height: 160, + child: PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPage = index; + }); + }, + children: [ + // 第一页:随机排序后的前4个选项 + Column( children: [ - Text( - '设置建议', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: isDark ? Colors.white : Colors.black, - ), + Row( + children: [ + Expanded( + child: _buildActionButton( + _actionItems[0]['title'], + _actionItems[0]['subtitle'], + _actionItems[0]['icon'], + () => _navigateToPage(_actionItems[0]['page']), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildActionButton( + _actionItems[1]['title'], + _actionItems[1]['subtitle'], + _actionItems[1]['icon'], + () => _navigateToPage(_actionItems[1]['page']), + ), + ), + ], ), - const SizedBox(height: 4), - Text( - _showAlternativeContent ? '是否在找,清理数据' : '是否在找,主题风格', - style: TextStyle( - fontSize: 13, - color: isDark ? Colors.grey[400] : Colors.grey[600], - ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildActionButton( + _actionItems[2]['title'], + _actionItems[2]['subtitle'], + _actionItems[2]['icon'], + () => _navigateToPage(_actionItems[2]['page']), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildActionButton( + _actionItems[3]['title'], + _actionItems[3]['subtitle'], + _actionItems[3]['icon'], + () => _navigateToPage(_actionItems[3]['page']), + ), + ), + ], ), ], ), + // 第二页:随机排序后的中间4个选项 + Column( + children: [ + Row( + children: [ + Expanded( + child: _buildActionButton( + _actionItems[4]['title'], + _actionItems[4]['subtitle'], + _actionItems[4]['icon'], + () => _navigateToPage(_actionItems[4]['page']), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildActionButton( + _actionItems[5]['title'], + _actionItems[5]['subtitle'], + _actionItems[5]['icon'], + () => _navigateToPage(_actionItems[5]['page']), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildActionButton( + _actionItems[6]['title'], + _actionItems[6]['subtitle'], + _actionItems[6]['icon'], + () => _navigateToPage(_actionItems[6]['page']), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildActionButton( + _actionItems[7]['title'], + _actionItems[7]['subtitle'], + _actionItems[7]['icon'], + () => _navigateToPage(_actionItems[7]['page']), + ), + ), + ], + ), + ], + ), + // 第三页:最后一个选项 + Column( + children: [ + Row( + children: [ + Expanded( + child: _buildActionButton( + _actionItems[8]['title'], + _actionItems[8]['subtitle'], + _actionItems[8]['icon'], + () => _navigateToPage(_actionItems[8]['page']), + ), + ), + const SizedBox(width: 12), + Expanded(child: Container()), + ], + ), + ], + ), + ], + ), + ), + const SizedBox(height: 8), + // 页面指示器 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _currentPage == 0 + ? themeColor + : (isDark ? Colors.grey[600] : Colors.grey[300]), + ), + ), + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _currentPage == 1 + ? themeColor + : (isDark ? Colors.grey[600] : Colors.grey[300]), + ), + ), + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _currentPage == 2 + ? themeColor + : (isDark ? Colors.grey[600] : Colors.grey[300]), + ), ), - Icon(Icons.refresh, color: AppConstants.primaryColor), ], ), - ), - const SizedBox(height: 16), - _showAlternativeContent - ? Row( - children: [ - Expanded( - child: _buildActionButton( - '清理数据', - '管理应用数据', - Icons.delete_sweep, - Colors.red, - () => Get.toNamed('/app-data-guide'), - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildActionButton( - '了解软件', - '查看应用信息', - Icons.info, - Colors.purple, - () => Get.to(() => const AppInfoPage()), - ), - ), - ], - ) - : Row( - children: [ - Expanded( - child: _buildActionButton( - '主题风格', - '自定义应用主题', - Icons.palette, - Colors.blue, - () => Get.to(() => const AppDiyPage()), - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildActionButton( - '创建笔记', - '快速创建新笔记', - Icons.note_add, - Colors.green, - () => Get.to(() => const CollectNotesPage()), - ), - ), - ], - ), - ], - ), - ); + ], + ), + ); + }); } Widget _buildActionButton( String title, String subtitle, IconData icon, - Color color, VoidCallback onTap, ) { - final isDark = _themeController.isDarkMode; + final isDark = _themeController.isDarkModeRx.value; + final themeColor = AppColors.primary; return InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(12), child: Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: color.withValues(alpha: 0.2), width: 1), + color: isDark ? const Color(0xFF333333) : const Color(0xFFF5F5F7), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: themeColor.withValues(alpha: 0.2), + width: 1, + ), ), - child: Column( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Icon(icon, color: color, size: 24), - const SizedBox(height: 4), - Text( - title, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: isDark ? Colors.white : Colors.black, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black, + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + style: TextStyle( + fontSize: 11, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ), ), - const SizedBox(height: 2), - Text( - subtitle, - style: TextStyle( - fontSize: 11, - color: isDark ? Colors.grey[400] : Colors.grey[600], + const SizedBox(width: 8), + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: themeColor.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), ), - textAlign: TextAlign.center, + child: Icon(icon, color: themeColor, size: 20), ), ], ), @@ -176,3 +413,171 @@ class _SuggestionsCardState extends State { ); } } + +/// 页面包装器 - 添加左侧返回悬浮按钮 +class _PageWithBackButton extends StatefulWidget { + final Widget child; + + const _PageWithBackButton({required this.child}); + + @override + State<_PageWithBackButton> createState() => _PageWithBackButtonState(); +} + +class _PageWithBackButtonState extends State<_PageWithBackButton> { + bool _showBackButton = true; + Timer? _hideTimer; + + @override + void initState() { + super.initState(); + _startHideTimer(); + } + + void _startHideTimer() { + _hideTimer?.cancel(); + _hideTimer = Timer(const Duration(seconds: 5), () { + if (mounted) { + setState(() { + _showBackButton = false; + }); + } + }); + } + + @override + void dispose() { + _hideTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final themeColor = AppColors.primary; + + return Scaffold( + body: Stack( + children: [ + // 原始页面内容 + widget.child, + + // 左侧返回悬浮按钮 + if (_showBackButton) + Positioned( + left: 0, + top: MediaQuery.of(context).size.height * 0.45, + child: GestureDetector( + onTap: () => Get.back(), + child: AnimatedOpacity( + opacity: _showBackButton ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues( + alpha: isDark ? 0.4 : 0.15, + ), + blurRadius: 8, + offset: const Offset(2, 0), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.arrow_back_ios, + color: themeColor, + size: 18, + ), + const SizedBox(width: 4), + Text( + '返回', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + }); + } +} + +/// 占位页面 - 用于清理数据功能 +class _PlaceholderPage extends StatelessWidget { + final String title; + + const _PlaceholderPage({required this.title}); + + @override + Widget build(BuildContext context) { + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final themeColor = AppColors.primary; + + return Scaffold( + appBar: AppBar( + title: Text(title), + backgroundColor: isDark ? const Color(0xFF1A1A1A) : Colors.white, + foregroundColor: isDark ? Colors.white : Colors.black, + elevation: 0, + ), + backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50], + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.construction, + size: 64, + color: themeColor.withValues(alpha: 0.5), + ), + const SizedBox(height: 16), + Text( + '$title 功能开发中', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Text( + '敬请期待', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/main.dart b/lib/main.dart index fe09b3e..3ac6246 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,9 @@ void main() async { await SharedPreferencesStorageController.init(); + // 初始化 AppConfig,动态获取版本号 + await AppConfig.init(); + // 初始化 ThemeController(在 AppInitializer 之前,确保主题最先加载) Get.put(ThemeController(), permanent: true); diff --git a/lib/models/colors/theme_colors.dart b/lib/models/colors/theme_colors.dart index 2496fba..500d549 100644 --- a/lib/models/colors/theme_colors.dart +++ b/lib/models/colors/theme_colors.dart @@ -8,19 +8,19 @@ import '../../constants/app_constants.dart'; class ThemeColors { // 主题颜色选项 static const List themeColors = [ - AppConstants.primaryColor, // 默认紫色 + Color(0xFF8B4513), // 书褐色 - 传统中国风(默认) + AppConstants.primaryColor, // 紫色 Colors.blue, // 蓝色 Colors.green, // 绿色 Colors.orange, // 橙色 Colors.red, // 红色 Colors.teal, // 青色 - Color(0xFF8B4513), // 书褐色 - 传统中国风 ]; // 强调色选项 static const List accentColors = [ - AppConstants.primaryColor, // 默认紫色 - Colors.yellow, // 黄色 + Colors.yellow, // 黄色(默认) + AppConstants.primaryColor, // 紫色 Colors.pink, // 粉色 Colors.cyan, // 青色 Colors.purple, // 深紫色 diff --git a/lib/services/document/stats.php b/lib/services/document/stats.php index 099013e..8bb3308 100644 --- a/lib/services/document/stats.php +++ b/lib/services/document/stats.php @@ -1,66 +1,353 @@ 0, 'msg' => '', 'data' => null]; -try { - $stats = []; - - $stats['count_category'] = $DB->count('category'); - $stats['count_site'] = $DB->count('site'); - $stats['count_apply'] = $DB->count('apply', array('reject' => 0)); - $stats['count_apply_reject'] = $DB->count('apply', array('reject' => 1)); - $stats['count_article'] = $DB->count('article'); - $stats['count_article_category'] = $DB->count('article_category'); - $stats['count_notice'] = $DB->count('notice'); - $stats['count_link'] = $DB->count('link'); - $stats['count_tags'] = 131; - - $top_hits_day = $DB->find('site', 'id, name', array('date' => date("Y-m-d", time())), '`hits_day` desc'); - $top_hits_month = $DB->find('site', 'id, name', array('datem' => date("Y-m", time())), '`hits_month` desc'); - $top_hits_total = $DB->find('site', 'id, name', null, '`hits_total` desc'); - $top_like = $DB->find('site', 'id, name', null, '`like` desc'); - - $cumulative_hits_result = $DB->query("SELECT SUM(hits_total) as total FROM pre_site")->fetch(); - $cumulative_likes_result = $DB->query("SELECT SUM(`like`) as total FROM pre_site")->fetch(); - - $stats['cumulative_hits'] = $cumulative_hits_result['total'] ?? 0; - $stats['cumulative_likes'] = $cumulative_likes_result['total'] ?? 0; - - $stats['top_hits_day'] = $top_hits_day ? [ - 'id' => $top_hits_day['id'], - 'name' => $top_hits_day['name'] - ] : null; - - $stats['top_hits_month'] = $top_hits_month ? [ - 'id' => $top_hits_month['id'], - 'name' => $top_hits_month['name'] - ] : null; - - $stats['top_hits_total'] = $top_hits_total ? [ - 'id' => $top_hits_total['id'], - 'name' => $top_hits_total['name'] - ] : null; - - $stats['top_like'] = $top_like ? [ - 'id' => $top_like['id'], - 'name' => $top_like['name'] - ] : null; - - $stats['build_time'] = $conf['build_time'] ?? ''; - - echo json_encode([ - 'ok' => true, - 'data' => $stats, - 'timestamp' => time() - ], JSON_UNESCAPED_UNICODE); - -} catch (Exception $e) { - echo json_encode([ - 'ok' => false, - 'error' => $e->getMessage() - ], JSON_UNESCAPED_UNICODE); +$action = $_REQUEST['action'] ?? 'question'; +$id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null; + +switch($action){ + case 'question': + $result['data'] = getQuestion($id ?? getCurrentId()); + break; + case 'next': + $result['data'] = getNextQuestionAuto(); + break; + case 'fetch': + $result['data'] = fetchNewQuestion(); + break; + case 'answer': + $result['data'] = checkAnswer($id, $_REQUEST['answer'] ?? ''); + break; + case 'hint': + $result['data'] = getHint($id); + break; + case 'list': + $result['data'] = getQuestionList(); + break; + case 'refresh': + $result['data'] = refreshCache(); + break; + case 'stats': + $result['data'] = getStats(); + break; + default: + $result['code'] = 400; + $result['msg'] = '未知操作'; } + +echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + +function getStats(){ + global $cacheData; + $cache = loadCache(); + return [ + 'total_questions' => count($cache), + 'cache_file_size' => file_exists(CACHE_FILE) ? round(filesize(CACHE_FILE) / 1024, 2) . ' KB' : 0, + 'last_updated' => file_exists(CACHE_FILE) ? date('Y-m-d H:i:s', filemtime(CACHE_FILE)) : 'N/A', + 'memory_usage' => round(memory_get_peak_usage(true) / 1024, 2) . ' KB' + ]; +} + +function getQuestion($id){ + $cache = loadCache(); + if(empty($cache)){ + return ['error' => '没有题目数据']; + } + if($id < 0 || $id >= count($cache)){ + return ['error' => '题目不存在', 'total' => count($cache)]; + } + $item = $cache[$id]; + return [ + 'id' => $id, + 'total' => count($cache), + 'question' => $item['question_content'], + 'author' => $item['type']['person'] ?? '', + 'type' => $item['type']['type'] ?? '', + 'grade' => $item['type']['grade'] ?? '', + 'dynasty' => $item['type']['dynasty'] ?? '', + 'options' => formatOptions($item['option_answers'] ?? []) + ]; +} + +function getNextQuestionAuto(){ + $cache = loadCache(); + if(empty($cache)){ + return ['error' => '没有题目数据']; + } + $total = count($cache); + $currentId = getCurrentId(); + $nextId = $currentId + 1; + if($nextId >= $total){ + $nextId = 0; + } + setCurrentId($nextId); + $result = getQuestion($nextId); + $result['prev_id'] = $currentId; + return $result; +} + +function getCurrentId(){ + return isset($_SESSION['current_question_id']) ? intval($_SESSION['current_question_id']) : 0; +} + +function setCurrentId($id){ + $_SESSION['current_question_id'] = $id; +} + +function fetchNewQuestion(){ + $apiData = fetchFromBaiduApi(); + + if($apiData === null){ + $cache = loadCache(); + if(!empty($cache)){ + $randomId = array_rand($cache); + $result = formatQuestionItem($randomId, $cache[$randomId], count($cache)); + $result['from_cache'] = true; + $result['api_status'] = 'failed'; + return $result; + } + return ['error' => 'API请求失败且无本地缓存']; + } + + $cache = loadCache(); + $newCount = mergeToCache($apiData, $cache); + + $randomItem = $apiData[array_rand($apiData)]; + $result = formatQuestionItem(null, $randomItem, count($cache)); + $result['from_cache'] = false; + $result['new_questions'] = $newCount; + $result['api_status'] = 'success'; + + return $result; +} + +function formatQuestionItem($id, $item, $total, $fromCache = false, $newCount = 0){ + return [ + 'id' => $id, + 'total' => $total, + 'question' => $item['question_content'], + 'author' => $item['type']['person'] ?? '', + 'type' => $item['type']['type'] ?? '', + 'grade' => $item['type']['grade'] ?? '', + 'dynasty' => $item['type']['dynasty'] ?? '', + 'options' => formatOptions($item['option_answers'] ?? []), + 'from_cache' => $fromCache, + 'new_questions' => $newCount + ]; +} + +function checkAnswer($id, $answer){ + $cache = loadCache(); + if(empty($cache)){ + return ['error' => '没有题目数据']; + } + if($id < 0 || $id >= count($cache)){ + return ['error' => '题目不存在']; + } + $item = $cache[$id]; + $correctAnswer = findAnswer($item['option_answers'] ?? []); + $isCorrect = ($answer === $correctAnswer); + + return [ + 'id' => $id, + 'correct' => $isCorrect, + 'your_answer' => $answer, + 'correct_answer' => $correctAnswer, + 'next_id' => $id + 1, + 'has_next' => $id + 1 < count($cache) + ]; +} + +function getHint($id){ + $cache = loadCache(); + if(empty($cache)){ + return ['error' => '没有题目数据']; + } + if($id < 0 || $id >= count($cache)){ + return ['error' => '题目不存在']; + } + $item = $cache[$id]; + return [ + 'id' => $id, + 'hint' => '这是首描写' . ($item['type']['type'] ?? '') . '的诗,你在' . ($item['type']['grade'] ?? '') . '学过它。', + 'author' => $item['type']['person'] ?? '', + 'dynasty' => $item['type']['dynasty'] ?? '' + ]; +} + +function getQuestionList(){ + $cache = loadCache(); + if(empty($cache)){ + return ['total' => 0, 'list' => []]; + } + $list = []; + foreach($cache as $i => $item){ + $list[] = [ + 'id' => $i, + 'question' => mb_substr($item['question_content'], 0, 30, 'UTF-8') . '...', + 'author' => $item['type']['person'] ?? '', + 'dynasty' => $item['type']['dynasty'] ?? '' + ]; + } + return ['total' => count($list), 'list' => $list]; +} + +function refreshCache(){ + $apiData = fetchFromBaiduApi(); + if($apiData === null){ + return ['refreshed' => false, 'error' => 'API请求失败']; + } + $cache = loadCache(); + $newCount = mergeToCache($apiData, $cache); + return ['refreshed' => true, 'total' => count($cache), 'new_questions' => $newCount]; +} + +function loadCache(){ + global $cacheData; + + if($cacheData !== null){ + return $cacheData; + } + + if(!file_exists(CACHE_FILE)){ + $cacheData = []; + return []; + } + + $content = file_get_contents(CACHE_FILE); + if($content === false){ + $cacheData = []; + return []; + } + + $data = json_decode($content, true); + if(!is_array($data) || !isset($data['questions'])){ + $cacheData = []; + return []; + } + + $cacheData = $data['questions']; + return $cacheData; +} + +function saveCache($questions){ + global $cacheData; + + if(!is_dir(CACHE_DIR)){ + mkdir(CACHE_DIR, 0755, true); + } + + $data = [ + 'updated' => date('Y-m-d H:i:s'), + 'count' => count($questions), + 'questions' => $questions + ]; + + $tempFile = CACHE_FILE . '.tmp'; + $result = file_put_contents($tempFile, json_encode($data, JSON_UNESCAPED_UNICODE), LOCK_EX); + + if($result !== false){ + rename($tempFile, CACHE_FILE); + } + + $cacheData = $questions; +} + +function fetchFromBaiduApi(){ + if(file_exists(API_LOCK_FILE)){ + $lockTime = intval(file_get_contents(API_LOCK_FILE)); + if(time() - $lockTime < API_INTERVAL){ + return null; + } + } + + file_put_contents(API_LOCK_FILE, time()); + + $url = 'https://hanyu.baidu.com/hanyu/ajax/pingce_data'; + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => 3, + CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Accept-Language: zh-CN,zh;q=0.9' + ] + ]); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if($httpCode !== 200 || empty($response)){ + return null; + } + + $json = json_decode($response, true); + if(!isset($json['data']) || !is_array($json['data'])){ + return null; + } + + return $json['data']; +} + +function mergeToCache($newData, &$existingCache){ + if(empty($newData)){ + return 0; + } + + $existingKeys = []; + foreach($existingCache as $item){ + $key = $item['question_content'] ?? ''; + $existingKeys[$key] = true; + } + + $newCount = 0; + foreach($newData as $item){ + $key = $item['question_content'] ?? ''; + if(!isset($existingKeys[$key])){ + $existingCache[] = $item; + $existingKeys[$key] = true; + $newCount++; + } + } + + if($newCount > 0){ + saveCache($existingCache); + } + + return $newCount; +} + +function formatOptions($options){ + $result = []; + foreach($options as $i => $opt){ + $result[] = [ + 'index' => $i + 1, + 'content' => $opt['answer_content'] ?? '' + ]; + } + return $result; +} + +function findAnswer($options){ + foreach($options as $i => $opt){ + if(($opt['is_standard_answer'] ?? 0) === 1){ + return (string)($i + 1); + } + } + return ''; +} +?> diff --git a/lib/services/get/care_controller.dart b/lib/services/get/care_controller.dart index d689325..6a2b3ab 100644 --- a/lib/services/get/care_controller.dart +++ b/lib/services/get/care_controller.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -15,6 +16,8 @@ class CareController extends GetxController { final RxBool isCareButtonVisible = false.obs; final RxInt _careNavigationIndex = 0.obs; + Timer? _autoHideTimer; + bool get isCareModeEnabled => _isCareModeEnabled.value; String get userType => _userType.value; bool get pinyinEnabled => _pinyinEnabled.value; @@ -77,6 +80,40 @@ class CareController extends GetxController { void toggleCareButtonVisibility() { isCareButtonVisible.value = !isCareButtonVisible.value; + + // 如果显示关怀开关,启动3秒自动隐藏定时器 + if (isCareButtonVisible.value) { + _startAutoHideTimer(); + } else { + _cancelAutoHideTimer(); + } + } + + /// 启动自动隐藏定时器 + void _startAutoHideTimer() { + _cancelAutoHideTimer(); + _autoHideTimer = Timer(const Duration(seconds: 3), () { + isCareButtonVisible.value = false; + }); + } + + /// 取消自动隐藏定时器 + void _cancelAutoHideTimer() { + _autoHideTimer?.cancel(); + _autoHideTimer = null; + } + + /// 重置自动隐藏定时器(用户交互时调用) + void resetAutoHideTimer() { + if (isCareButtonVisible.value) { + _startAutoHideTimer(); + } + } + + @override + void onClose() { + _cancelAutoHideTimer(); + super.onClose(); } Future switchCareNavigation(int index) async { diff --git a/lib/services/get/discover_controller.dart b/lib/services/get/discover_controller.dart index 9f1fb99..082b20c 100644 --- a/lib/services/get/discover_controller.dart +++ b/lib/services/get/discover_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../controllers/shared_preferences_storage_controller.dart'; import '../../constants/app_constants.dart'; +import 'theme_controller.dart'; class DiscoverController extends GetxController { var categories = [].obs; @@ -64,7 +65,8 @@ class DiscoverController extends GetxController { Future refreshContent() async { await Future.delayed(const Duration(seconds: 1)); - Get.snackbar('提示', '内容已刷新'); + final themeController = Get.find(); + Get.snackbar('提示', '内容已刷新', colorText: themeController.currentThemeColor); } void toggleTips() { diff --git a/lib/services/get/favorites_controller.dart b/lib/services/get/favorites_controller.dart index 840019f..73f7f40 100644 --- a/lib/services/get/favorites_controller.dart +++ b/lib/services/get/favorites_controller.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; +import 'theme_controller.dart'; class FavoritesController extends GetxController { var categories = ['全部', '点赞', '笔记', '推送', '每日一句'].obs; @@ -24,10 +25,12 @@ class FavoritesController extends GetxController { Future refreshContent() async { await Future.delayed(const Duration(milliseconds: 500)); - Get.snackbar('提示', '内容已刷新'); + final themeController = Get.find(); + Get.snackbar('提示', '内容已刷新', colorText: themeController.currentThemeColor); } void showFilterOptions(BuildContext context) { + final themeController = Get.find(); // 先获取当前值,避免在弹窗中使用 Obx final currentSortByTime = sortByTime.value; final currentLikesFirst = likesFirst.value; @@ -58,7 +61,8 @@ class FavoritesController extends GetxController { sortByTime.value = true; // 使用 Future.delayed 确保弹窗完全关闭后再显示 snackbar Future.delayed(const Duration(milliseconds: 100), () { - Get.snackbar('提示', '已按时间排序'); + final tc = Get.find(); + Get.snackbar('提示', '已按时间排序', colorText: tc.currentThemeColor); }); }, ), @@ -76,7 +80,8 @@ class FavoritesController extends GetxController { sortByTime.value = false; // 使用 Future.delayed 确保弹窗完全关闭后再显示 snackbar Future.delayed(const Duration(milliseconds: 100), () { - Get.snackbar('提示', '已按分类排序'); + final tc = Get.find(); + Get.snackbar('提示', '已按分类排序', colorText: tc.currentThemeColor); }); }, ), @@ -90,7 +95,12 @@ class FavoritesController extends GetxController { likesFirst.value = !likesFirst.value; // 使用 Future.delayed 确保弹窗完全关闭后再显示 snackbar Future.delayed(const Duration(milliseconds: 100), () { - Get.snackbar('提示', likesFirst.value ? '点赞在前' : '笔记在前'); + final tc = Get.find(); + Get.snackbar( + '提示', + likesFirst.value ? '点赞在前' : '笔记在前', + colorText: tc.currentThemeColor, + ); }); }, ), diff --git a/lib/services/get/home_controller.dart b/lib/services/get/home_controller.dart index 00ccba5..fe4f440 100644 --- a/lib/services/get/home_controller.dart +++ b/lib/services/get/home_controller.dart @@ -415,6 +415,7 @@ class HomeController extends GetxController with NetworkListenerMixin { 'keywords': true, 'introduction': true, }; + update(); // 立即通知UI更新,显示骨架屏 try { final offlineDataManager = OfflineDataManager(); @@ -490,7 +491,7 @@ class HomeController extends GetxController with NetworkListenerMixin { } } - // 模拟分步加载过程 + // 模拟分步加载过程 - 简化版,避免闪烁 Future simulateSectionLoading(PoetryData newPoetryData) async { // 记录浏览统计 try { @@ -501,60 +502,27 @@ class HomeController extends GetxController with NetworkListenerMixin { // 忽略错误 } - // 1. 加载标题区域 - var states = {}; - sectionLoadingStates.forEach((key, value) { - states[key] = value; - }); - states['title'] = false; - sectionLoadingStates.assignAll(states); + // 1. 立即更新所有数据 poetryData.value = newPoetryData; - update(); // 通知UI更新 - await Future.delayed(const Duration(milliseconds: 200)); - - // 2. 加载诗句区域 - states = {}; - sectionLoadingStates.forEach((key, value) { - states[key] = value; - }); - states['name'] = false; - sectionLoadingStates.assignAll(states); - update(); // 通知UI更新 - await Future.delayed(const Duration(milliseconds: 200)); - - // 3. 加载原文区域 - states = {}; - sectionLoadingStates.forEach((key, value) { - states[key] = value; - }); - states['content'] = false; - sectionLoadingStates.assignAll(states); - update(); // 通知UI更新 - await Future.delayed(const Duration(milliseconds: 200)); - - // 4. 加载关键词区域 - states = {}; - sectionLoadingStates.forEach((key, value) { - states[key] = value; - }); - states['keywords'] = false; - sectionLoadingStates.assignAll(states); keywordList.value = PoetryDataUtils.extractKeywords(newPoetryData); - update(); // 通知UI更新 - await Future.delayed(const Duration(milliseconds: 200)); - - // 5. 加载译文区域 - states = {}; - sectionLoadingStates.forEach((key, value) { - states[key] = value; - }); - states['introduction'] = false; - sectionLoadingStates.assignAll(states); starDisplay.value = PoetryDataUtils.getStarDisplay(newPoetryData); isLiked.value = false; errorMessage.value = ''; update(); // 通知UI更新 + // 2. 短暂延迟后,一次性关闭所有加载状态 + await Future.delayed(const Duration(milliseconds: 100)); + + // 一次性关闭所有加载状态 + sectionLoadingStates.value = { + 'title': false, + 'content': false, + 'name': false, + 'keywords': false, + 'introduction': false, + }; + update(); + checkIfLiked(); updateCurrentHistoryIndex(); } diff --git a/lib/services/get/profile_controller.dart b/lib/services/get/profile_controller.dart index 1de1645..3015e3e 100644 --- a/lib/services/get/profile_controller.dart +++ b/lib/services/get/profile_controller.dart @@ -12,6 +12,7 @@ import '../../controllers/history_controller.dart'; import '../../controllers/shared_preferences_storage_controller.dart'; import '../isweb/wakelock_service.dart'; import '../../views/profile/guide/tongji.dart'; +import 'theme_controller.dart'; class ProfileController extends GetxController with WidgetsBindingObserver { // 页面状态 @@ -214,9 +215,14 @@ class ProfileController extends GetxController with WidgetsBindingObserver { // === 屏幕常亮相关方法 === Future toggleScreenWake(bool enable) async { + final themeController = Get.find(); // Web 平台不支持 wakelock_plus if (kIsWeb) { - Get.snackbar('提示', 'Web 平台不支持屏幕常亮功能'); + Get.snackbar( + '提示', + 'Web 平台不支持屏幕常亮功能', + colorText: themeController.currentThemeColor, + ); return; } @@ -228,10 +234,18 @@ class ProfileController extends GetxController with WidgetsBindingObserver { if (enable) { await WakelockService.instance.enable(); - Get.snackbar('提示', '屏幕常亮已开启'); + Get.snackbar( + '提示', + '屏幕常亮已开启', + colorText: themeController.currentThemeColor, + ); } else { await WakelockService.instance.disable(); - Get.snackbar('提示', '屏幕常亮已关闭'); + Get.snackbar( + '提示', + '屏幕常亮已关闭', + colorText: themeController.currentThemeColor, + ); } // 不再更新开关状态,由 UI 层直接控制 @@ -324,6 +338,7 @@ class ProfileController extends GetxController with WidgetsBindingObserver { // === 显示提示 === void showSnackBar(String message) { - Get.snackbar('提示', message); + final themeController = Get.find(); + Get.snackbar('提示', message, colorText: themeController.currentThemeColor); } } diff --git a/lib/services/get/tap_liquid_glass_controller.dart b/lib/services/get/tap_liquid_glass_controller.dart index 435b0bb..2d20b64 100644 --- a/lib/services/get/tap_liquid_glass_controller.dart +++ b/lib/services/get/tap_liquid_glass_controller.dart @@ -17,7 +17,7 @@ enum TransparencyLevel { class TapLiquidGlassController extends GetxController { SharedPreferences? _prefs; final _isEnabled = true.obs; - final _transparencyLevel = TransparencyLevel.weak.obs; + final _transparencyLevel = TransparencyLevel.medium.obs; bool get isEnabled => _isEnabled.value; RxBool get isEnabledRx => _isEnabled; @@ -68,7 +68,7 @@ class TapLiquidGlassController extends GetxController { Future _loadSettings() async { _prefs = await SharedPreferences.getInstance(); _isEnabled.value = _prefs?.getBool(AppConfig.keyTapLiquidGlass) ?? true; - final levelIndex = _prefs?.getInt('tap_liquid_glass_transparency') ?? 0; + final levelIndex = _prefs?.getInt('tap_liquid_glass_transparency') ?? 1; _transparencyLevel.value = TransparencyLevel .values[levelIndex.clamp(0, TransparencyLevel.values.length - 1)]; } diff --git a/lib/services/get/theme_controller.dart b/lib/services/get/theme_controller.dart index f54b3e3..0ea223d 100644 --- a/lib/services/get/theme_controller.dart +++ b/lib/services/get/theme_controller.dart @@ -138,6 +138,7 @@ class ThemeController extends GetxController { duration: const Duration(seconds: 2), margin: const EdgeInsets.all(16), borderRadius: 12, + colorText: currentThemeColor, ); } @@ -173,6 +174,7 @@ class ThemeController extends GetxController { duration: const Duration(seconds: 2), margin: const EdgeInsets.all(16), borderRadius: 12, + colorText: currentThemeColor, ); } @@ -266,6 +268,7 @@ class ThemeController extends GetxController { duration: const Duration(seconds: 2), margin: const EdgeInsets.all(16), borderRadius: 12, + colorText: currentThemeColor, ); } diff --git a/lib/utils/audio_manager.dart b/lib/utils/audio_manager.dart index c6b1144..5108441 100644 --- a/lib/utils/audio_manager.dart +++ b/lib/utils/audio_manager.dart @@ -38,12 +38,13 @@ class AudioManager { Future _loadSoundSetting() async { try { final prefs = await SharedPreferences.getInstance(); - bool soundEnabled = prefs.getBool('sound_enabled') ?? true; + // 默认值设为 false,与 app_fun.dart 保持一致 + bool soundEnabled = prefs.getBool('sound_enabled') ?? false; _isMuted = !soundEnabled; print('声音设置: soundEnabled=$soundEnabled, _isMuted=$_isMuted'); } catch (e) { print('加载声音设置失败: $e'); - _isMuted = false; + _isMuted = true; // 默认静音 } } @@ -98,9 +99,17 @@ class AudioManager { } /// 设置静音状态 - void setMuted(bool muted) { + Future setMuted(bool muted) async { _isMuted = muted; print('设置静音状态: $_isMuted'); + // 保存到 SharedPreferences + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('sound_enabled', !muted); + print('已保存声音设置到 SharedPreferences: sound_enabled=${!muted}'); + } catch (e) { + print('保存声音设置失败: $e'); + } } /// 获取静音状态 diff --git a/lib/views/active/category_page.dart b/lib/views/active/category_page.dart index 23ac10a..3ea561e 100644 --- a/lib/views/active/category_page.dart +++ b/lib/views/active/category_page.dart @@ -97,9 +97,13 @@ class CategoryPage extends StatelessWidget { ) { return ListView.separated( padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: data.keys.length, + itemCount: data.keys.length + 1, separatorBuilder: (context, index) => const SizedBox(height: 8), itemBuilder: (context, index) { + if (index == data.keys.length) { + return _buildEndIndicator(isDark, themeColor); + } + final category = data.keys.elementAt(index); final items = data[category]!; @@ -178,6 +182,49 @@ class CategoryPage extends StatelessWidget { ); } + Widget _buildEndIndicator(bool isDark, Color themeColor) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: const EdgeInsets.symmetric(vertical: 24), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 40, + height: 1, + color: isDark ? Colors.grey[700] : Colors.grey[300], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + '到底了', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[500] : Colors.grey[400], + fontWeight: FontWeight.w500, + ), + ), + ), + Container( + width: 40, + height: 1, + color: isDark ? Colors.grey[700] : Colors.grey[300], + ), + ], + ), + const SizedBox(height: 8), + Icon( + Icons.expand_more, + size: 20, + color: isDark ? Colors.grey[600] : Colors.grey[400], + ), + ], + ), + ); + } + Widget _buildCategoryChip( String label, String categoryType, diff --git a/lib/views/discover_page.dart b/lib/views/discover_page.dart index c80e24e..3b9f480 100644 --- a/lib/views/discover_page.dart +++ b/lib/views/discover_page.dart @@ -25,10 +25,12 @@ class DiscoverPage extends StatefulWidget { } class _DiscoverPageState extends State - with SingleTickerProviderStateMixin { + with TickerProviderStateMixin { late TabController _tabController; final controller = Get.put(DiscoverController()); late ThemeController _themeController; + late AnimationController _tipsAnimationController; + late Animation _tipsAnimation; @override void initState() { @@ -40,6 +42,17 @@ class _DiscoverPageState extends State vsync: this, ); + _tipsAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _tipsAnimation = CurvedAnimation( + parent: _tipsAnimationController, + curve: Curves.easeInOut, + ); + + _tipsAnimationController.value = 1.0; + _tabController.addListener(() { setState(() {}); }); @@ -48,6 +61,7 @@ class _DiscoverPageState extends State @override void dispose() { _tabController.dispose(); + _tipsAnimationController.dispose(); super.dispose(); } @@ -130,43 +144,52 @@ class _DiscoverPageState extends State } Widget _buildTopicChips(DiscoverController controller, bool isDark) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - color: isDark ? const Color(0xFF1E1E1E) : Colors.grey[50], - child: Row( - children: [ - Expanded( - child: Text( - '💡 探索更多精彩内容,发现你感兴趣的诗词世界', - style: TextStyle( - color: _themeController.currentThemeColor, - fontSize: 14, - fontWeight: FontWeight.w400, + return SizeTransition( + sizeFactor: _tipsAnimation, + axisAlignment: -1.0, + child: FadeTransition( + opacity: _tipsAnimation, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + color: isDark ? const Color(0xFF1E1E1E) : Colors.grey[50], + child: Row( + children: [ + Expanded( + child: Text( + '💡 探索更多精彩内容,发现你感兴趣的诗词世界', + style: TextStyle( + color: _themeController.currentThemeColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), ), - ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + _tipsAnimationController.reverse(); + await Future.delayed(const Duration(milliseconds: 300)); + controller.toggleTips(); + }, + child: Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: isDark + ? Colors.grey[800] + : Colors.grey.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + size: 16, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ], ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - controller.toggleTips(); - }, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - color: isDark - ? Colors.grey[800] - : Colors.grey.withValues(alpha: 0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.close, - size: 16, - color: isDark ? Colors.grey[400] : Colors.grey[600], - ), - ), - ), - ], + ), ), ); } @@ -430,7 +453,7 @@ class _DiscoverPageState extends State ), const SizedBox(height: 8), Text( - '展示诗词的浏览量和点赞数排行,包括总榜、日榜、月榜三种类型。帮助您发现最受欢迎的诗词作品。', + '统计开源:来自本软件各个平台的数据,安卓 鸿蒙 web 小程序等平台。', style: TextStyle( fontSize: 12, color: isDark ? Colors.grey[400] : Colors.grey[600], diff --git a/lib/views/home/care/care-page.dart b/lib/views/home/care/care-page.dart index 01b69fe..dca170d 100644 --- a/lib/views/home/care/care-page.dart +++ b/lib/views/home/care/care-page.dart @@ -30,7 +30,7 @@ class _CarePageState extends State { backgroundColor: isDark ? const Color(0xFF121212) : Colors.white, appBar: AppBar( title: Text( - '关怀', + '关怀 Beta', style: TextStyle( color: isDark ? Colors.white : primaryColor, fontWeight: FontWeight.bold, @@ -183,27 +183,31 @@ class _CarePageState extends State { ), ), const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildUserTypeButton( - '儿童', - _careController.userType == '儿童', - isDark, - primaryColor, + Obx(() { + final themeController = Get.find(); + final currentPrimaryColor = themeController.currentThemeColor; + return Row( + children: [ + Expanded( + child: _buildUserTypeButton( + '儿童', + _careController.userType == '儿童', + isDark, + currentPrimaryColor, + ), ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildUserTypeButton( - '学生', - _careController.userType == '学生', - isDark, - primaryColor, + const SizedBox(width: 16), + Expanded( + child: _buildUserTypeButton( + '学生', + _careController.userType == '学生', + isDark, + currentPrimaryColor, + ), ), - ), - ], - ), + ], + ); + }), const SizedBox(height: 16), Container( width: double.infinity, diff --git a/lib/views/home/care/care_poetry_page.dart b/lib/views/home/care/care_poetry_page.dart index 544d2e1..58abba6 100644 --- a/lib/views/home/care/care_poetry_page.dart +++ b/lib/views/home/care/care_poetry_page.dart @@ -10,6 +10,7 @@ import '../../../services/get/theme_controller.dart'; import '../../../services/get/care_controller.dart'; import '../../../constants/app_constants.dart'; import '../home_part.dart'; +import '../components/skeleton_widgets.dart'; import 'care_widgets.dart'; import 'pinyin_helper.dart'; @@ -75,15 +76,18 @@ class _CarePoetryPageState extends State final isDark = themeController.isDarkModeRx.value; final primaryColor = themeController.currentThemeColor; final poetryData = homeController.poetryData.value; + final isLoadingNext = homeController.isLoadingNext.value; + + // 当诗词数据变化时,重新播放动画 + if (!isLoadingNext) { + _animationController.reset(); + _animationController.forward(); + } if (poetryData == null) { return const Center(child: CircularProgressIndicator()); } - // 当诗词数据变化时,重新播放动画 - _animationController.reset(); - _animationController.forward(); - return Scaffold( backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50], body: SafeArea( @@ -144,7 +148,12 @@ class _CarePoetryPageState extends State const SizedBox(height: 16), // 出处 if (careController.selectedOptions.contains('出处')) - _buildPoetrySource(poetryData, isDark, primaryColor), + _buildPoetrySource( + poetryData, + isDark, + primaryColor, + isLoadingNext, + ), if (careController.selectedOptions.contains('出处')) const SizedBox(height: 24), if (!careController.selectedOptions.contains('问候语') && @@ -152,7 +161,12 @@ class _CarePoetryPageState extends State const SizedBox(height: 24), // 诗词标题 if (careController.selectedOptions.contains('诗词')) - _buildPoetryTitle(poetryData, isDark, primaryColor), + _buildPoetryTitle( + poetryData, + isDark, + primaryColor, + isLoadingNext, + ), const SizedBox(height: 24), @@ -170,6 +184,7 @@ class _CarePoetryPageState extends State poetryData, isDark, primaryColor, + isLoadingNext, ), ), @@ -186,13 +201,23 @@ class _CarePoetryPageState extends State // 译文 if (careController.selectedOptions.contains('译文')) - _buildPoetryTranslation(poetryData, isDark, primaryColor), + _buildPoetryTranslation( + poetryData, + isDark, + primaryColor, + isLoadingNext, + ), const SizedBox(height: 24), // 更多 if (careController.selectedOptions.contains('更多')) - _buildPoetryMore(poetryData, isDark, primaryColor), + _buildPoetryMore( + poetryData, + isDark, + primaryColor, + isLoadingNext, + ), const SizedBox(height: 48), ], @@ -209,22 +234,48 @@ class _CarePoetryPageState extends State dynamic poetryData, bool isDark, Color primaryColor, + bool isLoading, ) { + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; + return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100], borderRadius: BorderRadius.circular(12), ), - child: Text( - poetryData.name ?? '未知标题', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: primaryColor, - ), - textAlign: TextAlign.center, - ), + child: isLoading + ? Center( + child: Column( + children: [ + SkeletonContainer( + width: 180, + height: 32, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 120, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ), + ) + : Text( + poetryData.name ?? '未知标题', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: primaryColor, + ), + textAlign: TextAlign.center, + ), ); } @@ -232,6 +283,7 @@ class _CarePoetryPageState extends State dynamic poetryData, bool isDark, Color primaryColor, + bool isLoading, ) { final themeController = Get.find(); final careController = Get.find(); @@ -239,6 +291,8 @@ class _CarePoetryPageState extends State final content = poetryData.name ?? ''; final lines = content.split('\n'); final showPinyin = careController.pinyinEnabled; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Container( padding: const EdgeInsets.all(16), @@ -248,95 +302,128 @@ class _CarePoetryPageState extends State : accentColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: lines.asMap().entries.map((entry) { - int index = entry.key; - String line = entry.value; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Column( + child: isLoading + ? Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (showPinyin && line.trim().isNotEmpty) - Wrap( - alignment: WrapAlignment.center, - children: PinyinHelper.convertToCharPinyinList(line) - .asMap() - .entries - .map((charEntry) { - int charIndex = charEntry.key; - var item = charEntry.value; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: AnimatedOpacity( - opacity: 1.0, - duration: Duration( - milliseconds: 300 + charIndex * 100, - ), - curve: Curves.easeOut, - child: Column( - children: [ - Text( - item['pinyin'] ?? '', - style: TextStyle( - fontSize: 12, - color: isDark - ? Colors.grey[400] - : Colors.grey[600], - height: 1.2, - ), - textAlign: TextAlign.center, - ), - Text( - item['char'] ?? '', - style: TextStyle( - fontSize: 20, - color: primaryColor, - height: 1.6, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - ); - }) - .toList(), - ), - if (!showPinyin || line.trim().isEmpty) - Wrap( - alignment: WrapAlignment.center, - children: line.split('').asMap().entries.map(( - charEntry, - ) { - int charIndex = charEntry.key; - String char = charEntry.value; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: AnimatedOpacity( - opacity: 1.0, - duration: Duration( - milliseconds: 300 + charIndex * 100, - ), - curve: Curves.easeOut, - child: Text( - char, - style: TextStyle( - fontSize: 20, - color: primaryColor, - height: 1.6, - ), - textAlign: TextAlign.center, - ), - ), - ); - }).toList(), - ), + SkeletonContainer( + width: double.infinity, + height: 28, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: double.infinity, + height: 28, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: 200, + height: 28, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: lines.asMap().entries.map((entry) { + int index = entry.key; + String line = entry.value; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + children: [ + if (showPinyin && line.trim().isNotEmpty) + Wrap( + alignment: WrapAlignment.center, + children: PinyinHelper.convertToCharPinyinList(line) + .asMap() + .entries + .map((charEntry) { + int charIndex = charEntry.key; + var item = charEntry.value; + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + ), + child: AnimatedOpacity( + opacity: 1.0, + duration: Duration( + milliseconds: 300 + charIndex * 100, + ), + curve: Curves.easeOut, + child: Column( + children: [ + Text( + item['pinyin'] ?? '', + style: TextStyle( + fontSize: 12, + color: isDark + ? Colors.grey[400] + : Colors.grey[600], + height: 1.2, + ), + textAlign: TextAlign.center, + ), + Text( + item['char'] ?? '', + style: TextStyle( + fontSize: 20, + color: primaryColor, + height: 1.6, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + }) + .toList(), + ), + if (!showPinyin || line.trim().isEmpty) + Wrap( + alignment: WrapAlignment.center, + children: line.split('').asMap().entries.map(( + charEntry, + ) { + int charIndex = charEntry.key; + String char = charEntry.value; + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + ), + child: AnimatedOpacity( + opacity: 1.0, + duration: Duration( + milliseconds: 300 + charIndex * 100, + ), + curve: Curves.easeOut, + child: Text( + char, + style: TextStyle( + fontSize: 20, + color: primaryColor, + height: 1.6, + ), + textAlign: TextAlign.center, + ), + ), + ); + }).toList(), + ), + ], + ), + ); + }).toList(), ), - ); - }).toList(), - ), ); } @@ -344,8 +431,11 @@ class _CarePoetryPageState extends State dynamic poetryData, bool isDark, Color primaryColor, + bool isLoading, ) { final url = poetryData.url ?? '未知作者'; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Container( padding: const EdgeInsets.all(16), @@ -353,15 +443,25 @@ class _CarePoetryPageState extends State color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100], borderRadius: BorderRadius.circular(12), ), - child: Text( - url, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: primaryColor, - ), - textAlign: TextAlign.center, - ), + child: isLoading + ? Center( + child: SkeletonContainer( + width: 200, + height: 24, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ) + : Text( + url, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: primaryColor, + ), + textAlign: TextAlign.center, + ), ); } @@ -369,8 +469,11 @@ class _CarePoetryPageState extends State dynamic poetryData, bool isDark, Color primaryColor, + bool isLoading, ) { final translation = poetryData.introduce ?? '暂无译文'; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Container( padding: const EdgeInsets.all(16), @@ -378,46 +481,118 @@ class _CarePoetryPageState extends State color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100], borderRadius: BorderRadius.circular(12), ), - child: Text( - translation, - style: TextStyle( - fontSize: 16, - color: isDark ? Colors.grey[300] : Colors.grey[700], - height: 1.6, - ), - textAlign: TextAlign.center, - ), + child: isLoading + ? Column( + children: [ + SkeletonContainer( + width: 100, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: double.infinity, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: double.infinity, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 200, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ) + : Text( + translation, + style: TextStyle( + fontSize: 16, + color: isDark ? Colors.grey[300] : Colors.grey[700], + height: 1.6, + ), + textAlign: TextAlign.center, + ), ); } - Widget _buildPoetryMore(dynamic poetryData, bool isDark, Color primaryColor) { + Widget _buildPoetryMore( + dynamic poetryData, + bool isDark, + Color primaryColor, + bool isLoading, + ) { + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; + return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.grey[100], borderRadius: BorderRadius.circular(12), ), - child: Column( - children: [ - Text( - '更多信息', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: primaryColor, + child: isLoading + ? Column( + children: [ + SkeletonContainer( + width: 120, + height: 22, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: double.infinity, + height: 14, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 250, + height: 14, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ) + : Column( + children: [ + Text( + '更多信息', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: primaryColor, + ), + ), + const SizedBox(height: 12), + Text( + '诗词解析、背景知识等内容将在此显示', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + textAlign: TextAlign.center, + ), + ], ), - ), - const SizedBox(height: 12), - Text( - '诗词解析、背景知识等内容将在此显示', - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.grey[300] : Colors.grey[700], - ), - textAlign: TextAlign.center, - ), - ], - ), ); } diff --git a/lib/views/home/care/care_widgets.dart b/lib/views/home/care/care_widgets.dart index ccffcdc..57ef134 100644 --- a/lib/views/home/care/care_widgets.dart +++ b/lib/views/home/care/care_widgets.dart @@ -61,30 +61,30 @@ class CareModeToggle extends StatelessWidget { final themeController = Get.find(); final primaryColor = themeController.currentThemeColor; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: isDark ? const Color(0xFF2A2A2A) : Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(isDark ? 40 : 20), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon(Icons.people, color: primaryColor, size: 20), - const SizedBox(width: 12), - GestureDetector( - onTap: () => Get.to(() => const CarePage()), - child: Text( + return GestureDetector( + onTap: () => Get.to(() => const CarePage()), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(isDark ? 40 : 20), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon(Icons.people, color: primaryColor, size: 20), + const SizedBox(width: 12), + Text( '关怀', style: TextStyle( fontSize: 16, @@ -92,16 +92,21 @@ class CareModeToggle extends StatelessWidget { color: isDark ? Colors.white : Colors.black, ), ), + ], + ), + GestureDetector( + onTap: onToggle, + child: Switch( + value: isEnabled, + onChanged: (_) => onToggle(), + activeColor: primaryColor, + inactiveTrackColor: isDark + ? Colors.grey[700] + : Colors.grey[300], ), - ], - ), - Switch( - value: isEnabled, - onChanged: (_) => onToggle(), - activeColor: primaryColor, - inactiveTrackColor: isDark ? Colors.grey[700] : Colors.grey[300], - ), - ], + ), + ], + ), ), ); } diff --git a/lib/views/home/home_page.dart b/lib/views/home/home_page.dart index e60117e..31653aa 100644 --- a/lib/views/home/home_page.dart +++ b/lib/views/home/home_page.dart @@ -25,31 +25,24 @@ class HomePage extends StatefulWidget { State createState() => _HomePageState(); } -class _HomePageState extends State - with SingleTickerProviderStateMixin { +class _HomePageState extends State { final GlobalKey repaintKey = GlobalKey(); final SecondaryButtonsManager _secondaryButtonsManager = SecondaryButtonsManager(); final FloatingButtonsVisibilityManager _floatingButtonsVisibilityManager = FloatingButtonsVisibilityManager(); final CareController _careController = Get.put(CareController()); - late AnimationController _animationController; @override void initState() { super.initState(); _secondaryButtonsManager.init(); _floatingButtonsVisibilityManager.init(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); } @override void dispose() { _floatingButtonsVisibilityManager.dispose(); - _animationController.dispose(); super.dispose(); } @@ -63,35 +56,14 @@ class _HomePageState extends State return Obx(() { final isDark = themeController.isDarkModeRx.value; - // 触发动画 - if (_animationController.status != AnimationStatus.forward) { - _animationController.forward(from: 0); - } - - // 关怀模式开启时显示关怀页面,添加过渡动画 + // 关怀模式开启时显示关怀页面 if (careController.isCareModeEnabled) { - return FadeTransition( - opacity: Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - ), - ), - child: const CarePoetryPage(), - ); + return const CarePoetryPage(); } - return FadeTransition( - opacity: Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - ), - ), - child: Scaffold( - backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50], - body: SafeArea(child: _buildBody(controller, isDark)), - ), + return Scaffold( + backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey[50], + body: SafeArea(child: _buildBody(controller, isDark)), ); }); } @@ -168,10 +140,16 @@ class _HomePageState extends State top: 60, left: 16, right: 16, - child: CareModeToggle( - isEnabled: _careController.isCareModeEnabled, - onToggle: _careController.toggleCareMode, - isDark: isDark, + child: GestureDetector( + onTap: () => _careController.resetAutoHideTimer(), + child: CareModeToggle( + isEnabled: _careController.isCareModeEnabled, + onToggle: () { + _careController.resetAutoHideTimer(); + _careController.toggleCareMode(); + }, + isDark: isDark, + ), ), ) : const SizedBox.shrink(), diff --git a/lib/views/home/home_part.dart b/lib/views/home/home_part.dart index 902ed8a..a83fd32 100644 --- a/lib/views/home/home_part.dart +++ b/lib/views/home/home_part.dart @@ -11,6 +11,7 @@ import '../../../services/get/theme_controller.dart'; import '../../../utils/http/poetry_api.dart'; import '../../../utils/audio_manager.dart'; import 'set/home_components.dart'; +import 'components/skeleton_widgets.dart'; /// 诗词卡片组件 - 优化版本,防止拉伸和处理文本溢出 class PoetryCard extends StatefulWidget { @@ -130,65 +131,61 @@ class _PoetryCardState extends State { ), ], ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildTimeBar(isDark, themeColor), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildTitleSection(isDark, themeColor), - if (widget.poetryData.drtime.isNotEmpty) ...[ - _buildContentSection(context, isDark, themeColor), - const SizedBox(height: 3), - ], - Align( - alignment: Alignment.centerRight, - child: Container( - margin: EdgeInsets.zero, - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 2, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '精选诗句', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: themeColor, - ), - ), - const SizedBox(width: 4), - Icon( - Icons.format_quote, - color: themeColor, - size: 14, - ), - ], - ), - ), + child: Padding( + padding: const EdgeInsets.only( + left: 20, + right: 20, + bottom: 20, + top: 50, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildTitleSection(isDark, themeColor), + if (widget.poetryData.drtime.isNotEmpty) ...[ + _buildContentSection(context, isDark, themeColor), + const SizedBox(height: 3), + ], + Align( + alignment: Alignment.centerRight, + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 2, ), - _buildNameSection(isDark, themeColor), - const SizedBox(height: 12), - if (widget.keywordList.isNotEmpty) ...[ - _buildKeywordSection(isDark, themeColor), - const SizedBox(height: 16), - ], - if (widget.poetryData.introduce.isNotEmpty) ...[ - _buildIntroductionSection(context, isDark, themeColor), - ], - ], + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '精选诗句', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: themeColor, + ), + ), + const SizedBox(width: 4), + Icon(Icons.format_quote, color: themeColor, size: 14), + ], + ), + ), ), - ), - ], + _buildNameSection(isDark, themeColor), + const SizedBox(height: 12), + if (widget.keywordList.isNotEmpty) ...[ + _buildKeywordSection(isDark, themeColor), + const SizedBox(height: 16), + ], + if (widget.poetryData.introduce.isNotEmpty) ...[ + _buildIntroductionSection(context, isDark, themeColor), + ], + ], + ), ), ), + _buildTimeBar(isDark, themeColor), _buildCopyTip(isDark, themeColor), ], ), @@ -201,52 +198,57 @@ class _PoetryCardState extends State { } Widget _buildTimeBar(bool isDark, Color themeColor) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - setState(() { - _showRecommendation = !_showRecommendation; - }); - }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [themeColor.withAlpha(204), themeColor], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + return Positioned( + top: 16, + left: 16, + right: 16, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + _showRecommendation = !_showRecommendation; + }); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [themeColor.withAlpha(204), themeColor], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Row( - children: [ - Icon( - _showRecommendation ? Icons.lightbulb : Icons.wb_sunny, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - _showRecommendation - ? _getRecommendation() - : _getTimeOfDayGreeting(), - style: const TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, + child: Row( + children: [ + Icon( + _showRecommendation ? Icons.lightbulb : Icons.wb_sunny, + color: Colors.white, + size: 20, ), - ), - Icon( - _showRecommendation ? Icons.expand_less : Icons.expand_more, - color: Colors.white70, - size: 20, - ), - ], + const SizedBox(width: 8), + Expanded( + child: Text( + _showRecommendation + ? _getRecommendation() + : _getTimeOfDayGreeting(), + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + Icon( + _showRecommendation ? Icons.expand_less : Icons.expand_more, + color: Colors.white70, + size: 20, + ), + ], + ), ), ), ); @@ -258,7 +260,7 @@ class _PoetryCardState extends State { } return Positioned( - top: 56, + top: 80, right: 24, child: GestureDetector( onTap: () => setState(() => _showCopyTip = false), @@ -299,6 +301,8 @@ class _PoetryCardState extends State { Widget _buildTitleSection(bool isDark, Color themeColor) { final isLoading = widget.sectionLoadingStates?['title'] ?? false; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return SizedBox( height: 28, @@ -314,30 +318,12 @@ class _PoetryCardState extends State { isDark: isDark, ), child: isLoading - ? Row( - children: [ - SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - themeColor, - ), - ), - ), - const SizedBox(width: 8), - Text( - '出处加载中...', - style: TextStyle( - fontSize: 14, - color: isDark - ? Colors.grey[400] - : Colors.grey[600], - fontStyle: FontStyle.italic, - ), - ), - ], + ? SkeletonContainer( + width: double.infinity, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, ) : Text( "出处: ${widget.poetryData.url}", @@ -360,6 +346,8 @@ class _PoetryCardState extends State { Widget _buildNameSection(bool isDark, Color themeColor) { final isLoading = widget.sectionLoadingStates?['name'] ?? false; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Container( width: double.infinity, @@ -402,23 +390,20 @@ class _PoetryCardState extends State { ? Center( child: Column( children: [ - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - themeColor, - ), - ), + SkeletonContainer( + width: 120, + height: 28, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, ), const SizedBox(height: 8), - Text( - '诗句加载中...', - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.grey[400] : Colors.grey[600], - ), + SkeletonContainer( + width: 80, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, ), ], ), @@ -454,6 +439,8 @@ class _PoetryCardState extends State { Widget _buildKeywordSection(bool isDark, Color themeColor) { final isLoading = widget.sectionLoadingStates?['keywords'] ?? false; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Column( children: [ @@ -461,32 +448,33 @@ class _PoetryCardState extends State { children: [ Expanded( child: isLoading - ? Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 16, - height: 8, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - themeColor, - ), - ), - ), - const SizedBox(width: 4), - Text( - '关键词加载中...', - style: TextStyle( - fontSize: 12, - color: isDark - ? Colors.grey[400] - : Colors.grey[600], - ), - ), - ], - ), + ? Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.center, + children: [ + SkeletonContainer( + width: 60, + height: 24, + borderRadius: 12, + baseColor: baseColor, + highlightColor: highlightColor, + ), + SkeletonContainer( + width: 80, + height: 24, + borderRadius: 12, + baseColor: baseColor, + highlightColor: highlightColor, + ), + SkeletonContainer( + width: 70, + height: 24, + borderRadius: 12, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], ) : Wrap( spacing: 8, @@ -602,6 +590,8 @@ class _PoetryCardState extends State { Color themeColor, ) { final isLoading = widget.sectionLoadingStates?['content'] ?? false; + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; return Builder( builder: (context) => GestureDetector( @@ -622,28 +612,41 @@ class _PoetryCardState extends State { borderRadius: BorderRadius.circular(8), ), child: isLoading - ? Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(themeColor), - ), - ), - const SizedBox(width: 8), - Text( - '时间加载中...', - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.grey[400] : Colors.grey[600], - ), - ), - ], - ), + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SkeletonContainer( + width: 80, + height: 18, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: double.infinity, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: double.infinity, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 150, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/views/home/set/home-set.dart b/lib/views/home/set/home-set.dart index af6200d..ed5ca9c 100644 --- a/lib/views/home/set/home-set.dart +++ b/lib/views/home/set/home-set.dart @@ -30,8 +30,11 @@ class FloatingButtonsVisibilityManager { Future init() async { final prefs = await SharedPreferences.getInstance(); - _isVisible = prefs.getBool(_key) ?? true; - _visibleNotifier.value = _isVisible; + final visible = prefs.getBool(_key) ?? true; + WidgetsBinding.instance.addPostFrameCallback((_) { + _isVisible = visible; + _visibleNotifier.value = visible; + }); } bool get isVisible => _isVisible; @@ -59,10 +62,12 @@ class FloatingButtonsVisibilityManager { Future _restoreFromFlashing() async { _cancelAllTimers(); - _isFlashing = false; - _flashingNotifier.value = false; - _isVisible = true; - _visibleNotifier.value = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + _isFlashing = false; + _flashingNotifier.value = false; + _isVisible = true; + _visibleNotifier.value = true; + }); final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_key, true); } @@ -107,8 +112,10 @@ class FloatingButtonsVisibilityManager { void dispose() { _cancelAllTimers(); - _visibleNotifier.value = true; - _flashingNotifier.value = false; + WidgetsBinding.instance.addPostFrameCallback((_) { + _visibleNotifier.value = true; + _flashingNotifier.value = false; + }); } } diff --git a/lib/views/profile/app-info.dart b/lib/views/profile/app-info.dart index 5d22e92..9c38c17 100644 --- a/lib/views/profile/app-info.dart +++ b/lib/views/profile/app-info.dart @@ -87,15 +87,17 @@ class _AppInfoPageState extends State { _isDeveloperMode = true; }); _saveDeveloperMode(true); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('开发者模式激活'), - duration: Duration(seconds: 2), - ), - ); - } _tapCount = 0; + final primaryColor = _themeController.currentThemeColor; + Get.snackbar( + '提示', + '开发者模式激活', + snackPosition: SnackPosition.BOTTOM, + colorText: primaryColor, + duration: const Duration(seconds: 2), + borderRadius: 8, + margin: const EdgeInsets.all(16), + ); } } @@ -125,11 +127,15 @@ class _AppInfoPageState extends State { IconButton( icon: const Icon(Icons.bug_report, color: Colors.green), onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('调试信息已激活'), - duration: Duration(seconds: 2), - ), + final primaryColor = _themeController.currentThemeColor; + Get.snackbar( + '提示', + '调试信息已激活', + snackPosition: SnackPosition.BOTTOM, + colorText: primaryColor, + duration: const Duration(seconds: 2), + borderRadius: 8, + margin: const EdgeInsets.all(16), ); }, ), @@ -149,8 +155,6 @@ class _AppInfoPageState extends State { _buildDeviceInfoCard(context, isDark, primaryColor), const SizedBox(height: 16), _buildUpdateLogCard(isDark), - const SizedBox(height: 16), - _buildDesignStyleCard(isDark), const SizedBox(height: 24), _buildBottomIndicator(isDark), ], @@ -251,7 +255,7 @@ class _AppInfoPageState extends State { GestureDetector( onTap: _onFrameworkTap, child: const Text( - '框架 1.3', + '框架 1.4', style: TextStyle( fontSize: 12, color: Colors.white, @@ -270,16 +274,18 @@ class _AppInfoPageState extends State { : Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), - child: Text( - '软件版本 ${AppConfig.appVersion}', - style: TextStyle( - fontSize: 10, - color: _isDeveloperMode - ? Colors.green - : Colors.white, - fontWeight: _isDeveloperMode - ? FontWeight.bold - : FontWeight.normal, + child: Obx( + () => Text( + '软件版本 ${AppConfig.appVersion}', + style: TextStyle( + fontSize: 10, + color: _isDeveloperMode + ? Colors.green + : Colors.white, + fontWeight: _isDeveloperMode + ? FontWeight.bold + : FontWeight.normal, + ), ), ), ), @@ -350,15 +356,15 @@ class _AppInfoPageState extends State { childAspectRatio: 2.2, children: [ _buildTechStackItem( - 'Flutter', - '跨平台UI框架', + 'Dart', + '编程语言', Icons.flutter_dash, Colors.blue, isDark, ), _buildTechStackItem( - 'Dart', - '编程语言', + 'Getx', + '状态管理库', Icons.code, Colors.teal, isDark, @@ -456,19 +462,23 @@ class _AppInfoPageState extends State { ), ), Divider(height: 1, color: isDark ? Colors.grey[800] : null), - _buildCopyableItem( - context, - '版本号', - AppConfig.appVersion, - Icons.verified, - isDark, + Obx( + () => _buildCopyableItem( + context, + '版本号', + AppConfig.appVersion, + Icons.verified, + isDark, + ), ), - _buildCopyableItem( - context, - 'Builder version', - '26d03a26', - Icons.developer_mode, - isDark, + Obx( + () => _buildCopyableItem( + context, + 'Builder version', + AppConfig.appVersionCode.toString(), + Icons.developer_mode, + isDark, + ), ), _buildInfoItem( '打包时间', @@ -542,6 +552,7 @@ class _AppInfoPageState extends State { {'name': 'Cupertino Icons', 'license': 'MIT'}, {'name': 'Shared Preferences', 'license': 'BSD 3-Clause'}, {'name': 'Dio', 'license': 'MIT'}, + {'name': 'GetX', 'license': 'MIT'}, {'name': 'Platform Info', 'license': 'MIT'}, {'name': 'flutter_udid', 'license': 'MIT'}, ]; @@ -979,26 +990,27 @@ class _AppInfoPageState extends State { ], ), ), - // Divider(height: 1, color: isDark ? Colors.grey[800] : null), - // Padding( - // padding: const EdgeInsets.all(16), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // _buildUpdateItem('版本 1.2.40', '2026-03-27', [ - // '新增:主题个性化页面', - // '新增:设计风格卡片', - // '优化:界面布局和响应式设计', - // ], isDark), - // const SizedBox(height: 16), - // _buildUpdateItem('版本 1.2.39', '2026-03-27', [ - // '修复:引导页滑动问题', - // '优化:左侧进度条位置', - // '新增:协议内容焦点功能', - // ], isDark), - // ], - // ), - // ), + Divider(height: 1, color: isDark ? Colors.grey[800] : null), + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildUpdateItem('版本 1.4.1', '2026-04-03', [ + '新增:从 pubspec.yaml 动态获取版本号', + '新增:响应式版本号显示', + '优化:解决 package_info_plus 依赖冲突', + '修复:版本号不显示的问题', + ], isDark), + const SizedBox(height: 16), + _buildUpdateItem('版本 1.3.59', '2026-04-03', [ + '修复:出处字段被时间提示语遮挡的问题', + '修复:时间提示语布局遮挡问题', + '优化:诗词卡片内容布局', + ], isDark), + ], + ), + ), ], ), ); @@ -1417,20 +1429,15 @@ class _AppInfoPageState extends State { void _copyToClipboard(BuildContext context, String text) { Clipboard.setData(ClipboardData(text: text)); final primaryColor = _themeController.currentThemeColor; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Row( - children: [ - const Icon(Icons.check_circle, color: Colors.white, size: 20), - const SizedBox(width: 8), - Text('$text 已复制到剪贴板'), - ], - ), - backgroundColor: primaryColor, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - duration: const Duration(seconds: 2), - ), + Get.snackbar( + '复制成功', + '$text 已复制到剪贴板', + snackPosition: SnackPosition.BOTTOM, + colorText: primaryColor, + duration: const Duration(seconds: 2), + borderRadius: 8, + margin: const EdgeInsets.all(16), + icon: Icon(Icons.check_circle, color: primaryColor, size: 20), ); } diff --git a/lib/views/profile/components/entire_page.dart b/lib/views/profile/components/entire_page.dart index 646a87a..e7e8824 100644 --- a/lib/views/profile/components/entire_page.dart +++ b/lib/views/profile/components/entire_page.dart @@ -150,10 +150,7 @@ class _EntirePageState extends State backgroundColor: AppColors.surface, elevation: 0, leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - color: AppColors.primary, - ), + icon: Icon(Icons.arrow_back_ios, color: AppColors.primary), onPressed: () => Navigator.pop(context), ), title: Text( @@ -167,20 +164,14 @@ class _EntirePageState extends State centerTitle: true, actions: [ IconButton( - icon: Icon( - Icons.info_outline, - color: AppColors.primary, - ), + icon: Icon(Icons.info_outline, color: AppColors.primary), onPressed: _showServerInfo, tooltip: '服务器信息', ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(0.5), - child: Container( - height: 0.5, - color: AppColors.divider, - ), + child: Container(height: 0.5, color: AppColors.divider), ), ); } @@ -417,10 +408,7 @@ class _EntirePageState extends State const SizedBox(height: 16), Text( _errorMessage ?? '加载失败', - style: TextStyle( - color: AppColors.tertiaryText, - fontSize: 14, - ), + style: TextStyle(color: AppColors.tertiaryText, fontSize: 14), textAlign: TextAlign.center, ), const SizedBox(height: 24), @@ -475,10 +463,7 @@ class _EntirePageState extends State padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ - AppColors.primary, - AppColors.primary.withValues(alpha: 0.8), - ], + colors: [AppColors.primary, AppColors.primary.withValues(alpha: 0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -590,13 +575,6 @@ class _EntirePageState extends State Widget _buildCountGrid(bool isDark) { final counts = [ - { - 'label': '项目', - 'value': _statsData?['count_category'] ?? 0, - 'icon': Icons.category, - 'color': AppColors.iosBlue, - 'showIcon': true, - }, { 'label': '收录诗句', 'value': _statsData?['count_site'] ?? 0, @@ -604,6 +582,31 @@ class _EntirePageState extends State 'color': AppColors.iosGreen, 'showIcon': false, }, + + { + 'label': '诗词题目', + 'value': _statsData?['count_article_category'] ?? 0, + 'icon': Icons.folder, + 'color': AppColors.iosPink, + 'showIcon': false, + }, + + { + 'label': '分类标签', + 'value': _statsData?['count_tags'] ?? 0, + 'icon': Icons.label, + 'color': const Color(0xFF64D2FF), + 'showIcon': false, + }, + + { + 'label': '项目', + 'value': _statsData?['count_category'] ?? 0, + 'icon': Icons.category, + 'color': AppColors.iosBlue, + 'showIcon': true, + }, + { 'label': '审核中', 'value': _statsData?['count_apply'] ?? 0, @@ -625,13 +628,7 @@ class _EntirePageState extends State 'color': AppColors.iosPurple, 'showIcon': true, }, - { - 'label': '文章分类', - 'value': _statsData?['count_article_category'] ?? 0, - 'icon': Icons.folder, - 'color': AppColors.iosPink, - 'showIcon': true, - }, + { 'label': '推送', 'value': _statsData?['count_notice'] ?? 0, @@ -646,13 +643,6 @@ class _EntirePageState extends State 'color': AppColors.iosRed, 'showIcon': true, }, - { - 'label': '分类标签', - 'value': _statsData?['count_tags'] ?? 0, - 'icon': Icons.label, - 'color': const Color(0xFF64D2FF), - 'showIcon': false, - }, ]; return GridView.builder( @@ -739,10 +729,7 @@ class _EntirePageState extends State child: Center( child: Text( label, - style: TextStyle( - fontSize: 12, - color: AppColors.secondaryText, - ), + style: TextStyle(fontSize: 12, color: AppColors.secondaryText), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, @@ -974,11 +961,7 @@ class _EntirePageState extends State color: AppColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), ), - child: Icon( - Icons.cake, - color: AppColors.primary, - size: 20, - ), + child: Icon(Icons.cake, color: AppColors.primary, size: 20), ), const SizedBox(width: 12), Expanded( diff --git a/lib/views/profile/guide/app-data.dart b/lib/views/profile/guide/app-data.dart index 518c67b..439280c 100644 --- a/lib/views/profile/guide/app-data.dart +++ b/lib/views/profile/guide/app-data.dart @@ -801,7 +801,7 @@ class _AppDataPageState extends State { ), _buildDivider(), _buildActionButton( - '🔧 原生清理数据', + '🔧 系统级清理数据', '删除应用数据目录所有文件', Icons.system_security_update_warning, Colors.orange, diff --git a/lib/views/profile/guide/permission.dart b/lib/views/profile/guide/permission.dart index 8ac560d..c6a2611 100644 --- a/lib/views/profile/guide/permission.dart +++ b/lib/views/profile/guide/permission.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../constants/app_constants.dart'; import '../../../services/get/theme_controller.dart'; +import '../../../controllers/settings/suggestions.dart'; /// 时间: 2026-03-27 /// 功能: 权限管理页面 @@ -142,6 +143,8 @@ class _PermissionPageState extends State { const SizedBox(height: 16), _buildProjectSupplement(isDark), const SizedBox(height: 24), + const SuggestionsCard(), + const SizedBox(height: 24), _buildBottomTip(isDark), ], ), @@ -674,14 +677,14 @@ class _PermissionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - width: 4, - height: 4, - margin: const EdgeInsets.only(top: 8), - decoration: BoxDecoration( - color: _themeController.currentThemeColor, - borderRadius: BorderRadius.circular(2), - ), + width: 4, + height: 4, + margin: const EdgeInsets.only(top: 8), + decoration: BoxDecoration( + color: _themeController.currentThemeColor, + borderRadius: BorderRadius.circular(2), ), + ), const SizedBox(width: 12), Expanded( child: Column( diff --git a/lib/views/profile/history_page.dart b/lib/views/profile/history_page.dart index dac85c1..372004c 100644 --- a/lib/views/profile/history_page.dart +++ b/lib/views/profile/history_page.dart @@ -496,6 +496,8 @@ class HistoryPage extends StatelessWidget { int index, Map item, ) { + final themeController = Get.find(); + final primaryColor = themeController.currentThemeColor; showCupertinoModalPopup( context: context, builder: (BuildContext context) => CupertinoActionSheet( @@ -512,14 +514,14 @@ class HistoryPage extends StatelessWidget { CupertinoActionSheetAction( onPressed: () { Get.back(); - Get.snackbar('提示', '分享功能开发中...'); + Get.snackbar('提示', '分享功能开发中...', colorText: primaryColor); }, child: const Text('分享'), ), CupertinoActionSheetAction( onPressed: () { Get.back(); - Get.snackbar('提示', '查看详情功能开发中...'); + Get.snackbar('提示', '查看详情功能开发中...', colorText: primaryColor); }, child: const Text('查看详情'), ), diff --git a/lib/views/profile/level/poetry.dart b/lib/views/profile/level/poetry.dart index ff9f472..342c54c 100644 --- a/lib/views/profile/level/poetry.dart +++ b/lib/views/profile/level/poetry.dart @@ -933,11 +933,15 @@ class _PoetryLevelPageState extends State width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: isDark + ? Colors.grey[800] + : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withAlpha(10), + color: Colors.black.withAlpha( + isDark ? 40 : 10, + ), blurRadius: 12, offset: const Offset(0, 4), ), @@ -953,10 +957,15 @@ class _PoetryLevelPageState extends State child: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [ - Colors.white, - Colors.grey[50]!, - ], + colors: isDark + ? [ + Colors.grey[700]!, + Colors.grey[800]!, + ] + : [ + Colors.white, + Colors.grey[50]!, + ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -964,7 +973,7 @@ class _PoetryLevelPageState extends State BorderRadius.circular(12), border: Border.all( color: primaryColor.withAlpha( - 50, + isDark ? 80 : 50, ), width: 1, ), @@ -988,18 +997,19 @@ class _PoetryLevelPageState extends State children: [ Icon( Icons.arrow_back, - color: AppConstants - .primaryColor, + color: primaryColor, size: 20, ), const SizedBox(width: 8), - const Text( + Text( '上一题', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Colors.black87, + color: isDark + ? Colors.white + : Colors.black87, ), ), ], @@ -1013,10 +1023,15 @@ class _PoetryLevelPageState extends State child: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [ - Colors.white, - Colors.grey[50]!, - ], + colors: isDark + ? [ + Colors.grey[700]!, + Colors.grey[800]!, + ] + : [ + Colors.white, + Colors.grey[50]!, + ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -1029,7 +1044,7 @@ class _PoetryLevelPageState extends State boxShadow: [ BoxShadow( color: primaryColor.withAlpha( - 30, + isDark ? 50 : 30, ), blurRadius: 8, offset: const Offset(0, 4), @@ -1057,18 +1072,19 @@ class _PoetryLevelPageState extends State children: [ Icon( Icons.lightbulb_outline, - color: AppConstants - .primaryColor, + color: primaryColor, size: 20, ), const SizedBox(width: 8), - const Text( + Text( '提示', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.black87, + color: isDark + ? Colors.white + : Colors.black87, ), ), ], @@ -1093,9 +1109,9 @@ class _PoetryLevelPageState extends State BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: AppConstants - .primaryColor - .withAlpha(80), + color: primaryColor.withAlpha( + isDark ? 60 : 80, + ), blurRadius: 12, offset: const Offset(0, 4), ), diff --git a/lib/views/profile/per_card.dart b/lib/views/profile/per_card.dart index c93c42e..93f5a06 100644 --- a/lib/views/profile/per_card.dart +++ b/lib/views/profile/per_card.dart @@ -278,10 +278,12 @@ class PersonalCardState extends State { HapticFeedback.lightImpact(); // 显示切换提示 + final themeController = Get.find(); Get.snackbar( '头像切换', '头像已切换为 ${_avatarEmojis[_currentAvatarIndex]}', duration: const Duration(seconds: 1), + colorText: themeController.currentThemeColor, ); } diff --git a/lib/views/profile/profile_page.dart b/lib/views/profile/profile_page.dart index 2fa5d03..8a2064b 100644 --- a/lib/views/profile/profile_page.dart +++ b/lib/views/profile/profile_page.dart @@ -568,7 +568,7 @@ class ProfilePage extends StatelessWidget { ), const SizedBox(height: 8), Text( - '版本 ${AppConstants.appVersion}', + '版本 ${AppConfig.appVersion}', style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[400] : Colors.grey, diff --git a/lib/views/profile/settings/app_fun.dart b/lib/views/profile/settings/app_fun.dart index 31fdc4b..6139978 100644 --- a/lib/views/profile/settings/app_fun.dart +++ b/lib/views/profile/settings/app_fun.dart @@ -51,6 +51,7 @@ class _AppFunSettingsPageState extends State { Future _loadSettings() async { final prefs = await SharedPreferences.getInstance(); await GlobalTipsManager().init(); + await AudioManager().init(); // 确保 AudioManager 已初始化 if (mounted) { setState(() { _autoRefreshEnabled = prefs.getBool(_autoRefreshKey) ?? false; @@ -62,6 +63,8 @@ class _AppFunSettingsPageState extends State { _hideSecondaryButtons = prefs.getBool(_hideSecondaryButtonsKey) ?? false; // 加载隐藏次要按钮状态 }); + // 同步到 AudioManager + AudioManager().setMuted(!_soundEnabled); } } @@ -101,7 +104,7 @@ class _AppFunSettingsPageState extends State { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_soundEnabledKey, value); // 更新 AudioManager 的静音状态 - AudioManager().setMuted(!value); + await AudioManager().setMuted(!value); if (mounted) { setState(() { _soundEnabled = value; @@ -506,60 +509,53 @@ class _AppFunSettingsPageState extends State { final primaryColor = _themeController.currentThemeColor; return Obx(() { final currentIndex = _glassController.transparencyLevelIndex; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), - child: Row( + return Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: primaryColor.withAlpha(10), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(Icons.opacity, color: primaryColor, size: 20), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: primaryColor.withAlpha(10), - borderRadius: BorderRadius.circular(8), + Text( + '高透级别', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black, ), - child: Icon(Icons.opacity, color: primaryColor, size: 20), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '高透级别', - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: isDark ? Colors.white : Colors.black, - ), - ), - Text( - '当前: ${_glassController.transparencyLevelLabel}', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.grey[400] : Colors.grey[600], - ), - ), - ], + Text( + '当前: ${_glassController.transparencyLevelLabel}', + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], ), ), ], ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), - child: Row( - children: [ - _buildLevelButton('弱', 0, currentIndex == 0, isDark), - const SizedBox(width: 8), - _buildLevelButton('中', 1, currentIndex == 1, isDark), - const SizedBox(width: 8), - _buildLevelButton('强', 2, currentIndex == 2, isDark), - ], + const SizedBox(width: 12), + Expanded( + child: Row( + children: [ + _buildLevelButton('弱', 0, currentIndex == 0, isDark), + const SizedBox(width: 8), + _buildLevelButton('中', 1, currentIndex == 1, isDark), + const SizedBox(width: 8), + _buildLevelButton('强', 2, currentIndex == 2, isDark), + ], + ), ), - ), - ], + ], + ), ); }); } diff --git a/lib/views/profile/settings/learn-us.dart b/lib/views/profile/settings/learn-us.dart index 36836ea..f37c406 100644 --- a/lib/views/profile/settings/learn-us.dart +++ b/lib/views/profile/settings/learn-us.dart @@ -212,30 +212,18 @@ class LearnUsPage extends StatelessWidget { ), ), const SizedBox(height: 12), - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: isDark ? const Color(0xFF3A3A3A) : Colors.grey[50], - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: isDark - ? Colors.grey[700]!.withAlpha(20) - : Colors.grey.withAlpha(20), - ), - ), - child: Row( - children: [ - Icon(Icons.link, size: 16, color: primaryColor), - const SizedBox(width: 8), - Expanded( - child: Text( - 'https://poe.vogov.cn/app.html', - style: TextStyle(fontSize: 14, color: primaryColor), - ), - ), - ], - ), + _buildWebsiteItem( + 'https://poe.vogov.cn/app.html', + '官方APP页', + isDark, + primaryColor, + ), + const SizedBox(height: 12), + _buildWebsiteItem( + 'https://poe.vogov.cn/', + '情景诗词在线版', + isDark, + primaryColor, ), ], ), @@ -245,6 +233,71 @@ class LearnUsPage extends StatelessWidget { ); } + Widget _buildWebsiteItem( + String url, + String label, + bool isDark, + Color primaryColor, + ) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF3A3A3A) : Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isDark + ? Colors.grey[700]!.withAlpha(20) + : Colors.grey.withAlpha(20), + ), + ), + child: Row( + children: [ + Icon(Icons.link, size: 16, color: primaryColor), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey, + ), + ), + Text(url, style: TextStyle(fontSize: 14, color: primaryColor)), + ], + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: url)); + Get.snackbar( + '复制成功', + '链接已复制到剪贴板', + duration: const Duration(seconds: 2), + colorText: primaryColor, + snackPosition: SnackPosition.BOTTOM, + borderRadius: 8, + margin: const EdgeInsets.all(16), + ); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: primaryColor.withAlpha(10), + borderRadius: BorderRadius.circular(4), + ), + child: Icon(Icons.content_copy, size: 16, color: primaryColor), + ), + ), + ], + ), + ); + } + Widget _buildQQGroupCard(BuildContext context, bool isDark) { final themeController = Get.find(); final primaryColor = themeController.currentThemeColor; @@ -352,20 +405,15 @@ class LearnUsPage extends StatelessWidget { final primaryColor = themeController.currentThemeColor; Clipboard.setData(const ClipboardData(text: '271129018')); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Row( - children: [ - Icon(Icons.check_circle, color: Colors.white, size: 20), - SizedBox(width: 8), - Text('QQ群号已复制到剪贴板'), - ], - ), - backgroundColor: primaryColor, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - duration: const Duration(seconds: 2), - ), + Get.snackbar( + '复制成功', + 'QQ群号已复制到剪贴板', + duration: const Duration(seconds: 2), + colorText: primaryColor, + snackPosition: SnackPosition.BOTTOM, + borderRadius: 8, + margin: const EdgeInsets.all(16), + icon: Icon(Icons.check_circle, color: primaryColor, size: 20), ); } @@ -572,14 +620,21 @@ class LearnUsPage extends StatelessWidget { const SizedBox(width: 8), GestureDetector( onTap: () { + final themeController = + Get.find(); + final primaryColor = + themeController.currentThemeColor; Clipboard.setData( const ClipboardData(text: '微风暴'), ); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('已复制到剪贴板'), - duration: Duration(seconds: 2), - ), + Get.snackbar( + '复制成功', + '已复制到剪贴板', + duration: const Duration(seconds: 2), + colorText: primaryColor, + snackPosition: SnackPosition.BOTTOM, + borderRadius: 8, + margin: const EdgeInsets.all(16), ); }, child: Icon( @@ -734,6 +789,8 @@ class LearnUsPage extends StatelessWidget { } Widget _buildIcpCard(BuildContext context, bool isDark) { + final themeController = Get.find(); + final primaryColor = themeController.currentThemeColor; return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, @@ -794,6 +851,7 @@ class LearnUsPage extends StatelessWidget { '复制成功', '备案号已复制到剪贴板', duration: const Duration(seconds: 1), + colorText: primaryColor, ); }, child: Row( diff --git a/lib/views/profile/settings/offline-data.dart b/lib/views/profile/settings/offline-data.dart index d738700..55ea4e5 100644 --- a/lib/views/profile/settings/offline-data.dart +++ b/lib/views/profile/settings/offline-data.dart @@ -85,14 +85,12 @@ class _OfflineDataPageState extends State { ) ?? []; if (existingData.length >= 500) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('缓存已达上限500条,请先清空缓存'), - backgroundColor: Colors.red, - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '提示', + '缓存已达上限500条,请先清空缓存', + colorText: themeController.currentThemeColor, + ); return; } @@ -100,23 +98,24 @@ class _OfflineDataPageState extends State { if (_selectedCount == 100) { final isUserPlanJoined = await _checkUserPlanStatus(); if (!isUserPlanJoined) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('100条下载需要加入用户体验计划'), - backgroundColor: Colors.orange, - action: SnackBarAction( - label: '去加入', - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const UserPlanPage()), - ); - }, - ), + final themeController = Get.find(); + Get.snackbar( + '提示', + '100条下载需要加入用户体验计划', + colorText: themeController.currentThemeColor, + mainButton: TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const UserPlanPage()), + ); + }, + child: Text( + '去加入', + style: TextStyle(color: themeController.currentThemeColor), ), - ); - } + ), + ); return; } } @@ -149,14 +148,12 @@ class _OfflineDataPageState extends State { // 取消时保存已下载的数据 if (_downloadedCount > 0) { await prefs.setStringList(dataKey, currentData); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('已保存 $_downloadedCount 条数据'), - backgroundColor: _themeController.currentThemeColor, - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '提示', + '已保存 $_downloadedCount 条数据', + colorText: themeController.currentThemeColor, + ); } return; } @@ -240,17 +237,15 @@ class _OfflineDataPageState extends State { // 检查总缓存数量 if (currentData.length > 500) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('缓存将超过500条上限,请先清空缓存'), - backgroundColor: Colors.red, - ), - ); - setState(() { - _isLoading = false; - }); - } + final themeController = Get.find(); + Get.snackbar( + '提示', + '缓存将超过500条上限,请先清空缓存', + colorText: themeController.currentThemeColor, + ); + setState(() { + _isLoading = false; + }); return; } @@ -260,13 +255,12 @@ class _OfflineDataPageState extends State { _cachedCount = currentData.length; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '成功缓存 ${_selectedType == DownloadType.poetry ? '诗词' : '答题'} $_cachedCount 条数据', - ), - backgroundColor: _themeController.currentThemeColor, - ), + final themeController = Get.find(); + final themeController2 = Get.find(); + Get.snackbar( + '成功', + '成功缓存 ${_selectedType == DownloadType.poetry ? '诗词' : '答题'} $_cachedCount 条数据', + colorText: themeController2.currentThemeColor, ); } } catch (e) { @@ -275,8 +269,11 @@ class _OfflineDataPageState extends State { _status = '下载失败: $e'; }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('下载失败: $e'), backgroundColor: Colors.red), + final themeController = Get.find(); + Get.snackbar( + '错误', + '下载失败: $e', + colorText: themeController.currentThemeColor, ); } } finally { @@ -297,14 +294,12 @@ class _OfflineDataPageState extends State { final quizCount = (prefs.getStringList('offline_quiz_data') ?? []).length; if (poetryCount == 0 && quizCount == 0) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('暂无缓存数据'), - backgroundColor: Colors.orange, - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '提示', + '暂无缓存数据', + colorText: themeController.currentThemeColor, + ); return; } @@ -367,14 +362,12 @@ class _OfflineDataPageState extends State { final prefs = await SharedPreferences.getInstance(); await prefs.remove(key); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('已清空$typeName离线数据'), - backgroundColor: _themeController.currentThemeColor, - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '成功', + '已清空$typeName离线数据', + colorText: themeController.currentThemeColor, + ); // 更新对应的计数 if (key == 'offline_poetry_data') { @@ -399,14 +392,12 @@ class _OfflineDataPageState extends State { await prefs.remove('offline_poetry_data'); await prefs.remove('offline_quiz_data'); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('已清空所有离线数据'), - backgroundColor: _themeController.currentThemeColor, - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '成功', + '已清空所有离线数据', + colorText: themeController.currentThemeColor, + ); setState(() { _poetryCount = 0; @@ -455,55 +446,47 @@ class _OfflineDataPageState extends State { if (data['status'] == 'success') { _displayServerInfoDialog(data); } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('服务器返回错误状态: ${data['status']}'), - backgroundColor: Colors.red, - duration: const Duration(seconds: 3), - ), - ); - } - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('解析服务器数据失败: $e'), - backgroundColor: Colors.red, - duration: const Duration(seconds: 3), - ), + final themeController = Get.find(); + Get.snackbar( + '错误', + '服务器返回错误状态: ${data['status']}', + colorText: themeController.currentThemeColor, + duration: const Duration(seconds: 3), ); } - } - } else { - if (mounted) { - final errorMsg = - '获取服务器信息失败\n' - '状态码: ${response.statusCode}\n' - '消息: ${response.message}'; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(errorMsg), - backgroundColor: Colors.red, - duration: const Duration(seconds: 5), - ), + } catch (e) { + final themeController = Get.find(); + Get.snackbar( + '错误', + '解析服务器数据失败: $e', + colorText: themeController.currentThemeColor, + duration: const Duration(seconds: 3), ); } + } else { + final errorMsg = + '获取服务器信息失败\n' + '状态码: ${response.statusCode}\n' + '消息: ${response.message}'; + final themeController = Get.find(); + Get.snackbar( + '错误', + errorMsg, + colorText: themeController.currentThemeColor, + duration: const Duration(seconds: 5), + ); } } catch (e) { if (!mounted) return; Navigator.of(context).pop(); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('获取服务器信息异常: $e'), - backgroundColor: Colors.red, - duration: const Duration(seconds: 5), - ), - ); - } + final themeController = Get.find(); + Get.snackbar( + '错误', + '获取服务器信息异常: $e', + colorText: themeController.currentThemeColor, + duration: const Duration(seconds: 5), + ); } } diff --git a/lib/views/profile/theme/app-diy.dart b/lib/views/profile/theme/app-diy.dart index 8fae115..dabc858 100644 --- a/lib/views/profile/theme/app-diy.dart +++ b/lib/views/profile/theme/app-diy.dart @@ -23,6 +23,7 @@ class _AppDiyPageState extends State { bool _showGuideOnStartup = true; int _cardSizeIndex = 1; // 0: 小, 1: 中, 2: 大 bool _enableSystemNavigation = false; + bool _showDevNotice = true; // 是否显示开发中提示对话框 // 滚动控制 final ScrollController _scrollController = ScrollController(); @@ -50,12 +51,16 @@ class _AppDiyPageState extends State { _startScrolling(); // 延迟显示开发中提示对话框 WidgetsBinding.instance.addPostFrameCallback((_) { - _showDevNoticeDialog(); + if (_showDevNotice) { + _showDevNoticeDialog(); + } }); } // 显示开发中提示对话框 void _showDevNoticeDialog() { + bool doNotShowAgain = false; + showDialog( context: context, barrierDismissible: false, @@ -63,81 +68,119 @@ class _AppDiyPageState extends State { final primaryColor = _themeController.currentThemeColor; final isDark = _themeController.isDarkMode; - return AlertDialog( - backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - title: Row( - children: [ - Icon(Icons.construction, color: primaryColor), - const SizedBox(width: 8), - Text( - '开发中', - style: TextStyle(color: isDark ? Colors.white : Colors.black), + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '个性化设置开发中', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: isDark ? Colors.white : Colors.black, + title: Row( + children: [ + Icon(Icons.construction, color: primaryColor), + const SizedBox(width: 8), + Text( + '开发中', + style: TextStyle( + color: isDark ? Colors.white : Colors.black, + ), + ), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '个性化设置开发中', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: isDark ? Colors.white : Colors.black, + ), + ), + const SizedBox(height: 12), + Text( + '• ✅ 深色模式(已支持)', + style: TextStyle( + color: isDark ? Colors.grey[300] : Colors.black87, + ), + ), + Text( + '• ✅ 主题色彩(已支持)', + style: TextStyle( + color: isDark ? Colors.grey[300] : Colors.black87, + ), + ), + Text( + '• ⏳ 其他设置仅当前页面生效', + style: TextStyle( + color: isDark ? Colors.grey[300] : Colors.black87, + ), + ), + Text( + '• ⏳ 后续版本将陆续支持', + style: TextStyle( + color: isDark ? Colors.grey[300] : Colors.black87, + ), + ), + const SizedBox(height: 12), + Text( + '感谢您的耐心等待!', + style: TextStyle( + color: isDark ? Colors.grey[400] : Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), + actions: [ + Row( + children: [ + Checkbox( + value: doNotShowAgain, + onChanged: (value) { + setState(() { + doNotShowAgain = value ?? false; + }); + }, + activeColor: primaryColor, + ), + Text( + '不再提醒', + style: TextStyle( + color: isDark ? Colors.grey[300] : Colors.black87, + fontSize: 14, + ), + ), + const Spacer(), + ElevatedButton( + onPressed: () async { + if (doNotShowAgain) { + await _themeController.prefs?.setBool( + 'showDevNotice', + false, + ); + setState(() { + _showDevNotice = false; + }); + } + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('我知道了'), + ), + ], ), - ), - const SizedBox(height: 12), - Text( - '• ✅ 深色模式(已支持)', - style: TextStyle( - color: isDark ? Colors.grey[300] : Colors.black87, - ), - ), - Text( - '• ✅ 主题色彩(已支持)', - style: TextStyle( - color: isDark ? Colors.grey[300] : Colors.black87, - ), - ), - Text( - '• ⏳ 其他设置仅当前页面生效', - style: TextStyle( - color: isDark ? Colors.grey[300] : Colors.black87, - ), - ), - Text( - '• ⏳ 后续版本将陆续支持', - style: TextStyle( - color: isDark ? Colors.grey[300] : Colors.black87, - ), - ), - const SizedBox(height: 12), - Text( - '感谢您的耐心等待!', - style: TextStyle( - color: isDark ? Colors.grey[400] : Colors.grey[600], - fontSize: 12, - ), - ), - ], - ), - actions: [ - ElevatedButton( - onPressed: () => Navigator.of(context).pop(), - style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: const Text('我知道了'), - ), - ], + ], + ); + }, ); }, ); @@ -187,6 +230,7 @@ class _AppDiyPageState extends State { _cardSizeIndex = _themeController.prefs?.getInt('cardSizeIndex') ?? 1; _enableSystemNavigation = _themeController.prefs?.getBool('enableSystemNavigation') ?? false; + _showDevNotice = _themeController.prefs?.getBool('showDevNotice') ?? true; }); } @@ -218,6 +262,16 @@ class _AppDiyPageState extends State { ), backgroundColor: isDark ? Colors.grey[900] : Colors.white, elevation: 0, + actions: [ + IconButton( + icon: Icon( + Icons.info_outline, + color: _themeController.currentThemeColor, + ), + onPressed: () => _showDevNoticeDialog(), + tooltip: '显示开发中提示', + ), + ], ), backgroundColor: isDark ? Colors.grey[900] : Colors.grey[50], body: ListView( diff --git a/lib/widgets/care/care_mode_navigation.dart b/lib/widgets/care/care_mode_navigation.dart index f4cc950..e001d99 100644 --- a/lib/widgets/care/care_mode_navigation.dart +++ b/lib/widgets/care/care_mode_navigation.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../config/app_config.dart'; import '../../services/get/theme_controller.dart'; +import '../../services/get/tap_liquid_glass_controller.dart'; class CareModeNavigation extends StatelessWidget { final int currentIndex; @@ -22,13 +23,21 @@ class CareModeNavigation extends StatelessWidget { @override Widget build(BuildContext context) { final themeController = Get.find(); + final glassController = Get.find(); return Obx(() { final isDark = themeController.isDarkMode; final enableBlur = themeController.enableBlurEffect; final primaryColor = themeController.currentThemeColor; + final transparencyValues = glassController.transparencyValues; - return _buildGlassBar(context, isDark, enableBlur, primaryColor); + return _buildGlassBar( + context, + isDark, + enableBlur, + primaryColor, + transparencyValues, + ); }); } @@ -37,78 +46,92 @@ class CareModeNavigation extends StatelessWidget { bool isDark, bool enableBlur, Color primaryColor, + Map transparencyValues, ) { + final backgroundOpacity = transparencyValues['backgroundOpacity']!; return SafeArea( top: false, child: Container( padding: EdgeInsets.only( left: AppConfig.liquidGlassHorizontalMargin, right: AppConfig.liquidGlassHorizontalMargin, - bottom: AppConfig.liquidGlassBottomMargin, - top: 8, + bottom: 16, + top: 16, ), child: Container( - height: AppConfig.liquidGlassHeight, + height: AppConfig.liquidGlassHeight, // 使用原始高度 decoration: BoxDecoration( borderRadius: BorderRadius.circular( AppConfig.liquidGlassCornerRadius, ), - boxShadow: [ - BoxShadow( - color: isDark - ? Colors.black.withValues(alpha: 0.6) - : Colors.black.withValues(alpha: 0.15), - blurRadius: 35, - spreadRadius: -10, - offset: const Offset(0, 12), - ), - ], + boxShadow: backgroundOpacity > 0.2 + ? [ + BoxShadow( + color: isDark + ? Colors.black.withValues(alpha: 0.6) + : Colors.black.withValues(alpha: 0.15), + blurRadius: 35, + spreadRadius: -10, + offset: const Offset(0, 12), + ), + ] + : [], ), child: ClipRRect( borderRadius: BorderRadius.circular( AppConfig.liquidGlassCornerRadius, ), - child: enableBlur + child: enableBlur && backgroundOpacity >= 0.01 ? BackdropFilter( filter: ImageFilter.blur( sigmaX: AppConfig.liquidGlassBlur, sigmaY: AppConfig.liquidGlassBlur, ), - child: _buildGlassContent(isDark, primaryColor), + child: _buildGlassContent( + isDark, + primaryColor, + backgroundOpacity, + ), ) - : _buildGlassContent(isDark, primaryColor), + : _buildGlassContent(isDark, primaryColor, backgroundOpacity), ), ), ), ); } - Widget _buildGlassContent(bool isDark, Color primaryColor) { + Widget _buildGlassContent( + bool isDark, + Color primaryColor, + double backgroundOpacity, + ) { return Stack( children: [ - Container( - decoration: BoxDecoration( - color: isDark - ? Colors.black.withValues(alpha: 0.4) - : Colors.white.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular( - AppConfig.liquidGlassCornerRadius, - ), - ), - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.liquidGlassCornerRadius, - ), - border: Border.all( + if (backgroundOpacity >= 0.1) + Container( + decoration: BoxDecoration( color: isDark - ? Colors.white.withValues(alpha: 0.2) - : Colors.black.withValues(alpha: 0.1), - width: 0.6, + ? Colors.black.withValues(alpha: backgroundOpacity * 0.4) + : Colors.white.withValues(alpha: backgroundOpacity), + borderRadius: BorderRadius.circular( + AppConfig.liquidGlassCornerRadius, + ), + ), + ), + if (backgroundOpacity >= 0.15) + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.liquidGlassCornerRadius, + ), + border: Border.all( + color: isDark + ? Colors.white.withValues(alpha: backgroundOpacity * 0.2) + : Colors.black.withValues(alpha: backgroundOpacity * 0.1), + width: 0.6, + ), ), ), - ), _buildNavItems(primaryColor), ], ); @@ -149,43 +172,45 @@ class CareModeNavigation extends StatelessWidget { borderRadius: BorderRadius.circular( AppConfig.liquidGlassCornerRadius, ), - child: AnimatedContainer( - duration: enableAnimation - ? const Duration(milliseconds: 250) - : Duration.zero, - curve: Curves.easeOutCubic, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - enableAnimation - ? AnimatedScale( - scale: isSelected ? 1.1 : 1.0, - duration: const Duration(milliseconds: 200), - curve: Curves.easeOutCubic, - child: _buildIcon( - item, - isSelected, - isDark, - primaryColor, - ), - ) - : _buildIcon(item, isSelected, isDark, primaryColor), - const SizedBox(height: 3), - AnimatedDefaultTextStyle( - duration: enableAnimation - ? const Duration(milliseconds: 200) - : Duration.zero, - style: TextStyle( - fontSize: 12, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, - color: isSelected - ? primaryColor - : (isDark ? Colors.grey[400] : Colors.grey[600]), - letterSpacing: 0.15, + child: SizedBox( + height: AppConfig.liquidGlassHeight, // 使用原始高度 + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + enableAnimation + ? AnimatedScale( + scale: isSelected ? 1.1 : 1.0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + child: _buildIcon( + item, + isSelected, + isDark, + primaryColor, + ), + ) + : _buildIcon(item, isSelected, isDark, primaryColor), + const SizedBox(width: 10), + AnimatedDefaultTextStyle( + duration: enableAnimation + ? const Duration(milliseconds: 200) + : Duration.zero, + style: TextStyle( + fontSize: 16, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? primaryColor + : (isDark ? Colors.grey[300] : Colors.grey[700]), + letterSpacing: 0.15, + ), + child: Text(item.label), ), - child: Text(item.label), - ), - ], + ], + ), ), ), ), @@ -201,10 +226,10 @@ class CareModeNavigation extends StatelessWidget { ) { return Icon( item.icon, - size: 24, + size: 28, color: isSelected ? primaryColor - : (isDark ? Colors.grey[400] : Colors.grey[600]), + : (isDark ? Colors.grey[300] : Colors.grey[700]), ); } } diff --git a/lib/widgets/main_navigation.dart b/lib/widgets/main_navigation.dart index ce1cfe0..0e7d11f 100644 --- a/lib/widgets/main_navigation.dart +++ b/lib/widgets/main_navigation.dart @@ -178,7 +178,10 @@ class MainNavigation extends StatelessWidget { /// 使用 Stack 布局,让导航栏悬浮在页面内容上方 Widget _buildCareModeBody(CareController careController) { return Obx(() { - final carePages = [const CarePoetryPage(), const PoetryLevelPage()]; + final carePages = [ + const CarePoetryPage(), + const PoetryLevelPage(showBackButton: false, showAppBar: false), + ]; return Stack( children: [ diff --git a/lib/widgets/tap-liquid-glass.dart b/lib/widgets/tap-liquid-glass.dart index 99f7ab0..6ae3cb0 100644 --- a/lib/widgets/tap-liquid-glass.dart +++ b/lib/widgets/tap-liquid-glass.dart @@ -194,7 +194,7 @@ class TapLiquidGlassNavigation extends StatelessWidget { Widget _buildIcon(_NavItem item, bool isSelected, bool isDark) { final themeController = Get.find(); - + return Icon( item.icon, size: 24, diff --git a/ohos/entry/src/main/module.json5 b/ohos/entry/src/main/module.json5 index 4567eb4..4bcb046 100644 --- a/ohos/entry/src/main/module.json5 +++ b/ohos/entry/src/main/module.json5 @@ -75,8 +75,11 @@ } ], "requestPermissions": [ - {"name" : "ohos.permission.INTERNET"}, - {"name" : "ohos.permission.VIBRATE"} + {"name" : "ohos.permission.INTERNET" + + }, + {"name" : "ohos.permission.VIBRATE"}, + ] } } diff --git a/pubspec.lock b/pubspec.lock index 9ac6ebf..ce0640d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -351,7 +351,7 @@ packages: source: hosted version: "1.0.6" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" diff --git a/pubspec.yaml b/pubspec.yaml index f76f46b..97ceb41 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: url: https://gitcode.com/openharmony-sig/fluttertpc_get pinyin: ^3.3.0 + package_info_plus: ^9.0.1 dev_dependencies: flutter_test: diff --git a/resize_android_icons.py b/resize_android_icons.py deleted file mode 100644 index 882b29c..0000000 --- a/resize_android_icons.py +++ /dev/null @@ -1,40 +0,0 @@ -from PIL import Image -import os - -# 原始图片路径 -original_image_path = r"e:\project\flutter\f3\flutter_application_2\assets\audios\IMG_20260402_170051.png" - -# 安卓端图标目录 -android_res_dir = r"e:\project\flutter\f3\flutter_application_2\android\app\src\main\res" - -# 安卓端图标尺寸配置 (density: (width, height)) -android_sizes = { - "mdpi": (48, 48), - "hdpi": (72, 72), - "xhdpi": (144, 144), - "xxhdpi": (96, 96), - "xxxhdpi": (192, 192), -} - -try: - # 打开原始图片 - with Image.open(original_image_path) as img: - print(f"原始图片尺寸: {img.size}") - - # 为每个密度生成图标 - for density, size in android_sizes.items(): - # 调整尺寸 - resized_img = img.resize(size, Image.Resampling.LANCZOS) - - # 构建输出路径 - output_dir = os.path.join(android_res_dir, f"mipmap-{density}") - os.makedirs(output_dir, exist_ok=True) - - output_path = os.path.join(output_dir, "ic_launcher.png") - resized_img.save(output_path) - print(f"已保存 {density} ({size[0]}x{size[1]}) 图标到: {output_path}") - - print("\n安卓端所有图标尺寸调整完成!") - -except Exception as e: - print(f"处理图片时出错: {e}") diff --git a/resize_icons.py b/resize_icons.py deleted file mode 100644 index 6095782..0000000 --- a/resize_icons.py +++ /dev/null @@ -1,52 +0,0 @@ -from PIL import Image -import os - -# 原始图片路径 -original_image_path = r"e:\project\flutter\f3\flutter_application_2\assets\audios\IMG_20260402_170051.png" - -# 输出目录 -app_scope_dir = r"e:\project\flutter\f3\flutter_application_2\ohos\AppScope\resources\base\media" -entry_media_dir = r"e:\project\flutter\f3\flutter_application_2\ohos\entry\src\main\resources\base\media" - -# 确保目录存在 -os.makedirs(app_scope_dir, exist_ok=True) -os.makedirs(entry_media_dir, exist_ok=True) - -try: - # 打开原始图片 - with Image.open(original_image_path) as img: - print(f"原始图片尺寸: {img.size}") - - # 调整为 288x288 用于 app_icon.png - img_288 = img.resize((288, 288), Image.Resampling.LANCZOS) - app_icon_path = os.path.join(app_scope_dir, "app_icon.png") - img_288.save(app_icon_path) - print(f"已保存 288x288 图标到: {app_icon_path}") - - # 调整为 1024x1024 用于 entry 目录的图标 - img_1024 = img.resize((1024, 1024), Image.Resampling.LANCZOS) - - # 保存到 entry 目录 - entry_icon_path = os.path.join(entry_media_dir, "icon.png") - img_1024.save(entry_icon_path) - print(f"已保存 1024x1024 图标到: {entry_icon_path}") - - # 保存其他尺寸 - sizes = [ - (216, "icon_216.png"), - (48, "icon_48.png"), - (512, "icon_512.png"), - (72, "icon_72.png"), - (96, "icon_96.png"), - ] - - for size, filename in sizes: - resized_img = img.resize((size, size), Image.Resampling.LANCZOS) - output_path = os.path.join(entry_media_dir, filename) - resized_img.save(output_path) - print(f"已保存 {size}x{size} 图标到: {output_path}") - - print("\n所有图标尺寸调整完成!") - -except Exception as e: - print(f"处理图片时出错: {e}") diff --git a/update_android_icons.py b/update_android_icons.py deleted file mode 100644 index 2f5eb24..0000000 --- a/update_android_icons.py +++ /dev/null @@ -1,40 +0,0 @@ -from PIL import Image -import os - -# 新的图片路径 -new_image_path = r"e:\project\flutter\f3\flutter_application_2\assets\IMG_20260402_172628.png" - -# 安卓端图标目录 -android_res_dir = r"e:\project\flutter\f3\flutter_application_2\android\app\src\main\res" - -# 安卓端图标尺寸配置 (density: (width, height)) -android_sizes = { - "mdpi": (48, 48), - "hdpi": (72, 72), - "xhdpi": (144, 144), - "xxhdpi": (96, 96), - "xxxhdpi": (192, 192), -} - -try: - # 打开新图片 - with Image.open(new_image_path) as img: - print(f"新图片尺寸: {img.size}") - - # 为每个密度生成图标 - for density, size in android_sizes.items(): - # 调整尺寸 - resized_img = img.resize(size, Image.Resampling.LANCZOS) - - # 构建输出路径 - output_dir = os.path.join(android_res_dir, f"mipmap-{density}") - os.makedirs(output_dir, exist_ok=True) - - output_path = os.path.join(output_dir, "ic_launcher.png") - resized_img.save(output_path) - print(f"已保存 {density} ({size[0]}x{size[1]}) 图标到: {output_path}") - - print("\n安卓端所有图标更新完成!") - -except Exception as e: - print(f"处理图片时出错: {e}") diff --git a/update_harmony_icons.py b/update_harmony_icons.py deleted file mode 100644 index 7879fe2..0000000 --- a/update_harmony_icons.py +++ /dev/null @@ -1,48 +0,0 @@ -from PIL import Image -import os - -# 新的图片路径 -new_image_path = r"e:\project\flutter\f3\flutter_application_2\assets\IMG_20260402_172628.png" - -# 输出目录 -app_scope_dir = r"e:\project\flutter\f3\flutter_application_2\ohos\AppScope\resources\base\media" -entry_media_dir = r"e:\project\flutter\f3\flutter_application_2\ohos\entry\src\main\resources\base\media" - -try: - # 打开新图片 - with Image.open(new_image_path) as img: - print(f"新图片尺寸: {img.size}") - - # 调整为 288x288 用于 app_icon.png - img_288 = img.resize((288, 288), Image.Resampling.LANCZOS) - app_icon_path = os.path.join(app_scope_dir, "app_icon.png") - img_288.save(app_icon_path) - print(f"已保存 288x288 图标到: {app_icon_path}") - - # 调整为 1024x1024 用于 entry 目录的图标 - img_1024 = img.resize((1024, 1024), Image.Resampling.LANCZOS) - - # 保存到 entry 目录 - entry_icon_path = os.path.join(entry_media_dir, "icon.png") - img_1024.save(entry_icon_path) - print(f"已保存 1024x1024 图标到: {entry_icon_path}") - - # 保存其他尺寸 - sizes = [ - (216, "icon_216.png"), - (48, "icon_48.png"), - (512, "icon_512.png"), - (72, "icon_72.png"), - (96, "icon_96.png"), - ] - - for size, filename in sizes: - resized_img = img.resize((size, size), Image.Resampling.LANCZOS) - output_path = os.path.join(entry_media_dir, filename) - resized_img.save(output_path) - print(f"已保存 {size}x{size} 图标到: {output_path}") - - print("\n鸿蒙端所有图标更新完成!") - -except Exception as e: - print(f"处理图片时出错: {e}")