From 73036a88566529635ce49120a410b77a5c01160d Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 9 Apr 2026 02:23:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=83=85=E6=99=AF=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 38 +- CHANGELOG.md | 44 + WIFI_DEVICE_FIX.md | 155 +++ lib/controllers/settings/is_platform.dart | 228 +++ .../scenario/jinrishici_sdk_config.dart | 150 ++ lib/services/get/profile_controller.dart | 11 +- lib/services/jinrishici_service.dart | 150 ++ lib/utils/http/vote_api.dart | 34 +- lib/views/active/popular_page.dart | 22 +- lib/views/active/tags/corr_page.dart | 22 +- lib/views/footprint/all_list.dart | 22 +- lib/views/home/care/care_widgets.dart | 55 +- lib/views/home/components/pre-page.dart | 1229 +++++++++++++++++ lib/views/home/home_page.dart | 11 +- lib/views/profile/app-info.dart | 39 +- .../components/jinrishici_sdk_page.dart | 1171 ++++++++++++++++ lib/views/profile/components/pop-menu.dart | 45 +- lib/views/profile/expand/manu-script.dart | 25 +- lib/views/profile/guide/app-data.dart | 3 +- lib/views/profile/profile_page.dart | 4 +- pubspec.yaml | 3 +- wifi_device_helper.bat | 70 + wifi_device_helper.ps1 | 108 ++ 23 files changed, 3501 insertions(+), 138 deletions(-) create mode 100644 WIFI_DEVICE_FIX.md create mode 100644 lib/controllers/settings/is_platform.dart create mode 100644 lib/models/scenario/jinrishici_sdk_config.dart create mode 100644 lib/services/jinrishici_service.dart create mode 100644 lib/views/home/components/pre-page.dart create mode 100644 lib/views/profile/components/jinrishici_sdk_page.dart create mode 100644 wifi_device_helper.bat create mode 100644 wifi_device_helper.ps1 diff --git a/.vscode/launch.json b/.vscode/launch.json index c7329f5..2df615c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,25 +1,51 @@ { - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "flutter_application_2", "request": "launch", - "type": "dart" + "type": "dart", + "args": [ + "--device-timeout=60" + ] }, { "name": "flutter_application_2 (profile mode)", "request": "launch", "type": "dart", - "flutterMode": "profile" + "flutterMode": "profile", + "args": [ + "--device-timeout=60" + ] }, { "name": "flutter_application_2 (release mode)", "request": "launch", "type": "dart", - "flutterMode": "release" + "flutterMode": "release", + "args": [ + "--device-timeout=60" + ] + }, + { + "name": "flutter_application_2 (WiFi Device)", + "request": "launch", + "type": "dart", + "deviceId": "192.168.50.213:44263", + "args": [ + "--device-timeout=120", + "--device-connection=wireless" + ] + }, + { + "name": "flutter_application_2 (USB Device)", + "request": "launch", + "type": "dart", + "deviceId": "S48DTKPRVK4H8PKB", + "args": [ + "--device-timeout=30", + "--device-connection=attached" + ] } ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5db29..b074952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,52 @@ All notable changes to this project will be documented in this file. --- +## [1.4.3] - 2026-04-04 + +### 修复 +- 🐛 **修复 Trae 环境下 WiFi 设备安装卡顿问题** + - 问题:在 Trae 中运行到三星平板时,一直显示 "Installing build\app\outputs\flutter-apk\app-debug.apk...",但 Android Studio 能正常安装 + - 根本原因:WiFi 连接的设备建立 VM Service 连接较慢,导致 Flutter 认为安装卡住 + - 解决方案: + - 在 VS Code launch.json 中为所有配置添加 `--device-timeout=60` 参数 + - 新增 WiFi 设备专用配置,使用 `--device-timeout=120` 和 `--device-connection=wireless` + - 新增 USB 设备专用配置,使用 `--device-timeout=30` 和 `--device-connection=attached` + - 创建 WiFi 设备连接辅助脚本(wifi_device_helper.bat 和 wifi_device_helper.ps1) + - 提供手动安装 APK 的选项,绕过 VM Service 连接等待 + - 涉及文件: + - `.vscode/launch.json` - 优化设备连接配置 + - `wifi_device_helper.bat` - Windows 批处理辅助脚本 + - `wifi_device_helper.ps1` - PowerShell 辅助脚本 + - 使用说明: + - 在 VS Code 中运行时,选择 "flutter_application_2 (WiFi Device)" 配置 + - 或使用辅助脚本 `wifi_device_helper.bat` 或 `wifi_device_helper.ps1` + - 如仍卡住,可选择选项 4 手动安装 APK + +--- + ## [1.4.2] - 2026-04-04 +### 新增 +- ✨ **创建统一的平台判断工具类 PlatformUtils** + - 新建 `lib/controllers/settings/is_platform.dart` 文件 + - 统一管理平台判断逻辑,支持 Web、Android、iOS、HarmonyOS、Windows、macOS、Linux 等平台 + - 提供平台检测、操作系统信息获取、平台显示名称等功能 + - 所有平台判断方法都包含异常处理,确保在 Web 平台也能正常工作 + +### 重构 +- ♻️ **统一平台判断代码** + - 将分散在多个文件中的平台判断代码统一使用 PlatformUtils + - 移除了各文件中重复的 `import 'dart:io'` 和 `import 'package:flutter/foundation.dart'` + - 简化了平台判断逻辑,提高了代码可维护性 + - 涉及文件: + - `lib/views/profile/app-info.dart` - 使用 PlatformUtils 替代 io.Platform + - `lib/views/profile/expand/manu-script.dart` - 使用 PlatformUtils.platformDisplayName + - `lib/views/profile/profile_page.dart` - 使用 PlatformUtils.isWeb + - `lib/views/profile/components/pop-menu.dart` - 使用 PlatformUtils + - `lib/services/get/profile_controller.dart` - 使用 PlatformUtils + - `lib/views/profile/guide/app-data.dart` - 使用 PlatformUtils.pathSeparator + - `lib/utils/http/vote_api.dart` - 使用 PlatformUtils + ### 修复 - 🐛 **修复三星平板卡 Logo 启动页的问题** - 将屏幕适配从初始化阶段移到第一帧绘制后执行 diff --git a/WIFI_DEVICE_FIX.md b/WIFI_DEVICE_FIX.md new file mode 100644 index 0000000..7ee90e9 --- /dev/null +++ b/WIFI_DEVICE_FIX.md @@ -0,0 +1,155 @@ +# WiFi 设备连接问题解决方案 + +## 问题描述 + +在 Trae 环境下,运行应用到三星平板(WiFi 连接)时,一直显示 "Installing build\app\outputs\flutter-apk\app-debug.apk...",但 Android Studio 能正常安装。 + +## 根本原因 + +WiFi 连接的设备建立 VM Service 连接较慢,导致 Flutter 认为安装卡住。实际上 APK 已经成功安装,只是 Flutter 在等待 VM Service 端口连接时超时。 + +## 解决方案 + +### 方案 1:使用 VS Code 专用配置(推荐) + +1. 在 VS Code 中按 `F5` 或点击调试按钮 +2. 在配置下拉菜单中选择 **"flutter_application_2 (WiFi Device)"** +3. 该配置已优化为: + - 设备超时时间:120 秒 + - 连接类型:仅 WiFi 设备 + - 目标设备:192.168.50.213:44263(三星平板) + +### 方案 2:使用辅助脚本 + +#### Windows 批处理脚本 +```bash +wifi_device_helper.bat +``` + +#### PowerShell 脚本 +```powershell +.\wifi_device_helper.ps1 +``` + +脚本提供以下功能: +- 选项 1:运行到 WiFi 设备(三星平板) +- 选项 2:运行到 USB 设备 +- 选项 3:仅构建 APK +- 选项 4:手动安装 APK 到 WiFi 设备(绕过 VM Service 连接) +- 选项 5:检查设备连接状态 +- 选项 6:使用详细日志运行(调试用) + +### 方案 3:手动命令 + +#### 运行到 WiFi 设备 +```bash +flutter run --device-id=192.168.50.213:44263 --device-timeout=120 --device-connection=wireless +``` + +#### 仅构建 APK +```bash +flutter build apk --debug +``` + +#### 手动安装 APK +```bash +# 构建后手动安装 +flutter build apk --debug + +# 使用 ADB 安装 +E:\sdk\android\platform-tools\adb.exe -s 192.168.50.213:44263 install -r build\app\outputs\flutter-apk\app-debug.apk + +# 启动应用 +E:\sdk\android\platform-tools\adb.exe -s 192.168.50.213:44263 shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER app.wushu.poes/app.wushu.poes.MainActivity +``` + +## 配置说明 + +### VS Code launch.json 配置 + +所有配置已添加 `--device-timeout=60` 参数,默认超时时间从 10 秒增加到 60 秒。 + +#### 新增配置 + +**WiFi 设备配置:** +```json +{ + "name": "flutter_application_2 (WiFi Device)", + "request": "launch", + "type": "dart", + "deviceId": "192.168.50.213:44263", + "args": [ + "--device-timeout=120", + "--device-connection=wireless" + ] +} +``` + +**USB 设备配置:** +```json +{ + "name": "flutter_application_2 (USB Device)", + "request": "launch", + "type": "dart", + "deviceId": "S48DTKPRVK4H8PKB", + "args": [ + "--device-timeout=30", + "--device-connection=attached" + ] +} +``` + +## 常见问题 + +### Q: 为什么 Android Studio 能正常安装? + +A: Android Studio 有更完善的超时处理和重试机制,并且对 WiFi 设备有专门的优化。 + +### Q: 如何确认设备已连接? + +A: 运行以下命令: +```bash +flutter devices +# 或 +E:\sdk\android\platform-tools\adb.exe devices +``` + +### Q: 如果还是卡住怎么办? + +A: 使用辅助脚本的选项 4(手动安装 APK),这会绕过 VM Service 连接等待。 + +### Q: 如何查看详细日志? + +A: 使用 `--verbose` 参数: +```bash +flutter run --device-id=192.168.50.213:44263 --verbose +``` + +或使用辅助脚本的选项 6。 + +## 技术细节 + +### 问题分析过程 + +1. **设备连接检查**:Flutter 和 ADB 都能识别到设备 +2. **APK 构建**:构建成功完成 +3. **安装过程**:APK 实际已安装成功(显示 "Success") +4. **卡住位置**:在 "Waiting for VM Service port to be available..." 阶段 +5. **根本原因**:WiFi 连接延迟导致 VM Service 端口连接建立较慢 + +### 解决方案原理 + +1. **增加超时时间**:从默认 10 秒增加到 60-120 秒 +2. **指定连接类型**:使用 `--device-connection=wireless` 优化 WiFi 连接 +3. **手动安装**:绕过 VM Service 连接等待,直接安装 APK + +## 相关文件 + +- `.vscode/launch.json` - VS Code 调试配置 +- `wifi_device_helper.bat` - Windows 批处理辅助脚本 +- `wifi_device_helper.ps1` - PowerShell 辅助脚本 +- `CHANGELOG.md` - 更新日志(版本 1.4.3) + +## 更新日志 + +详见 [CHANGELOG.md](CHANGELOG.md) 版本 1.4.3。 \ No newline at end of file diff --git a/lib/controllers/settings/is_platform.dart b/lib/controllers/settings/is_platform.dart new file mode 100644 index 0000000..cba237d --- /dev/null +++ b/lib/controllers/settings/is_platform.dart @@ -0,0 +1,228 @@ +import 'dart:io' as io show Platform; +import 'package:flutter/foundation.dart' show kIsWeb; + +/// 时间: 2026-04-04 +/// 功能: 平台判断工具类 +/// 介绍: 统一管理平台判断逻辑,支持 Web、Android、iOS、HarmonyOS、Windows、macOS、Linux 等平台 +/// 最新变化: 新建文件,统一平台判断逻辑 + +class PlatformUtils { + PlatformUtils._(); + + /// 是否是 Web 平台 + static bool get isWeb => kIsWeb; + + /// 是否是移动平台(Android、iOS、HarmonyOS) + static bool get isMobile => isAndroid || isIOS || isHarmonyOS; + + /// 是否是桌面平台(Windows、macOS、Linux) + static bool get isDesktop => isWindows || isMacOS || isLinux; + + /// 是否是 Android 平台 + static bool get isAndroid { + if (kIsWeb) return false; + try { + return io.Platform.isAndroid; + } catch (e) { + return false; + } + } + + /// 是否是 iOS 平台 + static bool get isIOS { + if (kIsWeb) return false; + try { + return io.Platform.isIOS; + } catch (e) { + return false; + } + } + + /// 是否是鸿蒙平台 + static bool get isHarmonyOS { + if (kIsWeb) return false; + try { + final osName = io.Platform.operatingSystem.toLowerCase(); + return osName == 'ohos' || + osName == 'harmonyos' || + osName == 'openharmony'; + } catch (e) { + return false; + } + } + + /// 是否是 Windows 平台 + static bool get isWindows { + if (kIsWeb) return false; + try { + return io.Platform.isWindows; + } catch (e) { + return false; + } + } + + /// 是否是 macOS 平台 + static bool get isMacOS { + if (kIsWeb) return false; + try { + return io.Platform.isMacOS; + } catch (e) { + return false; + } + } + + /// 是否是 Linux 平台 + static bool get isLinux { + if (kIsWeb) return false; + try { + return io.Platform.isLinux; + } catch (e) { + return false; + } + } + + /// 是否是 Fuchsia 平台 + static bool get isFuchsia { + if (kIsWeb) return false; + try { + return io.Platform.isFuchsia; + } catch (e) { + return false; + } + } + + /// 获取操作系统名称 + static String get operatingSystem { + if (kIsWeb) return 'web'; + try { + return io.Platform.operatingSystem; + } catch (e) { + return 'unknown'; + } + } + + /// 获取操作系统版本 + static String get operatingSystemVersion { + if (kIsWeb) return 'web browser'; + try { + return io.Platform.operatingSystemVersion; + } catch (e) { + return 'unknown'; + } + } + + /// 获取平台显示名称(带 Flutter 后缀) + static String get platformDisplayName { + if (kIsWeb) return 'Web Flutter'; + + try { + final osName = io.Platform.operatingSystem.toLowerCase(); + + if (osName == 'ohos' || + osName == 'harmonyos' || + osName == 'openharmony') { + return 'HarmonyOS Flutter'; + } else if (io.Platform.isAndroid) { + return 'Android Flutter'; + } else if (io.Platform.isIOS) { + return 'iOS Flutter'; + } else if (io.Platform.isWindows) { + return 'Windows Flutter'; + } else if (io.Platform.isMacOS) { + return 'macOS Flutter'; + } else if (io.Platform.isLinux) { + return 'Linux Flutter'; + } else if (io.Platform.isFuchsia) { + return 'Fuchsia Flutter'; + } else { + return 'Flutter'; + } + } catch (e) { + return 'Flutter'; + } + } + + /// 获取简短的平台名称 + static String get platformShortName { + if (kIsWeb) return 'Web'; + + try { + final osName = io.Platform.operatingSystem.toLowerCase(); + + if (osName == 'ohos' || + osName == 'harmonyos' || + osName == 'openharmony') { + return 'HarmonyOS'; + } else if (io.Platform.isAndroid) { + return 'Android'; + } else if (io.Platform.isIOS) { + return 'iOS'; + } else if (io.Platform.isWindows) { + return 'Windows'; + } else if (io.Platform.isMacOS) { + return 'macOS'; + } else if (io.Platform.isLinux) { + return 'Linux'; + } else if (io.Platform.isFuchsia) { + return 'Fuchsia'; + } else { + return 'Unknown'; + } + } catch (e) { + return 'Unknown'; + } + } + + /// 获取路径分隔符 + static String get pathSeparator { + if (kIsWeb) return '/'; + try { + return io.Platform.pathSeparator; + } catch (e) { + return '/'; + } + } + + /// 获取本地主机名 + static String get localHostname { + if (kIsWeb) return 'web'; + try { + return io.Platform.localHostname; + } catch (e) { + return 'unknown'; + } + } + + /// 获取环境变量 + static Map get environment { + if (kIsWeb) return {}; + try { + return io.Platform.environment; + } catch (e) { + return {}; + } + } + + /// 获取 Dart 版本 + static String get dartVersion { + if (kIsWeb) return 'web'; + try { + return io.Platform.version; + } catch (e) { + return 'unknown'; + } + } + + /// 获取平台信息摘要 + static String get platformSummary { + if (kIsWeb) return 'Web Browser'; + + try { + final osName = operatingSystem; + final osVersion = operatingSystemVersion; + return '$osName ($osVersion)'; + } catch (e) { + return 'Unknown Platform'; + } + } +} diff --git a/lib/models/scenario/jinrishici_sdk_config.dart b/lib/models/scenario/jinrishici_sdk_config.dart new file mode 100644 index 0000000..d0b2818 --- /dev/null +++ b/lib/models/scenario/jinrishici_sdk_config.dart @@ -0,0 +1,150 @@ +/// 今日诗词安卓 SDK 用法和接口文档 +/// +/// 创建时间:2026-04-08 +/// 作用:记录今日诗词 SDK 的使用方法和 API 接口信息 +/// 最后更新:2026-04-08 - 初始创建 + +/// 今日诗词 SDK 配置信息 +class JinrishiciSdkConfig { + /// SDK 名称 + static const String sdkName = '今日诗词安卓 SDK'; + + /// SDK 包名 + static const String packageName = 'com.jinrishici:android-sdk'; + + /// GitHub 仓库地址 + static const String githubUrl = 'https://github.com/xenv/jinrishici-sdk-android'; + + /// 许可证 + static const String license = 'BSD 3-Clause "New" or "Revised" License'; + + /// Token 获取接口 + static const String tokenUrl = 'https://v2.jinrishici.com/token'; + + /// 获取诗词接口 + static const String sentenceUrl = 'https://v2.jinrishici.com/sentence'; + + /// SDK 版本号(需要根据实际发布版本更新) + static const String version = '{release-version}'; +} + +/// SDK 使用方法说明 +class JinrishiciSdkUsage { + /// 初始化方法 + static const String initMethod = ''' +// 初始化(两种方式任选一种) +JinrishiciFactory.init(getContext()); +JinrishiciClient.getInstance().init(getContext()); +'''; + + /// 异步获取诗词方法 + static const String asyncMethod = ''' +// 异步方法 +JinrishiciClient client = JinrishiciClient.getInstance(); +client.getOneSentenceBackground(new JinrishiciCallback() { + @Override + public void done(PoetySentence poetySentence) { + // 处理成功结果 + } + + @Override + public void error(JinrishiciRuntimeException e) { + // 处理错误 + } +}); +'''; + + /// 同步获取诗词方法 + static const String syncMethod = ''' +// 同步方法(会抛出 JinrishiciRuntimeException) +PoetySentence poetySentence = JinrishiciClient.getInstance().getOneSentence(); +'''; + + /// 自定义控件使用方法 + static const String customWidget = ''' +// XML 布局中使用 + +'''; +} + +/// SDK 自定义控件属性 +class JinrishiciWidgetAttributes { + /// 点击 TextView 时是否刷新 + static const String refreshOnClick = 'jrsc_refresh_on_click'; + + /// 当请求出现错误时,是否直接将错误信息显示到 TextView 上 + static const String showError = 'jrsc_show_error'; + + /// 是否在加载数据时显示加载文本 + static const String showLoadingText = 'jrsc_show_loading_text'; + + /// 加载数据时显示的文本 + static const String textLoading = 'jrsc_text_loading'; + + /// 加载失败时显示的文本 + static const String textError = 'jrsc_text_error'; +} + +/// SDK 工作流程 +class JinrishiciSdkWorkflow { + /// 工作流程步骤 + static const List steps = [ + 'SDK 首先从本地 SharedPreferences 检查是否有缓存的 token', + '如果没有 token,调用 https://v2.jinrishici.com/token 获取 token', + '使用 token 调用 https://v2.jinrishici.com/one.json 获取诗词内容', + '返回 PoetySentence 对象,包含诗词内容、来源、作者等信息', + ]; + + /// Gradle 依赖配置 + static const String gradleDependency = ''' +implementation 'com.jinrishici:android-sdk:{release-version}' +'''; + + /// Maven 依赖配置 + static const String mavenDependency = ''' + + com.jinrishici + android-sdk + {release-version} + pom + +'''; +} + +/// SDK 数据模型 +class JinrishiciSdkData { + /// PoetySentence - 诗词句子对象 + static const Map poetySentenceFields = { + 'ipAddress': 'IP 地址', + 'data': '诗词数据对象(DataBean)', + }; + + /// DataBean - 诗词数据对象 + static const Map dataBeanFields = { + 'content': '诗词内容', + 'origin': '诗词来源信息(OriginBean)', + 'matchTags': '匹配标签', + 'recommendedReason': '推荐理由', + }; + + /// OriginBean - 诗词来源对象 + static const Map originBeanFields = { + 'title': '诗词标题', + 'dynasty': '朝代', + 'author': '作者', + 'content': '完整诗词内容', + 'translate': '翻译', + 'note': '注释', + }; + + /// PoetyToken - Token 对象 + static const Map poetyTokenFields = { + 'token': '访问令牌', + 'status': '状态', + }; +} diff --git a/lib/services/get/profile_controller.dart b/lib/services/get/profile_controller.dart index 3015e3e..16506f9 100644 --- a/lib/services/get/profile_controller.dart +++ b/lib/services/get/profile_controller.dart @@ -1,15 +1,14 @@ import 'dart:convert'; import 'dart:math' show Random; -import 'dart:io' as io; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; import '../../controllers/history_controller.dart'; import '../../controllers/shared_preferences_storage_controller.dart'; +import '../../controllers/settings/is_platform.dart'; import '../isweb/wakelock_service.dart'; import '../../views/profile/guide/tongji.dart'; import 'theme_controller.dart'; @@ -217,7 +216,7 @@ class ProfileController extends GetxController with WidgetsBindingObserver { Future toggleScreenWake(bool enable) async { final themeController = Get.find(); // Web 平台不支持 wakelock_plus - if (kIsWeb) { + if (PlatformUtils.isWeb) { Get.snackbar( '提示', 'Web 平台不支持屏幕常亮功能', @@ -227,9 +226,9 @@ class ProfileController extends GetxController with WidgetsBindingObserver { } try { - // 使用 io.Platform 检测平台 - final String osName = io.Platform.operatingSystem; - final String osVersion = io.Platform.operatingSystemVersion; + // 获取平台信息 + final String osName = PlatformUtils.operatingSystem; + final String osVersion = PlatformUtils.operatingSystemVersion; print('Current platform: $osName, version: $osVersion'); if (enable) { diff --git a/lib/services/jinrishici_service.dart b/lib/services/jinrishici_service.dart new file mode 100644 index 0000000..4762bfa --- /dev/null +++ b/lib/services/jinrishici_service.dart @@ -0,0 +1,150 @@ +/// 今日诗词 API 服务 +/// +/// 创建时间:2026-04-08 +/// 作用:调用今日诗词 API 获取每日诗词 +/// 最后更新:2026-04-08 - 初始创建 + +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../models/scenario/jinrishici_sdk_config.dart'; + +class JinrishiciService { + static final JinrishiciService _instance = JinrishiciService._internal(); + factory JinrishiciService() => _instance; + JinrishiciService._internal(); + + final Dio _dio = Dio(BaseOptions( + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + )); + + static const String _tokenKey = 'jinrishici_token'; + + /// 获取 token + Future _getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_tokenKey); + } + + /// 保存 token + Future _saveToken(String token) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_tokenKey, token); + } + + /// 生成 token + Future generateToken() async { + try { + print('正在请求 token: ${JinrishiciSdkConfig.tokenUrl}'); + final response = await _dio.get(JinrishiciSdkConfig.tokenUrl); + print('Token 响应状态码: ${response.statusCode}'); + print('Token 响应数据: ${response.data}'); + + if (response.statusCode == 200 && response.data != null) { + // 根据官方文档,token 在 data 字段中 + String? token; + if (response.data is Map) { + token = response.data['data'] as String?; + } + + if (token != null && token.isNotEmpty) { + await _saveToken(token); + print('Token 获取成功: $token'); + return token; + } + } + throw Exception('获取 token 失败: 响应数据格式不正确'); + } catch (e) { + print('获取 token 异常: $e'); + throw Exception('获取 token 异常: $e'); + } + } + + /// 获取今日诗词 + Future> getTodayPoetry() async { + try { + // 获取或生成 token + String? token = await _getToken(); + if (token == null || token.isEmpty) { + try { + token = await generateToken(); + } catch (e) { + print('Token 获取失败,尝试不带 token 请求: $e'); + token = null; + } + } + + // 使用 token 获取诗词 + print('正在请求诗词: ${JinrishiciSdkConfig.sentenceUrl}'); + final response = await _dio.get( + JinrishiciSdkConfig.sentenceUrl, + options: token != null + ? Options( + headers: { + 'X-User-Token': token, + }, + ) + : null, + ); + + print('诗词响应状态码: ${response.statusCode}'); + print('诗词响应数据: ${response.data}'); + + if (response.statusCode == 200 && response.data != null) { + return response.data as Map; + } + + throw Exception('获取诗词失败'); + } catch (e) { + print('获取诗词异常: $e'); + throw Exception('获取诗词异常: $e'); + } + } + + /// 清除缓存的 token + Future clearToken() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_tokenKey); + } + + /// 获取用户信息(IP、地区、天气等) + Future> getUserInfo() async { + try { + // 获取或生成 token + String? token = await _getToken(); + if (token == null || token.isEmpty) { + try { + token = await generateToken(); + } catch (e) { + print('Token 获取失败,尝试不带 token 请求: $e'); + token = null; + } + } + + // 调用 info 接口 + print('正在请求用户信息: https://v2.jinrishici.com/info'); + final response = await _dio.get( + 'https://v2.jinrishici.com/info', + options: token != null + ? Options( + headers: { + 'X-User-Token': token, + }, + ) + : null, + ); + + print('用户信息响应状态码: ${response.statusCode}'); + print('用户信息响应数据: ${response.data}'); + + if (response.statusCode == 200 && response.data != null) { + return response.data as Map; + } + + throw Exception('获取用户信息失败'); + } catch (e) { + print('获取用户信息异常: $e'); + throw Exception('获取用户信息异常: $e'); + } + } +} diff --git a/lib/utils/http/vote_api.dart b/lib/utils/http/vote_api.dart index 4ea43d6..bcbf28f 100644 --- a/lib/utils/http/vote_api.dart +++ b/lib/utils/http/vote_api.dart @@ -4,12 +4,12 @@ /// 最新变化: 添加Cookie管理器支持PHP Session认证 import 'dart:convert'; -import 'dart:io' as io show Platform; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:platform_info/platform_info.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../controllers/settings/is_platform.dart'; class VoteApi { static const String _baseUrl = 'https://poe.vogov.cn/toupiao/'; @@ -246,34 +246,30 @@ class VoteApi { String platformName = 'Unknown'; try { - final String osName = io.Platform.operatingSystem; - final String osVersion = io.Platform.operatingSystemVersion.toLowerCase(); - - if (osName == 'ohos' || - osName == 'harmonyos' || - osName == 'openharmony') { + if (PlatformUtils.isHarmonyOS) { platformName = 'HarmonyOS'; isHarmonyOS = true; - } else if (io.Platform.isAndroid) { + } else if (PlatformUtils.isAndroid) { platformName = 'Android'; + final osVersion = PlatformUtils.operatingSystemVersion.toLowerCase(); if (osVersion.contains('harmony') || osVersion.contains('ohos') || osVersion.contains('openharmony')) { platformName = 'HarmonyOS'; isHarmonyOS = true; } - } else if (io.Platform.isIOS) { + } else if (PlatformUtils.isIOS) { platformName = 'iOS'; - } else if (io.Platform.isMacOS) { + } else if (PlatformUtils.isMacOS) { platformName = 'macOS'; - } else if (io.Platform.isWindows) { + } else if (PlatformUtils.isWindows) { platformName = 'Windows'; - } else if (io.Platform.isLinux) { + } else if (PlatformUtils.isLinux) { platformName = 'Linux'; - } else if (io.Platform.isFuchsia) { + } else if (PlatformUtils.isFuchsia) { platformName = 'Fuchsia'; } else { - platformName = osName[0].toUpperCase() + osName.substring(1); + platformName = PlatformUtils.platformShortName; } } catch (e) { platformName = switch (platform.operatingSystem) { @@ -289,7 +285,7 @@ class VoteApi { String deviceType = 'Unknown'; if (isHarmonyOS) { - final String osName = io.Platform.operatingSystem; + final osName = PlatformUtils.operatingSystem.toLowerCase(); if (osName == 'ohos') { deviceType = 'OHOS'; } else if (osName == 'harmonyos') { @@ -302,12 +298,12 @@ class VoteApi { } else { deviceType = platform.when( - mobile: () => 'Mobile', - desktop: () => 'Desktop', - js: () => 'Web', + mobile: () => '移动设备', + desktop: () => '桌面设备', + js: () => 'Web浏览器', orElse: () => null, ) ?? - 'Unknown'; + '未知设备'; } return '$platformName-$deviceType-Flutter'; diff --git a/lib/views/active/popular_page.dart b/lib/views/active/popular_page.dart index f5bc5af..7f294ca 100644 --- a/lib/views/active/popular_page.dart +++ b/lib/views/active/popular_page.dart @@ -270,18 +270,20 @@ class _PopularPageState extends State NetworkEventType.noteUpdate, data: noteId, ); - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('已创建笔记'))); - } + Get.snackbar( + '成功', + '已创建笔记', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } catch (e) { - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('创建笔记失败: $e'))); - } + Get.snackbar( + '错误', + '创建笔记失败: $e', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } diff --git a/lib/views/active/tags/corr_page.dart b/lib/views/active/tags/corr_page.dart index 50fcf3b..cf08443 100644 --- a/lib/views/active/tags/corr_page.dart +++ b/lib/views/active/tags/corr_page.dart @@ -684,18 +684,20 @@ class _CorrPageState extends State NetworkEventType.noteUpdate, data: noteId, ); - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('已创建笔记'))); - } + Get.snackbar( + '成功', + '已创建笔记', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } catch (e) { - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('创建笔记失败: $e'))); - } + Get.snackbar( + '错误', + '创建笔记失败: $e', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } diff --git a/lib/views/footprint/all_list.dart b/lib/views/footprint/all_list.dart index 3a69fb8..ac1b07c 100644 --- a/lib/views/footprint/all_list.dart +++ b/lib/views/footprint/all_list.dart @@ -670,19 +670,21 @@ class AllListPageState extends State { NetworkEventType.noteUpdate, data: noteId, ); - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('已创建笔记'))); - } + Get.snackbar( + '成功', + '已创建笔记', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } catch (e) { debugPrint('创建笔记失败: $e'); - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('创建笔记失败: $e'))); - } + Get.snackbar( + '错误', + '创建笔记失败: $e', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); } } diff --git a/lib/views/home/care/care_widgets.dart b/lib/views/home/care/care_widgets.dart index 57ef134..b011271 100644 --- a/lib/views/home/care/care_widgets.dart +++ b/lib/views/home/care/care_widgets.dart @@ -1,13 +1,66 @@ /// 时间: 2026-04-02 /// 功能: 关怀模式相关组件 /// 介绍: 包含关怀按钮和开关的UI组件 -/// 最新变化: 2026-04-02 初始创建 +/// 最新变化: 2026-04-09 添加情景推荐按钮组件 import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../services/get/theme_controller.dart'; import '../../../services/get/care_controller.dart'; import 'care-page.dart'; +import '../components/pre-page.dart'; + +/// 情景推荐按钮组件 +class PrePageButton extends StatelessWidget { + const PrePageButton({super.key, required this.isDark}); + + final bool isDark; + + @override + Widget build(BuildContext context) { + final themeController = Get.find(); + final primaryColor = themeController.currentThemeColor; + + return GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const PrePage(), + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(isDark ? 40 : 20), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.auto_stories, color: primaryColor, size: 20), + const SizedBox(width: 6), + Text( + '情景推荐', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + ), + ); + } +} /// 关怀按钮组件 class CareButton extends StatelessWidget { diff --git a/lib/views/home/components/pre-page.dart b/lib/views/home/components/pre-page.dart new file mode 100644 index 0000000..dd167a7 --- /dev/null +++ b/lib/views/home/components/pre-page.dart @@ -0,0 +1,1229 @@ +/// 时间: 2026-04-09 +/// 功能: 情景推荐页面 +/// 介绍: 展示今日诗词SDK的详细信息,包括诗词内容、环境信息等 +/// 最新变化: 2026-04-09 添加骨架屏、分享功能 + +import 'dart:convert'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; +import '../../../services/get/theme_controller.dart'; +import '../../../services/jinrishici_service.dart'; +import '../../../services/network_listener_service.dart'; +import '../../../models/colors/app_colors.dart'; +import '../../../controllers/history_controller.dart'; +import './skeleton_widgets.dart'; + +class PrePage extends StatefulWidget { + const PrePage({super.key}); + + @override + State createState() => _PrePageState(); +} + +class _PrePageState extends State with SingleTickerProviderStateMixin { + final ThemeController _themeController = Get.find(); + final JinrishiciService _jinrishiciService = JinrishiciService(); + final GlobalKey _repaintKey = GlobalKey(); + late AnimationController _fadeController; + late Animation _fadeAnimation; + + Map? _poetryData; + Map? _userInfo; + bool _isLoading = false; + String? _errorMessage; + bool _isMetadataExpanded = false; + bool _isRefreshLocked = false; + + @override + void initState() { + super.initState(); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + _fadeAnimation = CurvedAnimation( + parent: _fadeController, + curve: Curves.easeIn, + ); + _fadeController.forward(); + _loadData(); + } + + Future _loadData() async { + if (_isRefreshLocked) return; + + setState(() { + _isLoading = true; + _errorMessage = null; + _isRefreshLocked = true; + }); + + try { + final poetry = await _jinrishiciService.getTodayPoetry(); + setState(() { + _poetryData = poetry; + _isLoading = false; + }); + + try { + final userInfo = await _jinrishiciService.getUserInfo(); + setState(() { + _userInfo = userInfo; + }); + } catch (e) { + print('加载用户信息失败: $e'); + } + } catch (e) { + setState(() { + _errorMessage = e.toString(); + _isLoading = false; + }); + } + + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + _isRefreshLocked = false; + }); + } + }); + } + + Future _captureAndShare() async { + try { + Get.snackbar('提示', '正在生成图片...'); + + await Future.delayed(const Duration(milliseconds: 100)); + + if (!mounted) return; + + final boundary = + _repaintKey.currentContext?.findRenderObject() + as RenderRepaintBoundary?; + if (boundary == null) { + Get.snackbar('错误', '生成图片失败'); + return; + } + + if (boundary.debugNeedsPaint) { + await Future.delayed(const Duration(milliseconds: 100)); + } + + final image = await boundary.toImage(pixelRatio: 3.0); + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + Get.snackbar('错误', '生成图片失败'); + return; + } + + final pngBytes = byteData.buffer.asUint8List(); + final directory = await getTemporaryDirectory(); + final file = File( + '${directory.path}/poetry_${DateTime.now().millisecondsSinceEpoch}.png', + ); + await file.writeAsBytes(pngBytes); + + final result = await Share.shareXFiles( + [XFile(file.path)], + subject: '情景诗词分享', + text: '来自情景诗词App的分享', + ); + + if (result.status == ShareResultStatus.success) { + Get.snackbar('成功', '分享成功'); + } else if (result.status == ShareResultStatus.dismissed) { + Get.snackbar('提示', '分享已取消'); + } + + if (await file.exists()) { + await file.delete(); + } + } catch (e) { + print('分享失败: $e'); + Get.snackbar('错误', '分享失败: $e'); + } + } + + Future _createNoteFromPoetry() async { + if (_poetryData == null) { + Get.snackbar('提示', '暂无诗词数据'); + return; + } + + try { + final data = _poetryData!['data'] as Map?; + final origin = data?['origin'] as Map?; + + final title = origin?['title']?.toString() ?? '情景诗词笔记'; + final category = '情景诗词'; + final contentBuffer = StringBuffer(); + + if (origin != null) { + if (origin['title'] != null) { + contentBuffer.writeln('标题:${origin['title']}'); + } + if (origin['dynasty'] != null) { + contentBuffer.writeln('朝代:${origin['dynasty']}'); + } + if (origin['author'] != null) { + contentBuffer.writeln('作者:${origin['author']}'); + } + if (data?['content'] != null) { + contentBuffer.writeln('诗句:${data!['content']}'); + } + if (origin['content'] != null) { + final fullContent = (origin['content'] as List) + .map((e) => e.toString()) + .join('\n'); + contentBuffer.writeln('全文:\n$fullContent'); + } + if (origin['translate'] != null) { + final translate = (origin['translate'] as List) + .map((e) => e.toString()) + .join('\n'); + contentBuffer.writeln('译文:\n$translate'); + } + } + + final noteId = await HistoryController.saveNote( + title: title, + content: contentBuffer.toString().trim(), + category: category, + ); + + if (noteId != null) { + NetworkListenerService().sendSuccessEvent( + NetworkEventType.noteUpdate, + data: noteId, + ); + Get.snackbar( + '成功', + '已创建笔记', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); + } + } catch (e) { + Get.snackbar( + '错误', + '创建笔记失败: $e', + snackPosition: SnackPosition.BOTTOM, + colorText: _themeController.currentThemeColor, + ); + } + } + + @override + void dispose() { + _fadeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Obx(() { + final isDark = _themeController.isDarkMode; + final primaryColor = _themeController.currentThemeColor; + + return Scaffold( + backgroundColor: isDark ? const Color(0xFF1A1A1A) : Colors.grey[50], + appBar: AppBar( + title: Text( + '情景推荐', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17, + color: primaryColor, + ), + ), + backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, + foregroundColor: primaryColor, + elevation: 0, + centerTitle: true, + actions: [ + GestureDetector( + onTap: _isRefreshLocked ? null : _loadData, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + if (_isLoading) + SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + isDark ? Colors.grey[500]! : Colors.grey[400]!, + ), + ), + ) + else + Icon( + Icons.refresh, + color: _isRefreshLocked + ? (isDark ? Colors.grey[500] : Colors.grey[400]) + : primaryColor, + size: 20, + ), + const SizedBox(width: 4), + Text( + '刷新', + style: TextStyle( + fontSize: 14, + color: _isRefreshLocked + ? (isDark ? Colors.grey[500] : Colors.grey[400]) + : primaryColor, + ), + ), + ], + ), + ), + ), + ], + ), + body: FadeTransition( + opacity: _fadeAnimation, + child: _buildBody(isDark, primaryColor), + ), + ); + }); + } + + Widget _buildBody(bool isDark, Color primaryColor) { + if (_isLoading && _poetryData == null) { + return _buildSkeletonBody(isDark, primaryColor); + } + + if (_errorMessage != null && _poetryData == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.red[400]), + const SizedBox(height: 16), + Text( + '加载失败', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Text( + _errorMessage!, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: _loadData, + icon: const Icon(Icons.refresh), + label: const Text('重试'), + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ); + } + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + RepaintBoundary( + key: _repaintKey, + child: _buildPoetryCard(isDark, primaryColor), + ), + if (_userInfo != null) ...[ + const SizedBox(height: 16), + _buildUserInfoCard(isDark, primaryColor), + ], + const SizedBox(height: 100), + ], + ); + } + + Widget _buildSkeletonBody(bool isDark, Color primaryColor) { + final baseColor = isDark ? const Color(0xFF3A3A3A) : Colors.grey[300]; + final highlightColor = isDark ? const Color(0xFF4A4A4A) : Colors.grey[100]; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(isDark ? 40 : 10), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SkeletonContainer( + width: 40, + height: 40, + borderRadius: 12, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(width: 12), + SkeletonContainer( + width: 100, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ), + const SizedBox(height: 16), + SkeletonContainer( + width: 180, + height: 24, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 12), + SkeletonContainer( + width: 120, + height: 16, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A) : Colors.grey[100], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + SkeletonContainer( + width: double.infinity, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: double.infinity, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(height: 8), + SkeletonContainer( + width: 150, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + 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, + ), + ], + ), + ], + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(isDark ? 40 : 10), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SkeletonContainer( + width: 40, + height: 40, + borderRadius: 12, + baseColor: baseColor, + highlightColor: highlightColor, + ), + const SizedBox(width: 12), + SkeletonContainer( + width: 80, + height: 20, + borderRadius: 4, + baseColor: baseColor, + highlightColor: highlightColor, + ), + ], + ), + const SizedBox(height: 16), + 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, + ), + ], + ), + ), + ], + ); + } + + Widget _buildPoetryCard(bool isDark, Color primaryColor) { + if (_poetryData == null) { + return _buildSectionCard( + '情景诗词', + Icons.book, + [ + Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + '暂无数据', + style: TextStyle( + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ), + ], + isDark, + primaryColor, + ); + } + + final data = _poetryData!['data'] as Map?; + final origin = data?['origin'] as Map?; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [primaryColor.withAlpha(30), primaryColor.withAlpha(10)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withAlpha(50), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Text('📜', style: TextStyle(fontSize: 20)), + ), + ), + const SizedBox(width: 12), + Text( + '情景诗词', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + Row( + children: [ + IconButton( + onPressed: _captureAndShare, + icon: Icon(Icons.share, color: primaryColor, size: 22), + tooltip: '分享', + ), + IconButton( + onPressed: _createNoteFromPoetry, + icon: Icon(Icons.note_add, color: primaryColor, size: 22), + tooltip: '写入笔记', + ), + ], + ), + ], + ), + const SizedBox(height: 16), + if (origin != null) ...[ + Text( + origin['title'] ?? '未知标题', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + origin['dynasty'] ?? '未知朝代', + style: TextStyle( + fontSize: 14, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + Text( + '·', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + const SizedBox(width: 8), + Text( + origin['author'] ?? '未知作者', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ], + ), + const SizedBox(height: 16), + ], + if (data != null) ...[ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark + ? const Color(0xFF1A1A1A).withAlpha(50) + : Colors.white.withAlpha(50), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + data['content'] ?? '暂无内容', + style: TextStyle( + fontSize: 16, + color: isDark ? Colors.white : Colors.black87, + height: 1.8, + letterSpacing: 0.5, + ), + textAlign: TextAlign.center, + ), + ), + ], + if (origin != null && origin['content'] != null) ...[ + const SizedBox(height: 16), + _buildContentBlock( + '完整诗词', + (origin['content'] as List).map((e) => e.toString()).join('\n'), + isDark, + primaryColor, + ), + ], + if (origin != null && origin['translate'] != null) ...[ + const SizedBox(height: 12), + _buildContentBlock( + '翻译', + (origin['translate'] as List).map((e) => e.toString()).join('\n'), + isDark, + primaryColor, + ), + ], + if (data != null && data['matchTags'] != null) ...[ + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: (data['matchTags'] as List) + .map( + (tag) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: primaryColor.withAlpha(15), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withAlpha(30)), + ), + child: Text( + tag.toString(), + style: TextStyle( + fontSize: 12, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ) + .toList(), + ), + ], + const SizedBox(height: 16), + if (data != null) _buildPoetryMetadata(data, isDark, primaryColor), + ], + ), + ); + } + + Widget _buildPoetryMetadata( + Map data, + bool isDark, + Color primaryColor, + ) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark + ? const Color(0xFF1A1A1A).withAlpha(50) + : Colors.grey[100]!.withAlpha(128), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + setState(() { + _isMetadataExpanded = !_isMetadataExpanded; + }); + }, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + '流行度', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(width: 8), + Text( + data['popularity'] != null + ? _formatNumber(data['popularity']) + : '-', + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ], + ), + Row( + children: [ + Text( + '诗词信息', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(width: 4), + Icon( + _isMetadataExpanded + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: primaryColor, + size: 20, + ), + ], + ), + ], + ), + ), + if (_isMetadataExpanded) ...[ + const SizedBox(height: 8), + const Divider(height: 1), + const SizedBox(height: 8), + if (data['id'] != null) + _buildMetadataRow('ID', data['id'].toString(), isDark), + if (data['cacheAt'] != null) + _buildMetadataRow('缓存时间', data['cacheAt'].toString(), isDark), + if (data['recommendedReason'] != null && + data['recommendedReason'].toString().isNotEmpty) + _buildMetadataRow( + '推荐理由', + data['recommendedReason'].toString(), + isDark, + ), + if (_poetryData!['token'] != null) + _buildMetadataRow( + 'Token', + _poetryData!['token'].toString().substring( + 0, + (_poetryData!['token'].toString().length > 20 + ? 20 + : _poetryData!['token'].toString().length), + ) + + '...', + isDark, + ), + if (_poetryData!['ipAddress'] != null) + _buildMetadataRow( + 'IP 地址', + _poetryData!['ipAddress'].toString(), + isDark, + ), + ], + ], + ), + ); + } + + Widget _buildMetadataRow(String label, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ), + ], + ), + ); + } + + Widget _buildUserInfoCard(bool isDark, Color primaryColor) { + final data = _userInfo?['data'] as Map?; + if (data == null) return const SizedBox.shrink(); + + final weatherData = data['weatherData'] as Map?; + final tags = data['tags'] as List?; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [primaryColor.withAlpha(25), primaryColor.withAlpha(10)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withAlpha(40), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Text('🌤️', style: TextStyle(fontSize: 20)), + ), + ), + const SizedBox(width: 12), + Text( + '环境信息', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 16), + _buildUserInfoRow('IP 地址', data['ip']?.toString() ?? '未知', isDark), + _buildUserInfoRow('地区', data['region']?.toString() ?? '未知', isDark), + if (data['beijingTime'] != null) + _buildUserInfoRow('北京时间', data['beijingTime'].toString(), isDark), + if (weatherData != null) ...[ + const SizedBox(height: 12), + _buildWeatherInfo(weatherData, isDark, primaryColor), + ], + if (tags != null && tags.isNotEmpty) ...[ + const SizedBox(height: 12), + _buildTags(tags, isDark, primaryColor), + ], + ], + ), + ); + } + + Widget _buildUserInfoRow(String label, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[400] : Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ), + ], + ), + ); + } + + Widget _buildWeatherInfo( + Map weatherData, + bool isDark, + Color primaryColor, + ) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark + ? const Color(0xFF1A1A1A).withAlpha(50) + : Colors.white.withAlpha(50), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.cloud, color: primaryColor, size: 16), + const SizedBox(width: 6), + Text( + '天气信息', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 12, + runSpacing: 8, + children: [ + _buildWeatherItem( + '温度', + '${weatherData['temperature']?.toString() ?? '-'}°C', + isDark, + ), + _buildWeatherItem( + '天气', + weatherData['weather']?.toString() ?? '-', + isDark, + ), + _buildWeatherItem( + '湿度', + '${weatherData['humidity']?.toString() ?? '-'}%', + isDark, + ), + _buildWeatherItem( + '风向', + weatherData['windDirection']?.toString() ?? '-', + isDark, + ), + _buildWeatherItem( + '风力', + '${weatherData['windPower']?.toString() ?? '-'}级', + isDark, + ), + _buildWeatherItem( + '能见度', + weatherData['visibility']?.toString() ?? '-', + isDark, + ), + if (weatherData['rainfall'] != null) + _buildWeatherItem( + '降雨量', + '${weatherData['rainfall']}mm', + isDark, + ), + if (weatherData['pm25'] != null) + _buildWeatherItem('PM2.5', '${weatherData['pm25']}', isDark), + ], + ), + ], + ), + ); + } + + Widget _buildWeatherItem(String label, String value, bool isDark) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$label: ', + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ); + } + + Widget _buildTags(List tags, bool isDark, Color primaryColor) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.local_offer, color: primaryColor, size: 16), + const SizedBox(width: 6), + Text( + '推荐标签', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: tags.map((tag) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: primaryColor.withAlpha(15), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withAlpha(30)), + ), + child: Text( + tag.toString(), + style: TextStyle( + fontSize: 12, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ); + }).toList(), + ), + ], + ); + } + + Widget _buildSectionCard( + String title, + IconData icon, + List children, + bool isDark, + Color primaryColor, + ) { + return Container( + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withAlpha(76) + : Colors.black.withAlpha(26), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon(icon, color: primaryColor, size: 20), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + ), + ...children, + ], + ), + ); + } + + Widget _buildContentBlock( + String title, + String content, + bool isDark, + Color primaryColor, + ) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark + ? const Color(0xFF1A1A1A).withAlpha(50) + : Colors.grey[100]!.withAlpha(128), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(height: 8), + ...(content.split('\n')).map( + (line) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + line, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + height: 1.6, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ); + } + + String _formatNumber(dynamic number) { + if (number is int) { + if (number >= 10000) { + return '${(number / 10000).toStringAsFixed(1)}万'; + } + return number.toString(); + } + return number.toString(); + } +} diff --git a/lib/views/home/home_page.dart b/lib/views/home/home_page.dart index 31653aa..49350e2 100644 --- a/lib/views/home/home_page.dart +++ b/lib/views/home/home_page.dart @@ -124,15 +124,8 @@ class _HomePageState extends State { ), ), ), - // 关怀按钮 - 左上角 - Positioned( - top: 8, - left: 16, - child: CareButton( - onTap: _careController.toggleCareButtonVisibility, - isDark: isDark, - ), - ), + // 情景推荐按钮 - 左上角 + Positioned(top: 8, left: 16, child: PrePageButton(isDark: isDark)), // 关怀模式开关 Obx( () => _careController.isCareButtonVisible.value diff --git a/lib/views/profile/app-info.dart b/lib/views/profile/app-info.dart index 9c38c17..630090c 100644 --- a/lib/views/profile/app-info.dart +++ b/lib/views/profile/app-info.dart @@ -1,5 +1,4 @@ import 'dart:ui'; -import 'dart:io' as io show Platform; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -10,6 +9,7 @@ import '../../../config/app_config.dart'; import '../../../constants/app_constants.dart'; import '../../../models/colors/theme_colors.dart'; import '../../../controllers/shared_preferences_storage_controller.dart'; +import '../../../controllers/settings/is_platform.dart'; import '../../../services/get/theme_controller.dart'; /// 时间: 2026-03-26 @@ -398,20 +398,17 @@ class _AppInfoPageState extends State { ) { String buildSdk = 'Unknown'; try { - final String osName = io.Platform.operatingSystem; - if (osName == 'ohos' || - osName == 'harmonyos' || - osName == 'openharmony') { + if (PlatformUtils.isHarmonyOS) { buildSdk = 'Deveco API 23'; - } else if (io.Platform.isAndroid) { + } else if (PlatformUtils.isAndroid) { buildSdk = 'Android Target 36'; - } else if (io.Platform.isWindows) { + } else if (PlatformUtils.isWindows) { buildSdk = 'Win10 SDK'; - } else if (io.Platform.isIOS) { + } else if (PlatformUtils.isIOS) { buildSdk = 'iOS 26'; - } else if (io.Platform.isMacOS) { + } else if (PlatformUtils.isMacOS) { buildSdk = 'macOS 18'; - } else if (io.Platform.isLinux) { + } else if (PlatformUtils.isLinux) { buildSdk = 'Linux 20'; } else { buildSdk = 'PHP 7.4'; @@ -700,32 +697,30 @@ class _AppInfoPageState extends State { String platformName = 'Unknown'; try { - final String osName = io.Platform.operatingSystem; - final String osVersion = io.Platform.operatingSystemVersion.toLowerCase(); - - if (osName == 'ohos' || osName == 'harmonyos' || osName == 'harmonyos') { + if (PlatformUtils.isHarmonyOS) { platformName = 'HarmonyOS'; isHarmonyOS = true; - } else if (io.Platform.isAndroid) { + } else if (PlatformUtils.isAndroid) { platformName = 'Android'; + final osVersion = PlatformUtils.operatingSystemVersion.toLowerCase(); if (osVersion.contains('harmony') || osVersion.contains('ohos') || osVersion.contains('openharmony')) { platformName = 'HarmonyOS'; isHarmonyOS = true; } - } else if (io.Platform.isIOS) { + } else if (PlatformUtils.isIOS) { platformName = 'iOS'; - } else if (io.Platform.isMacOS) { + } else if (PlatformUtils.isMacOS) { platformName = 'macOS'; - } else if (io.Platform.isWindows) { + } else if (PlatformUtils.isWindows) { platformName = 'Windows'; - } else if (io.Platform.isLinux) { + } else if (PlatformUtils.isLinux) { platformName = 'Linux'; - } else if (io.Platform.isFuchsia) { + } else if (PlatformUtils.isFuchsia) { platformName = 'Fuchsia'; } else { - platformName = osName[0].toUpperCase() + osName.substring(1); + platformName = PlatformUtils.platformShortName; } } catch (e) { platformName = switch (platform.operatingSystem) { @@ -749,7 +744,7 @@ class _AppInfoPageState extends State { String deviceType = '未知设备'; if (isHarmonyOS) { - final String osName = io.Platform.operatingSystem; + final osName = PlatformUtils.operatingSystem.toLowerCase(); if (osName == 'ohos') { deviceType = 'OHOS'; } else if (osName == 'harmonyos') { diff --git a/lib/views/profile/components/jinrishici_sdk_page.dart b/lib/views/profile/components/jinrishici_sdk_page.dart new file mode 100644 index 0000000..8bd03dc --- /dev/null +++ b/lib/views/profile/components/jinrishici_sdk_page.dart @@ -0,0 +1,1171 @@ +/// 今日诗词 SDK 信息展示页面 +/// +/// 创建时间:2026-04-08 +/// 作用:展示今日诗词安卓 SDK 的使用方法和 API 接口信息 +/// 最后更新:2026-04-08 - 添加复制JSON数据按钮功能 + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import '../../../models/scenario/jinrishici_sdk_config.dart'; +import '../../../models/colors/app_colors.dart'; +import '../../../services/get/theme_controller.dart'; +import '../../../services/jinrishici_service.dart'; + +class JinrishiciSdkPage extends StatefulWidget { + const JinrishiciSdkPage({super.key}); + + @override + State createState() => _JinrishiciSdkPageState(); +} + +class _JinrishiciSdkPageState extends State + with TickerProviderStateMixin { + final ThemeController _themeController = Get.find(); + final JinrishiciService _jinrishiciService = JinrishiciService(); + late AnimationController _fadeController; + late Animation _fadeAnimation; + + Map? _poetryData; + bool _isLoading = false; + String? _errorMessage; + Map? _userInfo; + + @override + void initState() { + super.initState(); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + _fadeAnimation = CurvedAnimation( + parent: _fadeController, + curve: Curves.easeIn, + ); + _fadeController.forward(); + _loadPoetry(); + } + + /// 加载今日诗词 + Future _loadPoetry() async { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + final poetry = await _jinrishiciService.getTodayPoetry(); + setState(() { + _poetryData = poetry; + _isLoading = false; + }); + + // 同时加载用户信息 + try { + final userInfo = await _jinrishiciService.getUserInfo(); + setState(() { + _userInfo = userInfo; + }); + } catch (e) { + print('加载用户信息失败: $e'); + // 用户信息加载失败不影响诗词显示 + } + } catch (e) { + setState(() { + _errorMessage = e.toString(); + _isLoading = false; + }); + } + } + + /// 复制当前诗词数据为JSON格式 + Future _copyPoetryJson() async { + if (_poetryData == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('暂无数据可复制')), + ); + return; + } + + try { + final jsonString = jsonEncode(_poetryData); + await Clipboard.setData(ClipboardData(text: jsonString)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('JSON数据已复制到剪贴板')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('复制失败: $e')), + ); + } + } + + @override + void dispose() { + _fadeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Obx(() { + final isDark = _themeController.isDarkMode; + final primaryColor = _themeController.currentThemeColor; + + return Scaffold( + backgroundColor: isDark ? const Color(0xFF1A1A1A) : Colors.grey[50], + appBar: AppBar( + title: Text( + '今日诗词 SDK', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17, + color: primaryColor, + ), + ), + backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, + foregroundColor: primaryColor, + elevation: 0, + centerTitle: true, + ), + body: FadeTransition( + opacity: _fadeAnimation, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildPoetryCard(isDark, primaryColor), + if (_userInfo != null) ...[ + const SizedBox(height: 16), + _buildUserInfoCard(isDark, primaryColor), + ], + const SizedBox(height: 16), + _buildInfoCard(isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('SDK 基本信息', Icons.info_outline, [ + _buildInfoRow('SDK 名称', JinrishiciSdkConfig.sdkName, isDark), + _buildInfoRow('包名', JinrishiciSdkConfig.packageName, isDark), + _buildInfoRow('许可证', JinrishiciSdkConfig.license, isDark), + _buildClickableRow( + 'GitHub', + JinrishiciSdkConfig.githubUrl, + isDark, + primaryColor, + ), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('API 接口', Icons.api, [ + _buildClickableRow( + 'Token 获取', + JinrishiciSdkConfig.tokenUrl, + isDark, + primaryColor, + ), + _buildClickableRow( + '获取诗词', + JinrishiciSdkConfig.sentenceUrl, + isDark, + primaryColor, + ), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('安装方式', Icons.download, [ + _buildCodeBlock('Gradle', JinrishiciSdkWorkflow.gradleDependency, isDark), + const SizedBox(height: 12), + _buildCodeBlock('Maven', JinrishiciSdkWorkflow.mavenDependency, isDark), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('初始化', Icons.play_arrow, [ + _buildCodeBlock('初始化方法', JinrishiciSdkUsage.initMethod, isDark), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('获取诗词', Icons.book, [ + _buildCodeBlock('异步方法', JinrishiciSdkUsage.asyncMethod, isDark), + const SizedBox(height: 12), + _buildCodeBlock('同步方法', JinrishiciSdkUsage.syncMethod, isDark), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('自定义控件', Icons.widgets, [ + _buildCodeBlock('XML 布局', JinrishiciSdkUsage.customWidget, isDark), + ], isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('工作流程', Icons.timeline, JinrishiciSdkWorkflow.steps + .map((step) => _buildStepItem(step, isDark, primaryColor)) + .toList(), isDark, primaryColor), + const SizedBox(height: 16), + _buildSectionCard('数据模型', Icons.data_object, [ + _buildDataModelCard('PoetySentence', JinrishiciSdkData.poetySentenceFields, isDark), + const SizedBox(height: 12), + _buildDataModelCard('DataBean', JinrishiciSdkData.dataBeanFields, isDark), + const SizedBox(height: 12), + _buildDataModelCard('OriginBean', JinrishiciSdkData.originBeanFields, isDark), + const SizedBox(height: 12), + _buildDataModelCard('PoetyToken', JinrishiciSdkData.poetyTokenFields, isDark), + ], isDark, primaryColor), + const SizedBox(height: 100), + ], + ), + ), + ); + }); + } + + Widget _buildPoetryCard(bool isDark, Color primaryColor) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + primaryColor.withAlpha(30), + primaryColor.withAlpha(10), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: primaryColor.withAlpha(50), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + '📜', + style: const TextStyle(fontSize: 20), + ), + ), + ), + const SizedBox(width: 12), + Text( + '今日诗词', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + Row( + children: [ + IconButton( + onPressed: _poetryData == null ? null : _copyPoetryJson, + icon: Icon( + Icons.code, + color: _poetryData == null + ? (isDark ? Colors.grey[600] : Colors.grey[400]) + : primaryColor, + ), + tooltip: '复制JSON', + ), + IconButton( + onPressed: _isLoading ? null : _loadPoetry, + icon: _isLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(primaryColor), + ), + ) + : Icon( + Icons.refresh, + color: primaryColor, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 16), + if (_isLoading) + Center( + child: Column( + children: [ + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(primaryColor), + ), + const SizedBox(height: 12), + Text( + '正在加载诗词...', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], + ), + ) + else if (_errorMessage != null) + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red.withAlpha(10), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.red.withAlpha(30), + ), + ), + child: Row( + children: [ + Icon( + Icons.error_outline, + color: Colors.red, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _errorMessage!, + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.red[300] : Colors.red[700], + ), + ), + ), + ], + ), + ) + else if (_poetryData != null) + _buildPoetryContent(isDark, primaryColor) + else + Center( + child: Text( + '点击刷新按钮获取今日诗词', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ], + ), + ); + } + + Widget _buildPoetryContent(bool isDark, Color primaryColor) { + print('完整诗词数据: $_poetryData'); + final data = _poetryData!['data'] as Map?; + print('Data 字段: $data'); + final origin = data?['origin'] as Map?; + print('Origin 字段: $origin'); + print('标签数据: ${data?['matchTags']}'); + + if (data == null) { + return Text( + '数据格式错误', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (origin != null) ...[ + Text( + origin['title'] ?? '未知标题', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + origin['dynasty'] ?? '未知朝代', + style: TextStyle( + fontSize: 14, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + Text( + '·', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + const SizedBox(width: 8), + Text( + origin['author'] ?? '未知作者', + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ], + ), + const SizedBox(height: 16), + ], + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A).withAlpha(50) : Colors.white.withAlpha(50), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + data['content'] ?? '暂无内容', + style: TextStyle( + fontSize: 16, + color: isDark ? Colors.white : Colors.black87, + height: 1.8, + letterSpacing: 0.5, + ), + textAlign: TextAlign.center, + ), + ), + if (origin != null && origin['content'] != null) ...[ + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A).withValues(alpha: 0.3) : Colors.grey[100]!.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '完整诗词', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(height: 8), + ...(origin['content'] as List).map((line) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + line.toString(), + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + height: 1.6, + ), + textAlign: TextAlign.center, + ), + )), + ], + ), + ), + ], + if (origin != null && origin['translate'] != null) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A).withValues(alpha: 0.3) : Colors.grey[100]!.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '翻译', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(height: 8), + ...(origin['translate'] as List).map((line) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + line.toString(), + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[400] : Colors.grey[600], + height: 1.6, + ), + ), + )), + ], + ), + ), + ], + if (data['matchTags'] != null) ...[ + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: (data['matchTags'] as List) + .map((tag) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: primaryColor.withAlpha(15), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: primaryColor.withAlpha(30), + ), + ), + child: Text( + tag.toString(), + style: TextStyle( + fontSize: 12, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + )) + .toList(), + ), + ], + const SizedBox(height: 16), + _buildPoetryMetadata(data, isDark, primaryColor), + ], + ); + } + + Widget _buildPoetryMetadata(Map data, bool isDark, Color primaryColor) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A).withValues(alpha: 0.3) : Colors.grey[100]!.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '诗词信息', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryColor, + ), + ), + const SizedBox(height: 8), + if (data['id'] != null) + _buildMetadataRow('ID', data['id'].toString(), isDark), + if (data['popularity'] != null) + _buildMetadataRow('流行度', _formatNumber(data['popularity']), isDark), + if (data['cacheAt'] != null) + _buildMetadataRow('缓存时间', data['cacheAt'].toString(), isDark), + if (data['recommendedReason'] != null && data['recommendedReason'].toString().isNotEmpty) + _buildMetadataRow('推荐理由', data['recommendedReason'].toString(), isDark), + if (_poetryData!['token'] != null) + _buildMetadataRow('Token', _poetryData!['token'].toString().substring(0, 20) + '...', isDark), + if (_poetryData!['ipAddress'] != null) + _buildMetadataRow('IP 地址', _poetryData!['ipAddress'].toString(), isDark), + ], + ), + ); + } + + Widget _buildMetadataRow(String label, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ), + ], + ), + ); + } + + String _formatNumber(dynamic number) { + if (number is int) { + if (number >= 10000) { + return '${(number / 10000).toStringAsFixed(1)}万'; + } + return number.toString(); + } + return number.toString(); + } + + Widget _buildUserInfoCard(bool isDark, Color primaryColor) { + final data = _userInfo?['data'] as Map?; + if (data == null) return const SizedBox.shrink(); + + final weatherData = data['weatherData'] as Map?; + final tags = data['tags'] as List?; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + primaryColor.withAlpha(25), + primaryColor.withAlpha(10), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: primaryColor.withAlpha(40), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + '🌤️', + style: const TextStyle(fontSize: 20), + ), + ), + ), + const SizedBox(width: 12), + Text( + '环境信息', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 16), + _buildUserInfoRow('IP 地址', data['ip']?.toString() ?? '未知', isDark), + _buildUserInfoRow('地区', data['region']?.toString() ?? '未知', isDark), + if (data['beijingTime'] != null) + _buildUserInfoRow('北京时间', data['beijingTime'].toString(), isDark), + if (weatherData != null) ...[ + const SizedBox(height: 12), + _buildWeatherInfo(weatherData, isDark, primaryColor), + ], + if (tags != null && tags.isNotEmpty) ...[ + const SizedBox(height: 12), + _buildTags(tags, isDark, primaryColor), + ], + ], + ), + ); + } + + Widget _buildUserInfoRow(String label, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[400] : Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ), + ], + ), + ); + } + + Widget _buildWeatherInfo(Map weatherData, bool isDark, Color primaryColor) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A).withAlpha(50) : Colors.white.withAlpha(50), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.cloud, color: primaryColor, size: 16), + const SizedBox(width: 6), + Text( + '天气信息', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 12, + runSpacing: 8, + children: [ + _buildWeatherItem('温度', '${weatherData['temperature']?.toString() ?? '-'}°C', isDark), + _buildWeatherItem('天气', weatherData['weather']?.toString() ?? '-', isDark), + _buildWeatherItem('湿度', '${weatherData['humidity']?.toString() ?? '-'}%', isDark), + _buildWeatherItem('风向', weatherData['windDirection']?.toString() ?? '-', isDark), + _buildWeatherItem('风力', '${weatherData['windPower']?.toString() ?? '-'}级', isDark), + _buildWeatherItem('能见度', weatherData['visibility']?.toString() ?? '-', isDark), + if (weatherData['rainfall'] != null) + _buildWeatherItem('降雨量', '${weatherData['rainfall']}mm', isDark), + if (weatherData['pm25'] != null) + _buildWeatherItem('PM2.5', '${weatherData['pm25']}', isDark), + ], + ), + ], + ), + ); + } + + Widget _buildWeatherItem(String label, String value, bool isDark) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$label: ', + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ); + } + + Widget _buildTags(List tags, bool isDark, Color primaryColor) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.local_offer, color: primaryColor, size: 16), + const SizedBox(width: 6), + Text( + '推荐标签', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: tags.map((tag) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: primaryColor.withAlpha(15), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withAlpha(30)), + ), + child: Text( + tag.toString(), + style: TextStyle( + fontSize: 12, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ); + }).toList(), + ), + ], + ); + } + + Widget _buildInfoCard(bool isDark, Color primaryColor) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + primaryColor.withAlpha(30), + primaryColor.withAlpha(10), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: primaryColor.withAlpha(50), + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Text( + '📜', + style: const TextStyle(fontSize: 28), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + JinrishiciSdkConfig.sdkName, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 4), + Text( + '每日一首诗词,感受传统文化之美', + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildSectionCard( + String title, + IconData icon, + List children, + bool isDark, + Color primaryColor, + ) { + return Container( + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withAlpha(76) + : Colors.black.withAlpha(26), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon(icon, color: primaryColor, size: 20), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ], + ), + ), + ...children, + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.white : Colors.black87, + ), + ), + ), + ], + ), + ); + } + + Widget _buildClickableRow( + String label, + String url, + bool isDark, + Color primaryColor, + ) { + return InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('已复制: $label')), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[400] : Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + url, + style: TextStyle( + fontSize: 14, + color: primaryColor, + decoration: TextDecoration.underline, + ), + ), + ), + Icon( + Icons.content_copy, + size: 16, + color: isDark ? Colors.grey[500] : Colors.grey[400], + ), + ], + ), + ), + ); + } + + Widget _buildCodeBlock(String title, String code, bool isDark) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + title, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: isDark ? Colors.grey[300] : Colors.grey[700], + ), + ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A) : Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: code)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('代码已复制')), + ); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + code.trim(), + style: TextStyle( + fontSize: 12, + fontFamily: 'monospace', + color: isDark ? Colors.grey[300] : Colors.grey[700], + height: 1.5, + ), + ), + ), + const SizedBox(width: 8), + Icon( + Icons.content_copy, + size: 16, + color: isDark ? Colors.grey[500] : Colors.grey[400], + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildStepItem(String step, bool isDark, Color primaryColor) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 8, + height: 8, + margin: const EdgeInsets.only(top: 6, right: 12), + decoration: BoxDecoration( + color: primaryColor, + shape: BoxShape.circle, + ), + ), + Expanded( + child: Text( + step, + style: TextStyle( + fontSize: 14, + color: isDark ? Colors.grey[300] : Colors.grey[700], + height: 1.5, + ), + ), + ), + ], + ), + ); + } + + Widget _buildDataModelCard(String className, Map fields, bool isDark) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A1A) : Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + className, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + ...fields.entries.map((entry) { + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${entry.key}: ', + style: TextStyle( + fontSize: 12, + fontFamily: 'monospace', + color: isDark ? Colors.blue[300] : Colors.blue[700], + fontWeight: FontWeight.w500, + ), + ), + Expanded( + child: Text( + entry.value, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ], + ), + ); + }).toList(), + ], + ), + ); + } +} diff --git a/lib/views/profile/components/pop-menu.dart b/lib/views/profile/components/pop-menu.dart index b81738c..a62b574 100644 --- a/lib/views/profile/components/pop-menu.dart +++ b/lib/views/profile/components/pop-menu.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:share_plus/share_plus.dart'; import 'package:get/get.dart'; import '../../../models/colors/app_colors.dart'; import '../../../services/isweb/wakelock_service.dart'; import '../../../services/get/theme_controller.dart'; +import '../../../controllers/settings/is_platform.dart'; import '../guide/beginner_page.dart'; -import 'dart:io' as io; +import 'jinrishici_sdk_page.dart'; +import '../../home/care/care-page.dart'; class PopMenu extends StatelessWidget { final VoidCallback? onRefresh; @@ -51,7 +52,7 @@ class PopMenu extends StatelessWidget { static Future toggleScreenWake(BuildContext context) async { // Web 平台不支持 wakelock_plus - if (kIsWeb) { + if (PlatformUtils.isWeb) { if (context.mounted) { ScaffoldMessenger.of( context, @@ -61,9 +62,9 @@ class PopMenu extends StatelessWidget { } try { - // 使用 io.Platform 检测平台 - final String osName = io.Platform.operatingSystem; - final String osVersion = io.Platform.operatingSystemVersion; + // 获取平台信息 + final String osName = PlatformUtils.operatingSystem; + final String osVersion = PlatformUtils.operatingSystemVersion; print('Current platform: $osName, version: $osVersion'); // 直接尝试启用屏幕常亮 @@ -93,7 +94,10 @@ class PopMenu extends StatelessWidget { builder: (context) => AlertDialog( backgroundColor: AppColors.surface, title: Text('提示', style: TextStyle(color: AppColors.primaryText)), - content: Text(errorMessage, style: TextStyle(color: AppColors.secondaryText)), + content: Text( + errorMessage, + style: TextStyle(color: AppColors.secondaryText), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context), @@ -186,10 +190,21 @@ class PopMenu extends StatelessWidget { }), _buildBottomSheetItem( context, - '取消', - Icons.settings, - onSettings, + '今日诗词SDK', + Icons.book_outlined, + () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const JinrishiciSdkPage(), + ), + ); + }, ), + _buildBottomSheetItem(context, '关怀模式', Icons.people, () { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const CarePage()), + ); + }), const SizedBox(height: 20), _buildBottomSheetItem(context, '返回桌面', Icons.exit_to_app, () { SystemNavigator.pop(); @@ -210,14 +225,8 @@ class PopMenu extends StatelessWidget { VoidCallback? onTap, ) { return ListTile( - leading: Icon( - icon, - color: AppColors.primary, - ), - title: Text( - title, - style: TextStyle(color: AppColors.primaryText), - ), + leading: Icon(icon, color: AppColors.primary), + title: Text(title, style: TextStyle(color: AppColors.primaryText)), onTap: () { Navigator.pop(context); HapticFeedback.lightImpact(); diff --git a/lib/views/profile/expand/manu-script.dart b/lib/views/profile/expand/manu-script.dart index 6ed21eb..aa8b60f 100644 --- a/lib/views/profile/expand/manu-script.dart +++ b/lib/views/profile/expand/manu-script.dart @@ -1,4 +1,3 @@ -import 'dart:io' as io; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -6,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../../constants/app_constants.dart'; import '../../../utils/http/http_client.dart'; import '../../../services/get/theme_controller.dart'; +import '../../../controllers/settings/is_platform.dart'; import 'tougao.dart'; /// 时间: 2026-03-30 @@ -55,28 +55,7 @@ class _ManuscriptPageState extends State { } String _getPlatform() { - try { - final String osName = io.Platform.operatingSystem; - if (osName == 'ohos' || - osName == 'harmonyos' || - osName == 'openharmony') { - return 'HarmonyOS Flutter'; - } else if (io.Platform.isAndroid) { - return 'Android Flutter'; - } else if (io.Platform.isIOS) { - return 'iOS Flutter'; - } else if (io.Platform.isWindows) { - return 'Windows Flutter'; - } else if (io.Platform.isMacOS) { - return 'macOS Flutter'; - } else if (io.Platform.isLinux) { - return 'Linux Flutter'; - } else { - return 'Flutter'; - } - } catch (e) { - return 'Flutter'; - } + return PlatformUtils.platformDisplayName; } Future _loadCategories() async { diff --git a/lib/views/profile/guide/app-data.dart b/lib/views/profile/guide/app-data.dart index 439280c..f0d3423 100644 --- a/lib/views/profile/guide/app-data.dart +++ b/lib/views/profile/guide/app-data.dart @@ -6,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:path_provider/path_provider.dart'; import '../../../constants/app_constants.dart'; import '../../../services/get/theme_controller.dart'; +import '../../../controllers/settings/is_platform.dart'; /// 时间: 2026-03-27 /// 功能: 应用数据管理页面 @@ -95,7 +96,7 @@ class _AppDataPageState extends State { if (await parentDir.exists()) { await for (FileSystemEntity entity in parentDir.list()) { if (entity is Directory) { - final dirName = entity.path.split(Platform.pathSeparator).last; + final dirName = entity.path.split(PlatformUtils.pathSeparator).last; if (!dirName.startsWith('.')) { totalSize += await _getDirectorySize(entity); } diff --git a/lib/views/profile/profile_page.dart b/lib/views/profile/profile_page.dart index 8a2064b..953240e 100644 --- a/lib/views/profile/profile_page.dart +++ b/lib/views/profile/profile_page.dart @@ -7,12 +7,12 @@ import 'dart:io' as io; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; import '../../config/app_config.dart'; import '../../models/colors/theme_colors.dart'; +import '../../controllers/settings/is_platform.dart'; import 'history_page.dart'; import 'per_card.dart'; import 'settings/app_fun.dart'; @@ -697,7 +697,7 @@ class ProfilePage extends StatelessWidget { bool isDark, Color primaryColor, ) { - if (kIsWeb) { + if (PlatformUtils.isWeb) { return const SizedBox.shrink(); } diff --git a/pubspec.yaml b/pubspec.yaml index c632514..56fc0e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: poes description: "腹有诗书气自华" publish_to: 'none' -version: 1.4.1+26040202 +version: 1.4.3+26040203 environment: sdk: ^3.9.2 @@ -37,6 +37,7 @@ dependencies: get: git: url: https://gitcode.com/openharmony-sig/fluttertpc_get + pinyin: ^3.3.0 package_info_plus: ^9.0.1 diff --git a/wifi_device_helper.bat b/wifi_device_helper.bat new file mode 100644 index 0000000..5645542 --- /dev/null +++ b/wifi_device_helper.bat @@ -0,0 +1,70 @@ +@echo off +REM Flutter WiFi Device Connection Helper +REM Helps connect and debug Flutter apps on WiFi-connected Android devices + +echo ======================================== +echo Flutter WiFi Device Connection Helper +echo ======================================== +echo. + +REM Check if device is connected +echo Checking connected devices... +flutter devices + +echo. +echo ======================================== +echo Available Actions: +echo ======================================== +echo 1. Run on WiFi Device (SM T970 - 192.168.50.213:44263) +echo 2. Run on USB Device (PKB110 - S48DTKPRVK4H8PKB) +echo 3. Build APK only +echo 4. Install APK to WiFi Device +echo 5. Check device connection status +echo 6. Exit +echo ======================================== + +set /p choice="Enter your choice (1-6): " + +if "%choice%"=="1" ( + echo. + echo Running on WiFi Device with extended timeout... + flutter run --device-id=192.168.50.213:44263 --device-timeout=120 --device-connection=wireless +) else if "%choice%"=="2" ( + echo. + echo Running on USB Device... + flutter run --device-id=S48DTKPRVK4H8PKB --device-timeout=30 --device-connection=attached +) else if "%choice%"=="3" ( + echo. + echo Building APK... + flutter build apk --debug +) else if "%choice%"=="4" ( + echo. + echo Installing APK to WiFi Device... + if exist "build\app\outputs\flutter-apk\app-debug.apk" ( + "E:\sdk\android\platform-tools\adb.exe" -s 192.168.50.213:44263 install -r "build\app\outputs\flutter-apk\app-debug.apk" + echo. + echo APK installed successfully! + echo Launching app... + "E:\sdk\android\platform-tools\adb.exe" -s 192.168.50.213:44263 shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER app.wushu.poes/app.wushu.poes.MainActivity + ) else ( + echo APK not found. Please build it first (option 3). + ) +) else if "%choice%"=="5" ( + echo. + echo Checking device connection status... + echo. + echo ADB Devices: + "E:\sdk\android\platform-tools\adb.exe" devices + echo. + echo Flutter Devices: + flutter devices +) else if "%choice%"=="6" ( + echo. + echo Exiting... + exit /b +) else ( + echo Invalid choice. Please try again. +) + +echo. +pause \ No newline at end of file diff --git a/wifi_device_helper.ps1 b/wifi_device_helper.ps1 new file mode 100644 index 0000000..000552d --- /dev/null +++ b/wifi_device_helper.ps1 @@ -0,0 +1,108 @@ +# Flutter WiFi Device Connection Helper (PowerShell Version) +# Helps connect and debug Flutter apps on WiFi-connected Android devices + +function Show-Menu { + Clear-Host + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Flutter WiFi Device Connection Helper" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "" + Write-Host "Available Actions:" -ForegroundColor Yellow + Write-Host "1. Run on WiFi Device (SM T970 - 192.168.50.213:44263)" + Write-Host "2. Run on USB Device (PKB110 - S48DTKPRVK4H8PKB)" + Write-Host "3. Build APK only" + Write-Host "4. Install APK to WiFi Device" + Write-Host "5. Check device connection status" + Write-Host "6. Run with verbose logging (WiFi Device)" + Write-Host "7. Exit" + Write-Host "========================================" -ForegroundColor Cyan +} + +function Check-Devices { + Write-Host "" + Write-Host "Checking connected devices..." -ForegroundColor Green + Write-Host "" + Write-Host "ADB Devices:" -ForegroundColor Yellow + & "E:\sdk\android\platform-tools\adb.exe" devices + Write-Host "" + Write-Host "Flutter Devices:" -ForegroundColor Yellow + flutter devices + Write-Host "" +} + +function Run-OnWiFiDevice { + Write-Host "" + Write-Host "Running on WiFi Device with extended timeout..." -ForegroundColor Green + flutter run --device-id=192.168.50.213:44263 --device-timeout=120 --device-connection=wireless +} + +function Run-OnUSBDevice { + Write-Host "" + Write-Host "Running on USB Device..." -ForegroundColor Green + flutter run --device-id=S48DTKPRVK4H8PKB --device-timeout=30 --device-connection=attached +} + +function Build-APK { + Write-Host "" + Write-Host "Building APK..." -ForegroundColor Green + flutter build apk --debug +} + +function Install-APK-WiFi { + Write-Host "" + $apkPath = "build\app\outputs\flutter-apk\app-debug.apk" + + if (Test-Path $apkPath) { + Write-Host "Installing APK to WiFi Device..." -ForegroundColor Green + & "E:\sdk\android\platform-tools\adb.exe" -s 192.168.50.213:44263 install -r $apkPath + + if ($LASTEXITCODE -eq 0) { + Write-Host "" + Write-Host "APK installed successfully!" -ForegroundColor Green + Write-Host "Launching app..." -ForegroundColor Green + & "E:\sdk\android\platform-tools\adb.exe" -s 192.168.50.213:44263 shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER app.wushu.poes/app.wushu.poes.MainActivity + } else { + Write-Host "APK installation failed!" -ForegroundColor Red + } + } else { + Write-Host "APK not found at $apkPath" -ForegroundColor Red + Write-Host "Please build it first (option 3)." -ForegroundColor Yellow + } +} + +function Run-Verbose-WiFi { + Write-Host "" + Write-Host "Running on WiFi Device with verbose logging..." -ForegroundColor Green + flutter run --device-id=192.168.50.213:44263 --device-timeout=120 --device-connection=wireless --verbose +} + +# Main loop +do { + Show-Menu + Check-Devices + + $choice = Read-Host "Enter your choice (1-7)" + + switch ($choice) { + "1" { Run-OnWiFiDevice } + "2" { Run-OnUSBDevice } + "3" { Build-APK } + "4" { Install-APK-WiFi } + "5" { Check-Devices } + "6" { Run-Verbose-WiFi } + "7" { + Write-Host "" + Write-Host "Exiting..." -ForegroundColor Yellow + exit + } + default { + Write-Host "" + Write-Host "Invalid choice. Please try again." -ForegroundColor Red + } + } + + if ($choice -ne "7") { + Write-Host "" + Read-Host "Press Enter to continue..." + } +} while ($choice -ne "7") \ No newline at end of file