chore: 汇总2026-05-30全量更新
### 详细变更:
1. **文档与配置**:更新AGENTS.md添加命令超时约束,升级Rive依赖至0.14.7并替换平台插件引用
2. **UI优化**:重构AppInfo页面布局、移除图表冗余配置、锁定部分系统设置项
3. **功能增强**:
- 新增工具面板拖拽状态管理与介绍弹窗
- 新增进度页面编辑/重排/清空用户进度功能
- 新增摇一摇路由作用域拦截逻辑
4. **体验优化**:
- 统一外部链接跳转弹窗,添加文件打开确认逻辑
- 修复设备卡片IP溢出、Android权限声明问题
- 后台任务初始化增加协议校验
5. **代码重构**:拆分工具面板配置、拖拽逻辑与动画参数,优化状态管理代码
6. **工具脚本**:新增协议文件上传脚本
This commit is contained in:
@@ -0,0 +1,837 @@
|
||||
# 闲言APP 批量问题修复与功能完善 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 修复25项问题,涵盖Android合规、UI/UX修复、功能完善和页面重设计
|
||||
|
||||
**Architecture:** Flutter feature-first架构,Riverpod状态管理,GoRouter路由,iOS风格Cupertino组件优先
|
||||
|
||||
**Tech Stack:** Flutter 3.x / Dart / Riverpod / GoRouter / sensors_plus / workmanager / permission_handler
|
||||
|
||||
---
|
||||
|
||||
## 任务分组
|
||||
|
||||
本计划按优先级和依赖关系分为6个批次,每批次内的任务可并行执行。
|
||||
|
||||
---
|
||||
|
||||
## 批次1: 合规与关键Bug修复 (高优先级)
|
||||
|
||||
### Task 1: AndroidManifest自启动修复
|
||||
|
||||
**问题:** APP未向用户明示未经用户同意,存在频繁自启动行为。举证: `android.intent.action.BOOT_COMPLETED` 由 `androidx.work.impl.background.systemalarm.RescheduleReceiver` 触发
|
||||
|
||||
**分析:** 项目AndroidManifest.xml中未直接声明BOOT_COMPLETED权限和Receiver,但`workmanager`库的`RescheduleReceiver`会在合并后的Manifest中自动注册。项目`BackgroundTaskService`已初始化但未实际注册周期性任务。
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/AndroidManifest.xml`
|
||||
- Modify: `lib/core/services/background/background_task_service.dart`
|
||||
- Modify: `lib/core/services/background/background_callback.dart`
|
||||
|
||||
- [ ] **Step 1: 在AndroidManifest.xml中移除workmanager的自动注册**
|
||||
|
||||
在`<application>`标签内添加`tools:node="remove"`来移除workmanager的RescheduleReceiver:
|
||||
|
||||
```xml
|
||||
<!-- 移除workmanager自启动Receiver -->
|
||||
<receiver
|
||||
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
|
||||
tools:node="remove" />
|
||||
```
|
||||
|
||||
同时在`<manifest>`标签添加 `xmlns:tools="http://schemas.android.com/tools"`
|
||||
|
||||
- [ ] **Step 2: 修改BackgroundTaskService,仅在用户同意后初始化**
|
||||
|
||||
在`background_task_service.dart`中添加条件检查,只有用户同意协议后才初始化workmanager:
|
||||
|
||||
```dart
|
||||
Future<void> init() async {
|
||||
final agreed = KvStorage.get<bool>('onboarding_completed') ?? false;
|
||||
if (!agreed) return;
|
||||
// ... 原有初始化逻辑
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在隐私政策中添加后台任务说明**
|
||||
|
||||
在协议数据文件中添加workmanager/后台同步的使用场景说明。
|
||||
|
||||
- [ ] **Step 4: 验证编译通过**
|
||||
|
||||
Run: `flutter build apk --debug`
|
||||
Expected: 编译成功,合并后的AndroidManifest不再包含RescheduleReceiver
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 主页句子广场下拉列表动画恢复
|
||||
|
||||
**问题:** 原本下拉列表有动画,现在没有了
|
||||
|
||||
**分析:** `home_page.dart`使用`AnimatedBuilder`+`SheetAnimationNotifier`实现底部弹窗打开时的缩放+圆角效果。句子广场列表项使用`ListItemAnimation.slideUp`入场动画。需要检查动画是否被禁用或条件判断有误。
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/home_page.dart`
|
||||
- Check: `lib/core/utils/ui/interaction_animations.dart`
|
||||
|
||||
- [ ] **Step 1: 检查home_page.dart中句子列表的动画代码**
|
||||
|
||||
搜索句子广场列表构建代码,确认是否使用了`ListItemAnimation`或`flutter_animate`的入场动画。检查`reduceAnimations`设置是否影响了动画。
|
||||
|
||||
- [ ] **Step 2: 恢复下拉刷新后的列表入场动画**
|
||||
|
||||
在句子列表的`ListView.builder`中,为每个item添加`ListItemAnimation`或`flutter_animate`的fadeIn+slideUp效果:
|
||||
|
||||
```dart
|
||||
itemBuilder: (context, index) {
|
||||
return ListItemAnimation.wrap(
|
||||
index: index,
|
||||
child: SentenceCard(...),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 确保动画不受reduceAnimations影响(下拉刷新时强制启用)**
|
||||
|
||||
下拉刷新时即使`reduceAnimations=true`,也应展示入场动画。
|
||||
|
||||
- [ ] **Step 4: 验证动画效果**
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 摇一摇权限写入权限管理页面
|
||||
|
||||
**问题:** 摇一摇权限需在权限管理页面显示,且需手动打开/关闭
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/core/services/auth/permission_service.dart`
|
||||
- Modify: `lib/features/mine/settings/presentation/privacy/permission_management_page.dart`
|
||||
|
||||
- [ ] **Step 1: 在AppPermission枚举中添加shake权限**
|
||||
|
||||
```dart
|
||||
shake(
|
||||
label: '摇一摇',
|
||||
permission: null, // 虚拟权限,不需要系统授权
|
||||
icon: CupertinoIcons.arrow_counterclockwise,
|
||||
description: '摇晃手机触发特定功能,如换句、刷新等',
|
||||
color: AppColors.iosPurple,
|
||||
isRequired: false,
|
||||
isVirtual: true,
|
||||
group: PermissionGroup.optional,
|
||||
usageScenes: ['切换每日推荐句子', '刷新内容', '互动彩蛋'],
|
||||
),
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在PermissionService中添加shake权限状态管理**
|
||||
|
||||
添加`isShakeEnabled`/`setShakeEnabled`方法,状态存储在KvStorage中:
|
||||
|
||||
```dart
|
||||
bool get isShakeEnabled => KvStorage.get<bool>('shake_enabled') ?? true;
|
||||
Future<void> setShakeEnabled(bool enabled) async {
|
||||
await KvStorage.set('shake_enabled', enabled);
|
||||
if (!enabled) {
|
||||
ShakeDetector.instance.stop();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在权限管理页面添加摇一摇权限卡片**
|
||||
|
||||
在虚拟权限列表中添加摇一摇,显示当前启用状态,点击可手动切换。
|
||||
|
||||
- [ ] **Step 4: 修改ShakeDetector.start()检查权限**
|
||||
|
||||
```dart
|
||||
void start() {
|
||||
if (!PermissionService.instance.isShakeEnabled) return;
|
||||
// ... 原有逻辑
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 非主页摇一摇触发修复
|
||||
|
||||
**问题:** 不在主页句子卡片的3级4级页面,摇一摇也能被触发
|
||||
|
||||
**分析:** ShakeDetector使用处理器栈模式,但`StatefulShellRoute.indexedStack`不调用子页面dispose,导致handler可能残留。需确保只有主页的句子卡片页面注册了摇一摇handler。
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/core/services/device/shake_detector.dart`
|
||||
- Modify: `lib/features/home/presentation/home_page.dart`
|
||||
- Check: `lib/features/check/presentation/check_page.dart`
|
||||
- Check: `lib/features/daily_fortune/presentation/daily_fortune_page.dart`
|
||||
|
||||
- [ ] **Step 1: 检查所有使用摇一摇的页面**
|
||||
|
||||
搜索所有调用`pushHandler`的页面,确认是否在3/4级页面也注册了handler。
|
||||
|
||||
- [ ] **Step 2: 限制摇一摇仅在主页Tab生效**
|
||||
|
||||
修改方案:在ShakeDetector中添加路由层级检查,只有`/home`路由的handler才在主页Tab生效。或者更简单:在`pushHandler`时记录路由深度,只有深度<=1的handler才生效。
|
||||
|
||||
```dart
|
||||
void pushHandler(String route, ShakeCallback callback) {
|
||||
_handlerStack.removeWhere((e) => e.route == route);
|
||||
_handlerStack.add(_ShakeHandlerEntry(route, callback));
|
||||
_updateSubscription();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在非主页子页面导航时暂停摇一摇**
|
||||
|
||||
当用户从主页导航到3/4级页面时,ShakeDetector应自动暂停(通过GoRouter路由监听):
|
||||
|
||||
```dart
|
||||
// 在AppShell或路由观察者中
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) {
|
||||
final currentPath = GoRouterState.of(context).fullPath;
|
||||
if (currentPath != '/home') {
|
||||
ShakeDetector.instance.stop();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 验证3/4级页面不再触发摇一摇**
|
||||
|
||||
---
|
||||
|
||||
### Task 22: 引导页未同意协议就读取权限修复
|
||||
|
||||
**问题:** 未同意协议就读取传感器列表、网络等权限
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/main.dart`
|
||||
- Modify: `lib/features/onboarding/presentation/pages/agreement_page.dart`
|
||||
|
||||
- [ ] **Step 1: 在main.dart中延迟初始化需要权限的服务**
|
||||
|
||||
将以下服务的初始化移到用户同意协议之后:
|
||||
- `ConnectivityService.init()` — 网络检测
|
||||
- `ClipboardMonitorService` — 剪贴板监控
|
||||
- `BackgroundTaskService.instance.init()` — 后台任务
|
||||
- `LocalNotificationService.init()` — 本地通知
|
||||
- `ShakeDetector` — 传感器
|
||||
|
||||
创建一个`postAgreementInit()`方法,在用户同意协议后调用。
|
||||
|
||||
- [ ] **Step 2: 修改onboarding_provider.dart**
|
||||
|
||||
在`completeOnboarding()`中调用`postAgreementInit()`:
|
||||
|
||||
```dart
|
||||
Future<void> completeOnboarding() async {
|
||||
// ... 原有逻辑
|
||||
await PostAgreementInitializer.init();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 确保引导页期间不触发任何权限请求**
|
||||
|
||||
检查agreement_page.dart中的权限说明Tab,确保只是展示文本,不调用任何权限API。
|
||||
|
||||
- [ ] **Step 4: 验证引导页期间无权限访问**
|
||||
|
||||
---
|
||||
|
||||
## 批次2: 数据与显示修复 (高优先级)
|
||||
|
||||
### Task 7: 稍后读句子不显示修复
|
||||
|
||||
**问题:** 主页句子广场点击稍后读的句子,在发现页稍后读页面没有显示
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/providers/readlater_page.dart`
|
||||
- Check: `lib/features/home/presentation/home_sentence_card.dart`
|
||||
- Check: `lib/features/discover/presentation/pages/home/discover_page.dart`
|
||||
|
||||
- [ ] **Step 1: 检查稍后读添加逻辑**
|
||||
|
||||
搜索"稍后读"或"readlater"的添加方法,确认点击稍后读按钮后的数据流向。
|
||||
|
||||
- [ ] **Step 2: 检查稍后读页面数据加载逻辑**
|
||||
|
||||
在`readlater_page.dart`中,`_loadData()`方法并行加载Feed API + 聊天消息。检查Feed类型稍后读的加载是否正确。
|
||||
|
||||
- [ ] **Step 3: 修复数据同步问题**
|
||||
|
||||
确保添加稍后读时写入的数据key与稍后读页面读取的key一致。可能需要检查`ReadlaterService`的存储和读取逻辑。
|
||||
|
||||
- [ ] **Step 4: 验证稍后读功能正常**
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 稍后读图片视频空白+发送弹窗
|
||||
|
||||
**问题:** 稍后读页面从其他页面发送的图片/视频看不了,就一个空白图片。发送后要求弹出对话框。
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/providers/readlater_page.dart`
|
||||
|
||||
- [ ] **Step 1: 检查图片/视频条目渲染逻辑**
|
||||
|
||||
在`readlater_page.dart`的`_buildImageEntry()`和`_buildVideoEntry()`中,检查缩略图加载是否正确处理本地文件路径。
|
||||
|
||||
- [ ] **Step 2: 修复图片/视频显示**
|
||||
|
||||
确保图片路径正确解析(网络URL vs 本地文件路径),使用`Image.file()`或`Image.network()`正确加载。
|
||||
|
||||
- [ ] **Step 3: 添加发送成功对话框**
|
||||
|
||||
在分享到稍后读成功后,弹出CupertinoAlertDialog确认:
|
||||
|
||||
```dart
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text('已添加到稍后读'),
|
||||
content: Text('内容已成功保存到稍后读列表'),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('好的'),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 验证图片/视频显示和弹窗**
|
||||
|
||||
---
|
||||
|
||||
### Task 9: 闲情逸致Sheet热度排版+外部跳转提示
|
||||
|
||||
**问题:** 闲情逸致卡片Sheet的热度范围小、杂乱不堪,要求重新排版。底部按钮点击直接跳转外部软件,要求跳转前弹窗提示。
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/tool_center/leisure/presentation/widgets/leisure_card_detail_sheet.dart`
|
||||
|
||||
- [ ] **Step 1: 重新设计信息网格布局**
|
||||
|
||||
将`_buildInfoGrid()`从Wrap双列改为更清晰的列表式布局,每个信息项一行,图标+标签+值,使用Divider分隔:
|
||||
|
||||
```dart
|
||||
Widget _buildInfoItem(IconData icon, String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 14, color: ext.textHint),
|
||||
const SizedBox(width: 8),
|
||||
Text(label, style: caption1),
|
||||
const Spacer(),
|
||||
Flexible(child: Text(value, style: caption1, textAlign: TextAlign.end)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 为所有外部跳转添加确认弹窗**
|
||||
|
||||
在地图按钮和外部搜索按钮的点击事件中,添加跳转确认弹窗:
|
||||
|
||||
```dart
|
||||
Future<void> _launchExternal(String url, String appName) async {
|
||||
final confirmed = await showCupertinoDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text('即将离开闲言'),
|
||||
content: Text('将打开$appName查看更多内容'),
|
||||
actions: [
|
||||
CupertinoDialogAction(child: Text('取消'), onPressed: () => Navigator.pop(_, false)),
|
||||
CupertinoDialogAction(isDefaultAction: true, child: Text('打开'), onPressed: () => Navigator.pop(_, true)),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) await launchUrl(Uri.parse(url));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 举一反三 — 检查其他页面的外部跳转**
|
||||
|
||||
搜索所有`launchUrl`或`url_launcher`调用,为每个外部跳转添加确认弹窗。涉及文件:
|
||||
- leisure_share_sheet.dart
|
||||
- leisure_card_detail_sheet.dart
|
||||
- 其他使用url_launcher的页面
|
||||
|
||||
- [ ] **Step 4: 验证排版和跳转提示**
|
||||
|
||||
---
|
||||
|
||||
### Task 10: 我的收藏记录不显示+输入法问题
|
||||
|
||||
**问题:** 收藏的记录没显示。输入框动不动弹输入法,要求不点输入框禁止输入法。
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/favorite_page.dart`
|
||||
- Check: `lib/features/home/providers/favorite_provider.dart`
|
||||
|
||||
- [ ] **Step 1: 检查收藏数据加载逻辑**
|
||||
|
||||
在`favorite_provider.dart`中检查数据加载方法,确认API调用和数据解析是否正确。
|
||||
|
||||
- [ ] **Step 2: 修复收藏记录显示**
|
||||
|
||||
确保收藏列表正确渲染,检查空状态处理和数据绑定。
|
||||
|
||||
- [ ] **Step 3: 修复输入法自动弹出问题**
|
||||
|
||||
在搜索框外层添加`GestureDetector`拦截触摸,搜索框默认`enabled: false`,点击时才启用:
|
||||
|
||||
```dart
|
||||
GestureDetector(
|
||||
onTapDown: (_) => FocusScope.of(context).requestFocus(_searchFocusNode),
|
||||
child: CupertinoTextField(
|
||||
focusNode: _searchFocusNode,
|
||||
enabled: false, // 默认禁用
|
||||
...
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
或使用`KeyboardDismissOnTap`包装整个页面,页面进入时不自动聚焦搜索框。
|
||||
|
||||
- [ ] **Step 4: 验证收藏显示和输入法控制**
|
||||
|
||||
---
|
||||
|
||||
### Task 11: 我的设备页面时间和IP被遮住
|
||||
|
||||
**问题:** 最后活跃时间后面的几点几分看不到,IP归属地后面的信息也被遮住了
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/mine/user_center/presentation/devices/device_detail_sheet.dart`
|
||||
- Modify: `lib/features/mine/user_center/presentation/devices/device_card.dart`
|
||||
|
||||
- [ ] **Step 1: 修复DeviceInfoRow的文本溢出**
|
||||
|
||||
在`device_detail_sheet.dart`的`DeviceInfoRow`中,将value的`Flexible`改为`Expanded`,并确保label使用固定宽度:
|
||||
|
||||
```dart
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 14, color: ext.textHint),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 72, // 固定label宽度
|
||||
child: Text(label, style: caption1),
|
||||
),
|
||||
Expanded( // value占满剩余空间
|
||||
child: Text(value, style: caption1, textAlign: TextAlign.end, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 修复device_card.dart中IP显示溢出**
|
||||
|
||||
确保IP地址文本有足够空间,可能需要调整Row布局或减小字体。
|
||||
|
||||
- [ ] **Step 3: 验证长文本不再被遮住**
|
||||
|
||||
---
|
||||
|
||||
### Task 20: 主页精灵气泡秒消失修复
|
||||
|
||||
**问题:** 主页顶部的精灵角色,点击弹出的气泡秒消失,要求下一句不触发最少显示3秒
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/home_page.dart`
|
||||
- Check: `lib/shared/widgets/animation/appbar_character_sprite.dart` 或相关精灵组件
|
||||
|
||||
- [ ] **Step 1: 找到精灵气泡的显示逻辑**
|
||||
|
||||
搜索气泡/精灵相关的显示和隐藏代码。
|
||||
|
||||
- [ ] **Step 2: 添加最小显示时间**
|
||||
|
||||
在气泡显示后设置3秒计时器,计时器未结束时不得隐藏或替换:
|
||||
|
||||
```dart
|
||||
Timer? _bubbleTimer;
|
||||
bool _bubbleVisible = false;
|
||||
|
||||
void _showBubble(String text) {
|
||||
if (_bubbleTimer?.isActive ?? false) return; // 3秒内不触发新气泡
|
||||
setState(() {
|
||||
_bubbleText = text;
|
||||
_bubbleVisible = true;
|
||||
});
|
||||
_bubbleTimer = Timer(const Duration(seconds: 3), () {
|
||||
if (mounted) setState(() => _bubbleVisible = false);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证气泡至少显示3秒**
|
||||
|
||||
---
|
||||
|
||||
## 批次3: 页面重设计与功能完善 (中优先级)
|
||||
|
||||
### Task 5-6: 工具中心设置页面+拖拽+介绍
|
||||
|
||||
**问题:** 发现页工具中心需增加设置页面、支持长按拖拽删除、长按弹出工具介绍
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/features/discover/presentation/pages/tool_center_settings_page.dart`
|
||||
- Modify: `lib/features/discover/presentation/pages/home/discover_page.dart`
|
||||
- Modify: `lib/features/discover/providers/tool_center_provider.dart`
|
||||
- Modify: `lib/core/router/app_routes.dart` + 对应路由文件
|
||||
|
||||
- [ ] **Step 1: 创建工具中心设置页面**
|
||||
|
||||
包含: 使用统计、已删除工具管理、工具排序、搜索恢复已删除工具
|
||||
|
||||
- [ ] **Step 2: 在发现页底部添加设置按钮**
|
||||
|
||||
点击跳转工具中心设置页面
|
||||
|
||||
- [ ] **Step 3: 实现长按拖拽功能**
|
||||
|
||||
使用`ReorderableGridView`或自定义`Draggable`实现工具图标拖拽,底部显示垃圾桶区域:
|
||||
|
||||
```dart
|
||||
Draggable<ToolItem>(
|
||||
data: tool,
|
||||
feedback: Material(child: ToolIcon(tool, scale: 1.1)),
|
||||
childWhenDragging: Opacity(opacity: 0.3, child: ToolIcon(tool)),
|
||||
child: ToolIcon(tool),
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 实现删除和恢复逻辑**
|
||||
|
||||
删除工具: 从工具列表移除,标记为`isDeleted`
|
||||
恢复工具: 在设置页面搜索找到已删除工具,点击恢复
|
||||
|
||||
- [ ] **Step 5: 添加长按弹出工具介绍**
|
||||
|
||||
对重要工具添加介绍弹窗:
|
||||
|
||||
```dart
|
||||
onLongPress: () => _showToolIntro(context, tool),
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 验证拖拽、删除、恢复功能**
|
||||
|
||||
---
|
||||
|
||||
### Task 12: 内容纠错页面增强
|
||||
|
||||
**问题:** 增加"其他"内容类型选择、内容ID增加icon提示、纠错描述增加字数限制提示、纠错记录增加本地/服务端标识
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/correction/presentation/correction_page.dart`
|
||||
- Modify: `lib/features/correction/providers/correction_provider.dart`
|
||||
|
||||
- [ ] **Step 1: 添加"其他"内容类型**
|
||||
|
||||
在内容类型列表中添加`other`选项
|
||||
|
||||
- [ ] **Step 2: 内容ID右侧添加info icon**
|
||||
|
||||
点击显示提示: "若无ID,请填写0"
|
||||
|
||||
- [ ] **Step 3: 纠错描述增加最少字数提示**
|
||||
|
||||
添加提示文字: "请至少描述10个字",并在提交时校验
|
||||
|
||||
- [ ] **Step 4: 纠错记录增加本地/服务端标识**
|
||||
|
||||
在记录列表中每条记录添加来源标签(本地/服务端)
|
||||
|
||||
- [ ] **Step 5: 验证纠错功能**
|
||||
|
||||
---
|
||||
|
||||
### Task 13: 日签卡片页面重新设计
|
||||
|
||||
**问题:** 布局混乱不堪,要求重新设计,支持动态主题和动态样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/daily_card/presentation/daily_card_page.dart`
|
||||
- Modify: `lib/features/daily_card/presentation/widgets/card_renderer.dart`
|
||||
|
||||
- [ ] **Step 1: 分析当前布局问题**
|
||||
|
||||
- [ ] **Step 2: 重新设计日签卡片页面布局**
|
||||
|
||||
采用iOS 26风格: 顶部大卡片预览 + 底部样式/内容选择器 + 操作按钮栏
|
||||
|
||||
- [ ] **Step 3: 确保动态主题支持**
|
||||
|
||||
使用`AppTheme.ext(context)`获取主题色,所有颜色从主题获取
|
||||
|
||||
- [ ] **Step 4: 验证日签卡片页面**
|
||||
|
||||
---
|
||||
|
||||
### Task 15: 灵感页面重新设计
|
||||
|
||||
**问题:** 杂乱不堪,没有appbar,要求重写重新设计
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/pages/home/inspiration_page.dart`
|
||||
|
||||
- [ ] **Step 1: 添加CupertinoNavigationBar**
|
||||
|
||||
- [ ] **Step 2: 重新设计页面布局**
|
||||
|
||||
分类标签+内容卡片+视图切换,iOS 26风格
|
||||
|
||||
- [ ] **Step 3: 确保动态主题支持**
|
||||
|
||||
- [ ] **Step 4: 验证灵感页面**
|
||||
|
||||
---
|
||||
|
||||
### Task 16: 进度页面完善
|
||||
|
||||
**问题:** 进度设置有空壳UI和空壳功能,要求完善显示样式、数据管理、分享
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/progress/presentation/progress_page.dart`
|
||||
|
||||
- [ ] **Step 1: 完善进度显示样式**
|
||||
|
||||
添加更多可视化: 环形进度、进度条、倒计时网格
|
||||
|
||||
- [ ] **Step 2: 完善数据管理**
|
||||
|
||||
添加编辑/删除进度项功能
|
||||
|
||||
- [ ] **Step 3: 完善分享功能**
|
||||
|
||||
生成进度卡片图片并分享
|
||||
|
||||
- [ ] **Step 4: 验证进度页面**
|
||||
|
||||
---
|
||||
|
||||
## 批次4: 设置与权限修复 (中优先级)
|
||||
|
||||
### Task 14: 壁纸模板下载/编辑按钮提示修改
|
||||
|
||||
**问题:** 壁纸下载和编辑按钮的toast提示改成"暂无版权相关的描述"
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/template/presentation/template_gallery_page.dart`
|
||||
- Check: 壁纸预览Sheet相关文件
|
||||
|
||||
- [ ] **Step 1: 找到下载和编辑按钮的toast提示代码**
|
||||
|
||||
- [ ] **Step 2: 修改提示文字为版权相关描述**
|
||||
|
||||
```dart
|
||||
showToast('暂无版权授权,无法下载/编辑此壁纸');
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证提示文字**
|
||||
|
||||
---
|
||||
|
||||
### Task 17: 面对面快传跳转文件传输助手
|
||||
|
||||
**问题:** 点击面对面快传要求跳转文件传输助手页面
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/mine/profile/presentation/profile_page.dart`
|
||||
|
||||
- [ ] **Step 1: 修改_quickActions中面对面快传的回调**
|
||||
|
||||
从`FeatureFlag.quickTransfer.unsupportedMessage`改为导航到文件传输页面:
|
||||
|
||||
```dart
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
context.go(AppRoutes.fileTransfer);
|
||||
},
|
||||
child: Text('面对面快传'),
|
||||
),
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证跳转**
|
||||
|
||||
---
|
||||
|
||||
### Task 18: 预测返回和长按预览默认关闭锁定
|
||||
|
||||
**问题:** 预测返回按钮默认关闭锁定不可打开,长按预览同样
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/mine/settings/presentation/general/general_settings_sections.dart`
|
||||
- Modify: `lib/features/mine/settings/providers/general_settings_provider.dart`
|
||||
|
||||
- [ ] **Step 1: 将预测返回和长按预览设置为锁定状态**
|
||||
|
||||
在设置项中添加`isLocked: true`属性:
|
||||
|
||||
```dart
|
||||
SettingItem(
|
||||
id: 'predictive_back',
|
||||
...
|
||||
isLocked: true, // 锁定不可修改
|
||||
lockedReason: '该功能暂不可用',
|
||||
),
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在设置页面UI中处理锁定状态**
|
||||
|
||||
锁定项显示锁定icon,点击弹出提示"该功能暂不可用"
|
||||
|
||||
- [ ] **Step 3: 确保默认值为false**
|
||||
|
||||
- [ ] **Step 4: 验证锁定状态**
|
||||
|
||||
---
|
||||
|
||||
### Task 19: 主题壁纸/背景功能完善
|
||||
|
||||
**问题:** 壁纸/背景点击无反应,空壳功能
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/mine/settings/presentation/theme/theme_sections_style.dart`
|
||||
|
||||
- [ ] **Step 1: 完善`_selectFromGallery`方法**
|
||||
|
||||
使用`image_picker`实现从相册选择壁纸:
|
||||
|
||||
```dart
|
||||
Future<void> _selectFromGallery(BuildContext context, WidgetRef ref) async {
|
||||
final hasPermission = await PermissionService.instance.requestPhotos();
|
||||
if (!hasPermission) return;
|
||||
final picker = ImagePicker();
|
||||
final image = await picker.pickImage(source: ImageSource.gallery);
|
||||
if (image != null) {
|
||||
ref.read(themeSettingsProvider.notifier).setWallpaper(image.path, 'gallery');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 完善预设壁纸选择**
|
||||
|
||||
确保预设壁纸资源文件存在,或使用渐变色代替
|
||||
|
||||
- [ ] **Step 3: 验证壁纸设置功能**
|
||||
|
||||
---
|
||||
|
||||
### Task 21: 拼音注音icon样式和对话框
|
||||
|
||||
**问题:** 拼音注音右下角icon样式改成其他的,点击弹出对话框说明汉语拼音等
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/mine/settings/presentation/plugin/plugin_pinyin_card.dart`
|
||||
|
||||
- [ ] **Step 1: 修改右下角icon样式**
|
||||
|
||||
从当前样式改为`CupertinoIcons.info_circle`或自定义样式
|
||||
|
||||
- [ ] **Step 2: 添加点击弹出对话框**
|
||||
|
||||
```dart
|
||||
onTap: () => _showPinyinIntro(context),
|
||||
|
||||
void _showPinyinIntro(BuildContext context) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text('拼音注音'),
|
||||
content: Text('基于汉语拼音方案,为汉字自动标注带声调的拼音。\n\n'
|
||||
'• 支持逐字注音\n'
|
||||
'• 支持带调标注\n'
|
||||
'• 支持诗词全文注音\n\n'
|
||||
'数据来源:《现代汉语词典》标准拼音'),
|
||||
actions: [
|
||||
CupertinoDialogAction(isDefaultAction: true, child: Text('了解了'), onPressed: () => Navigator.pop(_)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证icon和对话框**
|
||||
|
||||
---
|
||||
|
||||
### Task 23: 分析存储权限必要性
|
||||
|
||||
**问题:** 分析READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限是否有必要保留
|
||||
|
||||
**分析:**
|
||||
- `READ_EXTERNAL_STORAGE` (maxSdkVersion=32): Android 12及以下需要读取图片/视频。Android 13+使用READ_MEDIA_IMAGES/READ_MEDIA_VIDEO替代。**需要保留**,但maxSdkVersion=32已正确限制。
|
||||
- `WRITE_EXTERNAL_STORAGE` (maxSdkVersion=29): Android 10及以下需要写入外部存储。Android 10+使用Scoped Storage不需要此权限。**可以移除**,因为maxSdkVersion=29意味着只在Android 9及以下生效,而项目minSdkVersion可能已高于此。
|
||||
|
||||
- [ ] **Step 1: 确认项目minSdkVersion**
|
||||
|
||||
- [ ] **Step 2: 如果minSdkVersion >= 30,移除WRITE_EXTERNAL_STORAGE**
|
||||
|
||||
- [ ] **Step 3: 保留READ_EXTERNAL_STORAGE(maxSdk=32)和READ_MEDIA_*权限**
|
||||
|
||||
- [ ] **Step 4: 更新权限说明文档**
|
||||
|
||||
---
|
||||
|
||||
## 批次5: 页面细节优化 (中低优先级)
|
||||
|
||||
### Task 13+15+16 的具体实现已在批次3中描述
|
||||
|
||||
---
|
||||
|
||||
## 批次6: 审计与总结
|
||||
|
||||
### Task 24: 审计验收
|
||||
|
||||
- [ ] **Step 1: 编译检查** — `flutter analyze`
|
||||
- [ ] **Step 2: Android编译测试** — `flutter build apk --debug`
|
||||
- [ ] **Step 3: 逐项验证25个问题的修复**
|
||||
- [ ] **Step 4: 更新CHANGELOG.md**
|
||||
|
||||
### Task 25: 项目不足分析和建议
|
||||
|
||||
- [ ] **Step 1: 分析项目架构不足**
|
||||
- [ ] **Step 2: 提出可扩展的三方库和交互功能建议**
|
||||
- [ ] **Step 3: 输出分析报告**
|
||||
|
||||
---
|
||||
|
||||
## 文件变更总览
|
||||
|
||||
| 文件 | 变更类型 | 涉及任务 |
|
||||
|------|---------|---------|
|
||||
| `android/app/src/main/AndroidManifest.xml` | 修改 | Task 1, 23 |
|
||||
| `lib/main.dart` | 修改 | Task 22 |
|
||||
| `lib/core/services/device/shake_detector.dart` | 修改 | Task 3, 4 |
|
||||
| `lib/core/services/auth/permission_service.dart` | 修改 | Task 3 |
|
||||
| `lib/core/services/background/background_task_service.dart` | 修改 | Task 1 |
|
||||
| `lib/features/home/presentation/home_page.dart` | 修改 | Task 2, 4, 20 |
|
||||
| `lib/features/mine/settings/presentation/privacy/permission_management_page.dart` | 修改 | Task 3 |
|
||||
| `lib/features/discover/presentation/pages/home/discover_page.dart` | 修改 | Task 5-6 |
|
||||
| `lib/features/discover/providers/tool_center_provider.dart` | 修改 | Task 5-6 |
|
||||
| `lib/features/discover/presentation/pages/home/inspiration_page.dart` | 修改 | Task 15 |
|
||||
| `lib/features/home/presentation/providers/readlater_page.dart` | 修改 | Task 7, 8 |
|
||||
| `lib/features/tool_center/leisure/presentation/widgets/leisure_card_detail_sheet.dart` | 修改 | Task 9 |
|
||||
| `lib/features/home/presentation/favorite_page.dart` | 修改 | Task 10 |
|
||||
| `lib/features/mine/user_center/presentation/devices/device_detail_sheet.dart` | 修改 | Task 11 |
|
||||
| `lib/features/mine/user_center/presentation/devices/device_card.dart` | 修改 | Task 11 |
|
||||
| `lib/features/correction/presentation/correction_page.dart` | 修改 | Task 12 |
|
||||
| `lib/features/daily_card/presentation/daily_card_page.dart` | 修改 | Task 13 |
|
||||
| `lib/features/template/presentation/template_gallery_page.dart` | 修改 | Task 14 |
|
||||
| `lib/features/progress/presentation/progress_page.dart` | 修改 | Task 16 |
|
||||
| `lib/features/mine/profile/presentation/profile_page.dart` | 修改 | Task 17 |
|
||||
| `lib/features/mine/settings/presentation/general/general_settings_sections.dart` | 修改 | Task 18 |
|
||||
| `lib/features/mine/settings/providers/general_settings_provider.dart` | 修改 | Task 18 |
|
||||
| `lib/features/mine/settings/presentation/theme/theme_sections_style.dart` | 修改 | Task 19 |
|
||||
| `lib/features/mine/settings/presentation/plugin/plugin_pinyin_card.dart` | 修改 | Task 21 |
|
||||
| `lib/features/onboarding/presentation/pages/agreement_page.dart` | 修改 | Task 22 |
|
||||
| `lib/features/discover/presentation/pages/tool_center_settings_page.dart` | 新建 | Task 5-6 |
|
||||
| `CHANGELOG.md` | 修改 | Task 24 |
|
||||
171
docs/superpowers/plans/2026-05-30-tool-panel-improvements.md
Normal file
171
docs/superpowers/plans/2026-05-30-tool-panel-improvements.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 工具面板15项改进实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 实施工具面板15项架构重构与功能扩展
|
||||
|
||||
**Architecture:** 分4个阶段实施:Phase 1 架构基础 → Phase 2 代码质量 → Phase 3 UX打磨 → Phase 4 新功能。每阶段完成后运行 flutter analyze 验证。
|
||||
|
||||
**Tech Stack:** Flutter/Dart, Riverpod, share_plus, Cupertino风格
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: 架构基础(5项)
|
||||
|
||||
### Task 1: 动画配置类抽取 (#5)
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/features/discover/presentation/widgets/tool/tool_panel_config.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
|
||||
- [ ] 创建 ToolPanelAnimConfig 配置类,抽取所有硬编码动画参数
|
||||
- [ ] 在 _ToolPanelAnimatedContentState 中使用配置类替换硬编码值
|
||||
|
||||
### Task 2: ToolPanelOverlayRoute 公开化 (#15)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
|
||||
- [ ] 将 `_ToolPanelOverlayRoute` 重命名为 `ToolPanelOverlayRoute` 并公开
|
||||
- [ ] 确保外部可测试和复用
|
||||
|
||||
### Task 3: DragState 封装 (#12)
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/features/discover/presentation/widgets/tool/tool_panel_drag_state.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
|
||||
- [ ] 创建 DragState 类封装拖拽状态管理
|
||||
- [ ] 替换4个抽象方法为单一 DragState 对象
|
||||
- [ ] 更新 SectionsMixin 使用 DragState
|
||||
|
||||
### Task 4: 导航配置数据驱动 (#8)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_navigator.dart`
|
||||
- Modify: `lib/features/discover/models/tool_item.dart`
|
||||
|
||||
- [ ] 在 ToolItem 模型中添加 navConfig 字段
|
||||
- [ ] 创建 ToolNavConfig 数据类替代 switch-case
|
||||
- [ ] 在 defaultTools 中为每个工具配置 navConfig
|
||||
- [ ] ToolPanelNavHelper 改为从 navConfig 读取
|
||||
|
||||
### Task 5: Mixin链简化为组合模式 (#9)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_navigator.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_actions.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
|
||||
- [ ] 将 NavigatorMixin 改为独立 ToolPanelNavigation 类(持有 ref/context)
|
||||
- [ ] 将 ActionsMixin 改为独立 ToolPanelActions 类
|
||||
- [ ] 将 SectionsMixin 改为独立 ToolPanelSections 类
|
||||
- [ ] State 中通过组合持有这些类实例
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 代码质量(3项)
|
||||
|
||||
### Task 6: GridView 重复代码抽取 (#10)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
|
||||
- [ ] 抽取 buildToolGrid 公共方法
|
||||
- [ ] buildCategorizedTools 和 buildSearchResults 复用
|
||||
|
||||
### Task 7: ToolGridItem 回调简化 (#11)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
|
||||
- [ ] 抽取 buildGridItemCallbacks 方法
|
||||
- [ ] childWhenDragging 和 child 共用回调
|
||||
|
||||
### Task 8: 错误边界 — 工具跳转失败用户提示 (#13)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_navigator.dart`
|
||||
|
||||
- [ ] navigateToTool 添加 try-catch + AppToast 错误提示
|
||||
- [ ] 离线工具点击时显示状态提示
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: UX打磨(3项)
|
||||
|
||||
### Task 9: 分类吸顶实现 (#4)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
|
||||
- [ ] 将 Column+SliverToBoxAdapter 改为纯 Sliver 布局
|
||||
- [ ] 实现 CategoryPinnedHeaderDelegate (SliverPersistentHeaderDelegate)
|
||||
- [ ] 分类标题滚动时吸顶
|
||||
|
||||
### Task 10: 弹性回弹动画 (#14)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
|
||||
- [ ] 使用 SpringSimulation 替代线性 dismissOffset 归零
|
||||
- [ ] 添加弹性回弹效果
|
||||
|
||||
### Task 11: 无障碍 Semantics 支持 (#6)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_widgets.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_grid_item.dart`
|
||||
|
||||
- [ ] 工具网格项添加 Semantics(label/hint)
|
||||
- [ ] 操作菜单项添加 Semantics
|
||||
- [ ] 搜索结果空状态添加 Semantics
|
||||
- [ ] 拖拽反馈添加 Semantics
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: 新功能(4项)
|
||||
|
||||
### Task 12: 工具收藏/自定义排序 (#1)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel.dart`
|
||||
|
||||
- [ ] 新增收藏工具专区(favoritedTools)
|
||||
- [ ] 排序选择器(使用频率/名称/最近使用/评分)
|
||||
- [ ] 排序状态持久化
|
||||
|
||||
### Task 13: 工具使用统计面板 (#2)
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/features/discover/presentation/widgets/tool/tool_stats_sheet.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_actions.dart`
|
||||
|
||||
- [ ] 创建 ToolStatsSheet 底部弹窗
|
||||
- [ ] 显示使用频率柱状图 + 时长统计 + 最近使用记录
|
||||
- [ ] 长按菜单"📊 使用统计"接入
|
||||
|
||||
### Task 14: 工具分享功能 (#3)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_navigator.dart`
|
||||
|
||||
- [ ] 接入项目已有 ShareSheet 系统
|
||||
- [ ] 构建 ToolItem → ShareData 转换
|
||||
- [ ] 替换 Toast 提示为真实分享
|
||||
|
||||
### Task 15: 工具版本管理 (#7)
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/discover/models/tool_item.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_grid_item.dart`
|
||||
- Modify: `lib/features/discover/presentation/widgets/tool/tool_panel_sections.dart`
|
||||
|
||||
- [ ] ToolItem 添加 version/changelog/updatedAt 字段
|
||||
- [ ] isNew 角标逻辑优化(基于版本更新时间)
|
||||
- [ ] 工具详情弹窗显示版本号和更新日志
|
||||
Reference in New Issue
Block a user