Files
social-app/docs/plans/2026-03-12-home-composer-redesign-implementation-plan.md
T

276 lines
9.7 KiB
Markdown
Raw Normal View History

# Home Composer Redesign Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 重做 Home 输入组件,统一为单胶囊容器并稳定语音长按交互,消除布局漂移,同时保持 `+` 与发送等既有业务逻辑不变。
**Architecture:** 采用“单容器双模式”方案:`HomeScreen` 继续持有业务状态与动作,新增 `HomeComposer` 专注 UI 与手势分发;中间区域用受控状态在文本/按住说话/录音中/识别中之间切换,外层高度固定。录音提示与声波动画都内聚在主容器内部渲染,避免额外布局占位。
**Tech Stack:** Flutter, flutter_bloc, lucide_icons, design tokens (`AppColors`/`AppSpacing`/`AppRadius`), widget test
---
### Task 1: 建立 HomeComposer 组件骨架与参数契约
**Files:**
- Create: `apps/lib/features/home/ui/widgets/home_composer.dart`
- Modify: `apps/lib/features/home/ui/screens/home_screen.dart`
- Test: `apps/test/features/home/ui/widgets/home_composer_test.dart`
**Step 1: 写失败测试(渲染结构)**
```dart
testWidgets('renders one unified rounded composer container', (tester) async {
// pump HomeComposer with minimum required callbacks/states
// expect: one root container, plus button, center content slot, right action slot
});
```
**Step 2: 运行测试确认失败**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "renders one unified rounded composer container"`
Expected: FAIL`HomeComposer` 未定义或结构不匹配)
**Step 3: 写最小实现**
```dart
class HomeComposer extends StatelessWidget {
const HomeComposer({
super.key,
required this.isHoldToSpeakMode,
required this.isRecording,
required this.isTranscribing,
required this.hasMessage,
required this.isWaitingAgent,
required this.onTapPlus,
required this.onTapRightAction,
required this.centerChild,
});
// unified capsule container with left/center/right slots
}
```
**Step 4: 再跑测试确认通过**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "renders one unified rounded composer container"`
Expected: PASS
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/widgets/home_composer.dart apps/lib/features/home/ui/screens/home_screen.dart apps/test/features/home/ui/widgets/home_composer_test.dart
git commit -m "refactor: extract unified home composer container"
```
### Task 2: 完成右侧图标状态映射(activity/keyboard/send/stop
**Files:**
- Modify: `apps/lib/features/home/ui/widgets/home_composer.dart`
- Modify: `apps/lib/features/home/ui/screens/home_screen.dart`
- Test: `apps/test/features/home/ui/widgets/home_composer_test.dart`
**Step 1: 写失败测试(图标状态机)**
```dart
testWidgets('right action icon follows state priority', (tester) async {
// waiting > hasMessage > holdToSpeakMode > textMode
// expect LucideIcons.square/send/keyboard/activity respectively
});
```
**Step 2: 运行测试确认失败**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "right action icon follows state priority"`
Expected: FAIL(图标选择逻辑尚未完整实现)
**Step 3: 最小实现图标决策**
```dart
IconData resolveRightIcon(...) {
if (isWaitingAgent) return LucideIcons.square;
if (hasMessage) return LucideIcons.send;
return isHoldToSpeakMode ? LucideIcons.keyboard : LucideIcons.activity;
}
```
**Step 4: 再跑测试确认通过**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "right action icon follows state priority"`
Expected: PASS
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/widgets/home_composer.dart apps/lib/features/home/ui/screens/home_screen.dart apps/test/features/home/ui/widgets/home_composer_test.dart
git commit -m "refactor: stabilize composer right action icon mapping"
```
### Task 3: 实现中间区域双模式替换并固定高度
**Files:**
- Modify: `apps/lib/features/home/ui/widgets/home_composer.dart`
- Modify: `apps/lib/features/home/ui/screens/home_screen.dart`
- Test: `apps/test/features/home/ui/widgets/home_composer_test.dart`
**Step 1: 写失败测试(模式切换不改变高度)**
```dart
testWidgets('composer height remains stable across mode switches', (tester) async {
// measure size in text mode and hold-to-speak mode
// expect equal heights
});
```
**Step 2: 运行测试确认失败**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "composer height remains stable across mode switches"`
Expected: FAIL(当前结构切换时高度波动)
**Step 3: 最小实现(AnimatedSwitcher + fixed constraints**
```dart
SizedBox(
height: composerHeight,
child: AnimatedSwitcher(
duration: switchDuration,
child: isHoldToSpeakMode ? holdToSpeakChild : textInputChild,
),
)
```
**Step 4: 再跑测试确认通过**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "composer height remains stable across mode switches"`
Expected: PASS
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/widgets/home_composer.dart apps/lib/features/home/ui/screens/home_screen.dart apps/test/features/home/ui/widgets/home_composer_test.dart
git commit -m "refactor: keep composer layout stable during mode switch"
```
### Task 4: 实现长按录音交互(开始/上滑取消/松开发送)
**Files:**
- Modify: `apps/lib/features/home/ui/screens/home_screen.dart`
- Modify: `apps/lib/features/home/ui/widgets/home_composer.dart`
- Test: `apps/test/features/home/ui/widgets/home_composer_test.dart`
**Step 1: 写失败测试(录音提示只在 recording)**
```dart
testWidgets('recording hint appears only while recording', (tester) async {
// idle hold-to-speak: no hint
// recording: show "松开发送,上滑取消"
});
```
**Step 2: 运行测试确认失败**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "recording hint appears only while recording"`
Expected: FAIL
**Step 3: 最小实现录音流程映射**
```dart
onLongPressStart => HapticFeedback.lightImpact() + onHoldStart();
onLongPressMoveUpdate => if (dy < threshold) onHoldCancel();
onLongPressEnd => onHoldEnd(autoSend: true);
```
**Step 4: 再跑测试确认通过**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "recording hint appears only while recording"`
Expected: PASS
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/widgets/home_composer.dart apps/lib/features/home/ui/screens/home_screen.dart apps/test/features/home/ui/widgets/home_composer_test.dart
git commit -m "feat: rework hold-to-speak interaction with stable recording state"
```
### Task 5: 视觉重构为轻拟物胶囊(仅 tokens)
**Files:**
- Modify: `apps/lib/features/home/ui/widgets/home_composer.dart`
- Modify: `apps/lib/core/theme/design_tokens.dart`(仅当现有 token 不足时新增)
- Test: `apps/test/features/home/ui/widgets/home_composer_test.dart`
**Step 1: 写失败测试(主容器统一性)**
```dart
testWidgets('plus, center and right action are inside same capsule', (tester) async {
// find one capsule host and verify children are descendants
});
```
**Step 2: 运行测试确认失败**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "plus, center and right action are inside same capsule"`
Expected: FAIL
**Step 3: 最小视觉实现(不硬编码)**
```dart
// use AppColors/AppSpacing/AppRadius and existing shadow tokens
// no hardcoded color/spacing/radius/size
```
**Step 4: 再跑测试确认通过**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart --plain-name "plus, center and right action are inside same capsule"`
Expected: PASS
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/widgets/home_composer.dart apps/lib/core/theme/design_tokens.dart apps/test/features/home/ui/widgets/home_composer_test.dart
git commit -m "refactor: redesign home composer with neumorphic capsule style"
```
### Task 6: 集成回归与文档同步
**Files:**
- Modify: `apps/lib/features/home/ui/screens/home_screen.dart`
- Modify: `docs/runtime/runtime-route.md`(若交互说明有变化)
**Step 1: 运行目标测试文件**
Run: `flutter test test/features/home/ui/widgets/home_composer_test.dart`
Expected: PASS
**Step 2: 运行 Home 相关回归测试(若新增)**
Run: `flutter test test/features/home`
Expected: PASS(若目录存在)
**Step 3: 运行应用侧基础回归**
Run: `flutter test`
Expected: PASS 或仅存在与本改动无关的已知失败
**Step 4: 记录验证结论**
```text
- 输入组件统一容器:通过
- 模式切换稳定:通过
- 录音提示条件:通过
- + 按钮/发送逻辑:通过
```
**Step 5: 小步提交(仅用户明确要求时)**
```bash
git add apps/lib/features/home/ui/screens/home_screen.dart apps/lib/features/home/ui/widgets/home_composer.dart apps/test/features/home/ui/widgets/home_composer_test.dart docs/runtime/runtime-route.md
git commit -m "test: add regression coverage for home composer redesign"
```
## 实施注意事项
- 保持 `HomeScreen` 作为业务状态单一来源,避免在 `HomeComposer` 内部复制业务状态。
- 录音中 (`recording`) 禁止触发模式切换,防止并发手势引发错位。
- 严格遵守 `apps/AGENTS.md`:不硬编码视觉值,必须使用 design tokens。
- 用户反馈统一使用 `Toast.show(...)`,不得引入 `SnackBar`