iOS 提交

This commit is contained in:
Developer
2026-06-27 04:57:00 +08:00
parent 10a917adf6
commit b8d0bd39b5
20 changed files with 5403 additions and 247 deletions

View File

@@ -6,6 +6,271 @@
***
## [v6.144.0] - 2026-06-27
### 🐛 修复句子广场已收藏句子重复出现且未显示状态 + 按钮轮廓不清
#### 问题现象
1. 句子广场中已收藏的句子在循环加载时重复出现,且未显示"已收藏"状态
2. 未点赞/未收藏时按钮轮廓过淡(边框 0.5px + 25% white用户无法辨识可点击区域
#### 根因分析
1. **服务端根因**`/api/feed/list` 接口 `_batchAttachInteractionCounts` 只回填聚合计数like_count 等),不返回当前用户的 `is_liked`/`is_favorited` 状态。客户端 `HomeSentence.fromFeedItem` 解析时降级为 false导致已收藏句子重复出现时显示"未收藏"。
2. **客户端根因**`fetchRefreshSentences`/`fetchNewSentences` 直接使用服务端返回的 `isLiked`/`isFavorited`,未合并本地 DB 的互动状态。未登录用户服务端始终返回 false本地 DB 是唯一状态来源但未被读取。
3. **循环加载根因**`cycleRound >= 3``allSeenIds.clear()` 清空所有已见 ID已收藏句子的去重记录也一并清除导致循环加载时已收藏内容重复出现。
4. **按钮样式根因**`LiquidGlassActionButton` 未激活态边框仅 0.5px + 25% white 透明度,视觉上几乎不可见。
#### 服务端修复
**`docs/toolsapi/application/api/controller/Feed.php`**
- `_batchAttachInteractionCounts(&$items, $userId = 0)`:新增 `$userId` 参数,当用户已登录时批量查询 `feed_interaction` 表中该用户的 like/favorite 记录,回填 `is_liked`/`is_favorited` 字段。
- `list()` 方法:调用处从 `$this->_batchAttachInteractionCounts($items, $userId)` 改为 `$this->_batchAttachInteractionCounts($items, $this->_getUserId())`(修复未定义变量 `$userId`)。
- `_batchLoadFeedItems()` 方法:同样传入 `$this->_getUserId()`
- **缓存隔离**:缓存命中后重新调用 `_batchAttachInteractionCounts` 按当前用户重查 per-user 字段;缓存写入前剥离 `is_liked`/`is_favorited` 为 false避免不同用户串状态。
#### 客户端修复
**`lib/core/storage/database/app_database.dart`**
- 新增 `getSentencesByIds(List<String> ids)` 批量查询方法,返回 `Map<String, Sentence>`,用于合并本地 DB 互动状态。
**`lib/features/home/providers/home_feed_mixin.dart`**
- 新增 `_mergeLocalInteractionState(sentences)` 私有方法:批量查询本地 DB采用 OR 合并策略(`local true wins`)——服务端 true 或本地 true 均为 true。覆盖未登录场景服务端始终 false本地 DB 是唯一来源)和登录场景(本地兜底未同步的离线操作)。
- `fetchRefreshSentences``result.list.map(HomeSentence.fromFeedItem)` 后调用 `_mergeLocalInteractionState` 合并本地状态。
- `fetchNewSentences`:同上,生成 `mergedSentences` 供后续去重/循环加载使用。
- `cycleRound >= 3` 循环加载:清空 `allSeenIds`/`allSeenTexts` 前保留已收藏句子的 ID 和文本,恢复到去重集合中;`recentIds`/`recentTexts` 合并已收藏内容,确保循环加载时跳过已收藏句子。
**`lib/features/home/presentation/home_sentence_card.dart`**
- `LiquidGlassActionButton` 未激活态:边框宽度 0.5px → 1.2px;背景透明度加深(深色 8%→15% white浅色 18%→28% white边框透明度加深深色 12%→30% white浅色 25%→50% white
#### 验证
- `flutter analyze` 三个修改文件 0 issues
- curl 验证服务端:`limit=8` 非缓存路径返回 `"is_liked":false,"is_favorited":false`(未登录正确);缓存命中路径 `"成功(cache)"` 同样返回字段(重新附加 per-user 状态)
- 缓存隔离验证:缓存写入前剥离 per-user 字段,命中后按当前用户重查,杜绝跨用户串状态
#### 不足与建议
- **缓存命中额外查询**:每次缓存命中调用 `_batchAttachInteractionCounts` 会重新查询聚合计数(冗余),可后续拆分为仅查询 per-user 状态的轻量方法。
- **跨设备同步延迟**:未登录用户依赖本地 DB登录用户首次加载时服务端返回正确状态但缓存命中时 per-user 状态依赖 `_batchAttachInteractionCounts` 重查,若 DB 负载高可能增加延迟。
- **循环加载已收藏保留策略**:当前保留全部已收藏 ID 在 `allSeenIds` 中,若用户收藏量极大(>300`_buildSeenIdList` 会截断为最近 300 条,早期收藏可能再次出现。可考虑按收藏时间分页保留。
***
## [v6.143.0] - 2026-06-27
### 🐛 修复 lite 模式点赞/取消点赞后 like_count 不更新的问题
#### 问题现象
- 主页句子广场lite 模式limit=5点赞或取消点赞后like_count 不变化
- curl 实测unlike 后 lite list 返回 `msg=成功(cache)`like_count 仍为旧值
- 非 lite 模式limit=10/20正常仅 lite 模式 + limit=5 异常
#### 根因分析(三层 bug
1. **真正根因(核心)**`_clearFeedCache``$limits = [10, 20, 30, 50]` **不包含 5**,而客户端 lite 模式请求用 limit=5导致 `feed_list_hitokoto_newest_1_5_lite` 这个 cacheKey 从未被清理。互动操作后旧缓存仍然命中,返回过期数据。
2. **潜在 bug已兜底**`Cache::rm` 在某些环境opcache 缓存旧代码 / ThinkPHP complex 驱动异常)下不删除文件。诊断脚本中 `Cache::rm` 返回 true 但文件仍在。
3. **历史遗漏**`_clearFeedCache` 之前未覆盖 `_lite` 后缀的 cacheKeyv6.142.0 已加,但 limit=5 才是关键阻塞点)。
#### 服务端修复
**`docs/toolsapi/application/api/controller/Feed.php`**
- `_clearFeedCache``$limits``[10, 20, 30, 50]` 扩展为 `[5, 10, 15, 20, 25, 30, 40, 50]`,覆盖客户端所有可能的 limit 值(含 5。同步修复 `_clearWeightCache` 的同名数组。
- 新增 `_unlinkCacheFile($cacheKey)` 私有方法:在 `Cache::rm` 之后兜底,按 ThinkPHP 5 File 驱动路径规则(`CACHE_PATH/前2位md5/剩余30位md5.php`)直接 `unlink` 缓存文件,绕过 opcache 缓存旧代码 / complex 驱动异常。
- `_clearFeedCache` 所有 `Cache::rm` 调用后均追加 `$this->_unlinkCacheFile()` 兜底,覆盖 singleKeys / list 列表 / trending / 平台后缀 全部缓存 key。
- 移除 v6.142.0 排查期间遗留的 7 处临时调试代码(`file_put_contents` 写入 unlike_debug/clearcache_debug/feed_cache_debug 日志)。
#### 验证
自动化测试脚本 `/tmp/test_lite_cache.py` 全流程验证通过:
```
[stepA] 预热: msg='成功' feed_id=8812 like_count=3
[stepB] like: msg='操作成功'
[stepC] like后: msg='成功' like_count=4 期望=4 cache命中=False [OK]
[stepD] unlike: msg='操作成功'
[stepE] unlike后: msg='成功' like_count=3 期望=3 cache命中=False [OK]
=== 全部通过: lite 模式缓存清理修复成功 ===
```
关键验证点:
- stepC/stepE 的 `msg` 不再包含 `cache`,证明缓存已被清理、强制重新查库
- like_count 正确递增3→4和递减4→3
#### 排查过程经验
- 最初误判"unlike 不递减",实际是缓存竞态。加 sleep 2 秒重测发现 unlike 本身工作正常。
- 调试日志file_put_contents全部为空误导排查方向。根因是 opcache 缓存了旧 Feed.php不含调试日志的版本PHP-FPM 重启后 opcache 未彻底清理。通过 `opcache_invalidate(Feed.php, true)` + `opcache_reset()` 解决。
- 最终通过在 `_unlinkCacheFile` 中加 trace 日志,对比 `newest_1_5_lite``newest_1_10_lite` 的记录,发现 limit=5 的 key 从未被处理,定位到 `$limits` 数组遗漏。
#### 文档同步
- `API_FEED_DOC.md` 头部版本号 v2.4 → v2.5
- 修正 v6.142.0 中关于 unlike 的错误结论(标注为已修复,指向本版本)
#### 不足与建议
- **$limits 仍为枚举**:若未来客户端新增 limit=35 等值,需同步更新数组。建议后续改为 `range(5, 50, 5)` 或用 glob 扫描缓存文件前缀的方式彻底规避。
- **opcache 自动清理依赖**:当前依赖 `validate_timestamps=1` + `revalidate_freq=3`,文件更新后最多 3 秒延迟。若部署后 3 秒内有请求命中旧 opcache仍可能用旧代码。建议部署脚本中调用 `opcache_reset` 或重启 PHP-FPM。
- **Cache::rm 与 unlink 双写**:当前每次互动操作执行约 320 次 `Cache::rm + unlink`,性能可接受但略显冗余。可考虑用 `Cache::clear()` 清空整个缓存目录,但会影响其他模块缓存命中率。
***
## [v6.142.0] - 2026-06-27
### 🐛 修复主页句子广场点赞按钮显示 0 的问题
#### 根因分析(双重 bug
1. **服务端根因**`/api/feed/list` 接口 `_formatItem` 默认不返回 `like_count/favorite_count/comment_count/share_count` 字段,只有 `/api/feed/detail` 才附加。客户端 `FeedItem.fromJson` 解析 `null` → 默认 0 → 按钮显示 0。
2. **客户端根因**`toggleLike`/`toggleFavorite` 乐观更新时只翻转 `isLiked/isFavorited`,未递增/递减 `likeCount/favoriteCount`,导致即使点击点赞数字也不变。
#### 服务端修复
**`docs/toolsapi/application/api/controller/Feed.php`**
- 新增 `_batchAttachInteractionCounts(&$items)` 私有方法:
- 收集所有 `(feed_type, feed_id)`
- 按 feed_type 分组批量查询 `feed_interaction` 表,`GROUP BY feed_id, action` 一次性获取 like/favorite/comment/share 计数
- 回填到 items 的 `like_count/favorite_count/comment_count/share_count` 字段
- 避免 N+1 查询,列表接口性能影响极小
- `list()` 方法末尾调用,`_batchLoadFeedItems()` 末尾也调用(覆盖 favorites/likes/history/readlater 列表)
- 查询失败时降级填充 0保证字段始终存在客户端不再解析 null
#### 客户端修复
**`lib/features/home/providers/home_interaction_mixin.dart`**
- `toggleLike` 乐观更新:除翻转 `isLiked` 外,同步递增/递减 `likeCount`(点赞 +1取消点赞 -1下限 0
- `toggleFavorite` 乐观更新:除翻转 `isFavorited` 外,同步递增/递减 `favoriteCount`(举一反三,同类问题)
- `_persistLikeLocally` 兜底插入分支:调用新增的 `setLikesCount` 同步写入本地 DB 的 `likes` 字段
**`lib/core/storage/database/app_database.dart`**
- 新增 `setLikesCount(id, count)` 方法:
- 由于 `.g.dart` 未重新生成(`build_runner` 受 freezed/analyzer 版本冲突阻塞),`SentencesCompanion` 缺少 `likes` 命名参数
- 改用 `customStatement` 直接执行 SQL`UPDATE sentences SET likes = ?, updated_at = ? WHERE id = ?`
- 自动 clamp 负数为 0
**`lib/features/home/providers/home_sentence_model.dart`**
- `HomeSentence.fromDb` 添加注释说明 `likes` 字段未读取原因Sentence 类缺 `likes` getter.g.dart 未重新生成)
- 兜底场景保持 likeCount=0主场景由服务端返回真实数据
#### 验证
- curl 实测 `/api/feed/list` 修复前:`like_count= MISSING favorite_count= MISSING`
- curl 实测 `/api/feed/list` 修复后:`like_count= 4 favorite_count= 3 comment_count= 0`(真实数据)
- 端到端验证:登录 → 查询 detaillike_count=4, is_liked=True→ list 接口返回一致 like_count=4
- `flutter analyze` 三个修改文件零警告
#### 文档同步
- `API_FEED_DOC.md` 头部版本号 v2.3 → v2.4
- 3.4 节响应字段说明新增 v2.4 更新提示
#### 不足与建议
- ~~**服务端 unlike 潜在 bug**curl 实测 unlike 后 list 接口的 like_count 仍为 4没有递减。可能 feed_interaction 表的 unlike 操作未真正删除记录(待排查 action 方法逻辑)。~~ **[v6.143.0 已修复]** 实际根因是 `_clearFeedCache``$limits` 数组不含 5导致 lite 模式 limit=5 的缓存未被清理,详见 v6.143.0。
- **`build_runner` 阻塞**:导致 `SentencesCompanion`/`Sentence` 类缺少 `likes` 字段,本地兜底场景 likeCount 显示 0。建议解决 freezed 3.2.5/analyzer 12.1.0 版本冲突后重新生成。
- **可扩展**`_batchAttachInteractionCounts` 可进一步附加 `is_liked`/`is_favorited` 字段需登录用户ID让 list 接口也能返回用户个人互动状态,避免客户端依赖 detail 接口。
***
## [v6.141.0] - 2026-06-27
### ✨ LiquidGlassActionButton 深度升级 + 触觉音效 + 长按交互
#### 背景
v6.140.0 完成句子卡片按钮重设计与收藏 Bug 修复后,识别出 5 项不足。本版本逐一解决。
#### 改进清单
##### 1. 真正的 BackdropFilter 液态玻璃
**`lib/features/home/presentation/home_sentence_card.dart`**
- `LiquidGlassActionButton` 未激活态改用真正的 `BackdropFilter` + `ImageFilter.blur`
- sigma 计算:`GlassTokens.baseBlur * ext.glassBlurMultiplier`(按钮较小用 base 层 10.0
- 性能降级链路:
- 鸿蒙端 `OhosDeviceCapabilities.supportsBackdropFilter` 判断
- `PerformanceOptimizer.instance.shouldEnableBackdropFilter` 省电模式降级
- `PerformanceOptimizer.instance.suggestedBlurSigma()` 低端设备 sigma 缩放
- `RepaintBoundary` 隔离重绘,避免列表滚动重复模糊
- 激活态仍用渐变填充(无需模糊)
##### 2. 统一动画驱动
- 移除 `AnimatedScale` + `ScaleTransition` 双重叠加
- 改为单一 `AnimatedBuilder` + `Transform.scale` 统一缩放整个按钮icon + 文字)
- 动画曲线 `Curves.easeInOutBack`,时长 400ms
##### 3. 跨模块文案依赖修复
- `t.discover.base.favorite/favorited``t.home.sentenceDetail.favorite/favorited`
- home 模块不再依赖 discover 模块翻译,符合 AGENTS.md 架构约束
- `THomeBase` 无 favorite 字段,但 `TSentenceDetail`home.sentenceDetail已有完整字段
##### 4. 稍后读同类 Bug 排查(无需修复)
- 调查发现 `_persistReadLaterLocally` **不写 sentences 表**
- 稍后读状态通过 `ChatMessageService.sendReadLaterSentence` 写入 ChatMessage 表(会话 ID `'readlater'`
- 取消时调 `db.softDeleteChatMsgRecord` 软删除
- 已有 try-catch + `inserted = true` 兜底,逻辑鲁棒
- **结论**:稍后读不存在 UPDATE 静默失败问题,无需修复
##### 5. HapticService + SfxService 触觉音效接入
**`lib/features/home/presentation/home_sentence_card.dart`**
- 新增 `HapticType` 枚举like / favorite
- `LiquidGlassActionButton` 新增 `hapticType` 参数
- 激活态:`HapticService.medium()` + `SfxService.instance.play(SfxType.like/favorite)`
- 取消态:`HapticService.light()` + `SfxService.instance.play(SfxType.unlike/unfavorite)`
- 反馈在 `didUpdateWidget` 中根据状态变化触发,与动画同步
##### 6. 长按卡片触发重冲击
**`lib/core/utils/ui/interaction_animations.dart`**
- `PressableCard` 新增 `onLongPress` 参数
- 长按触发时自动回弹(避免停留在按压态)
**`lib/features/home/presentation/home_sentence_card.dart`**
- `SentenceCard` 接入长按:`HapticService.heavy()` + 触发详情面板
- 与点击效果一致,但提供差异化触觉反馈(重冲击 vs 无冲击)
##### 7. 服务端 /api/feed/favorites 验证
- 服务端代码审查:`Feed.php::_formatItem` 第 2186 行明确返回 `'feed_type' => $type`
- 客户端解析:`FeedItem.fromJson` 第 295 行 `feedType: json['feed_type'] as String? ?? ''`
- 接口在线验证curl 返回 401未登录非 404接口正常
- **结论**feed_type 字段链路完整,无需修复
#### 验证
- `flutter analyze` 通过0 error / 0 warning
- 2 个修改文件的 IDE 诊断全部通过
#### 改动文件
| 文件 | 改动 |
|------|------|
| [home_sentence_card.dart](file:///Users/wushu/Documents/trae_projects/project/xianyan/lib/features/home/presentation/home_sentence_card.dart) | LiquidGlassActionButton 重构 + HapticType + 长按接入 + 文案改用 home.sentenceDetail |
| [interaction_animations.dart](file:///Users/wushu/Documents/trae_projects/project/xianyan/lib/core/utils/ui/interaction_animations.dart) | PressableCard 新增 onLongPress 支持 |
---
## [v6.140.0] - 2026-06-27
### 🐛 修复句子广场收藏在「我的收藏」页面不显示的 Bug + 卡片按钮重设计
#### 背景
用户反馈主页「句子广场」的句子卡片点击收藏后,在「我的收藏」页面没显示收藏的句子。同时要求重新设计点赞/收藏按钮为长方形 icon+文字 样式,去掉分享 icon 按钮。
#### Bug 根因(多重故障叠加)
1. **本地数据库 UPDATE 静默失败**`home_interaction_mixin._persistFavoriteLocally` 直接调用 `interactionDb.toggleFavorite(id)` UPDATE若 sentences 表中不存在该 id 的记录Feed 列表未缓存/缓存被清UPDATE 静默失败,本地 isFavorite 字段从未被设置。
2. **Legacy API 硬编码 `targetType: 'article'`**`favorite_repository._loadLegacyFavorites` 中硬编码 `targetType: 'article'`,但句子广场的 feedType 是 `hitokoto`/`chengyu`/`hanzi`/`poetry` 等,降级 Legacy API 永远查不到句子收藏。
3. **`mergeWithLocalDb` 去重 key 不鲁棒**:仅用 `content+author` 作为去重 key空 content 时退化为 `|title`,多条空 content 条目互相误去重;且未利用 id 信息服务端和本地同一句子id 相同但 content 微小差异)会被当作两条。
4. **降级条件过激**`_loadFeedFavorites``result.list.isEmpty && result.total == 0 && page == 1` 触发立即降级 Legacy API在服务端 bug / 缓存延迟 / 新用户无收藏等场景下误降级。
#### 修复
| 文件 | 修复 |
|------|------|
| `lib/features/home/providers/home_interaction_mixin.dart` | `_persistFavoriteLocally` 改为先查再决定 `setFavoriteFlag``insertOrUpdateSentence` upsert 兜底;举一反三同步修复 `_persistLikeLocally` 相同问题 |
| `lib/core/storage/database/app_database.dart` | 新增 `setLikeFlag(id, value)` 方法(与 `setFavoriteFlag` 对称),避免 `toggleLike` 在记录不存在时静默失败 |
| `lib/features/home/favorite_repository.dart` | `_loadLegacyFavorites` 移除硬编码 `targetType: 'article'`,让服务端返回所有类型收藏 |
| `lib/features/home/favorite_repository.dart` | `mergeWithLocalDb` 改为双重去重策略:主 key 用 `targetType\|targetId`,副 key 用 `content\|title`(空 content 不参与去重) |
| `lib/features/home/favorite_repository.dart` | `_loadFeedFavorites` 空结果时先检查本地数据库,本地有则兜底返回,避免误降级 Legacy API |
#### 卡片按钮重设计(方案 C · 液态玻璃)
**`lib/features/home/presentation/home_sentence_card.dart`**
- 移除分享 icon 按钮(分享功能保留在左滑手势和详情面板入口)
- 移除 `_shareAsImage` / `_shareAsText` / `_isSharing` / `_repaintKey` 及相关 importdart:io / dart:ui / path_provider / share_plus / kIsWeb
- 新增 `LiquidGlassActionButton` 组件iOS 26 Liquid Glass 风格):
- 长方形 icon + 文字 按钮
- 未激活:半透明玻璃材质 + 微高光 + 中性色
- 已激活:渐变填充 + 内嵌高光 + 投射阴影 + 白色 icon/文字
- 点击触发弹跳动画AnimationController + easeInOutBack 曲线)
- 自动跟随 `AppThemeExtension` 切换明/暗/AMOLED/主色
- 点赞按钮:❤️ + 数字(用 `NumberFormatter.formatCount`),激活态 iOS systemRed 渐变
- 收藏按钮:★ + 文字"收藏"/"已藏"(用 `t.discover.base.favorite/favorited`),激活态 iOS systemOrange 渐变
- 新增 `_ToolIconButton` 抽出拼音/翻译/TTS 工具图标共用组件,添加无障碍语义标签
- HTML 设计原型:`docs/prototypes/sentence_card_redesign.html`4 种方案 + 动态主题切换预览)
#### 验证
- `flutter analyze` 通过0 error / 0 warning
- 4 个修改文件的 IDE 诊断全部通过
#### 不足与扩展建议
详见本次会话末尾的「审计验收」总结。
---
## [v6.139.0] - 2026-06-26
### 🧹 macOS 编译验证 + 静态分析清理