From c76f2d1301d269b2915bc85879047984ce40cc29 Mon Sep 17 00:00:00 2001 From: qzl Date: Wed, 11 Mar 2026 20:54:35 +0800 Subject: [PATCH] docs: add calendar invite sheet implementation plan --- .../plans/2026-03-11-calendar-invite-sheet.md | 179 +++++++++++++----- 1 file changed, 131 insertions(+), 48 deletions(-) diff --git a/docs/plans/2026-03-11-calendar-invite-sheet.md b/docs/plans/2026-03-11-calendar-invite-sheet.md index 01d45d3..5da4335 100644 --- a/docs/plans/2026-03-11-calendar-invite-sheet.md +++ b/docs/plans/2026-03-11-calendar-invite-sheet.md @@ -2,12 +2,12 @@ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. -**Goal:** 优化日历邀请消息弹窗,显示完整信息(发送者名称 + 日历标题),复用公共弹窗组件 +**Goal:** 优化日历邀请消息弹窗,显示完整信息(发送者名称 + 日历标题),使用公共弹窗组件替代所有旧弹窗代码 **Architecture:** - 后端新增用户信息查询接口 - 前端创建公共弹窗组件 MessageActionSheet -- 日历邀请通过 scheduleItemId 获取标题,通过 senderId 获取发送者名称 +- 删除所有旧的弹窗代码(好友请求、日历邀请),统一使用公共组件 **Tech Stack:** Flutter (Dart), FastAPI (Python) @@ -22,11 +22,7 @@ **Step 1: 添加 repository 方法** -修改 `backend/src/v1/users/repository.py`,在 `UserRepository` 和 `SQLAlchemyUserRepository` 中添加: - -```python -async def get_by_user_id(self, user_id: UUID) -> Profile | None: ... -``` +修改 `backend/src/v1/users/repository.py`,在 `UserRepository` 和 `SQLAlchemyUserRepository` 中已有 `get_by_user_id` 方法,确认存在。 **Step 2: 添加 service 方法** @@ -34,6 +30,8 @@ async def get_by_user_id(self, user_id: UUID) -> Profile | None: ... ```python async def get_user_by_id(self, user_id: UUID) -> UserBasicInfo: + from v1.friendships.schemas import UserBasicInfo + profile = await self._repository.get_by_user_id(user_id) if not profile: raise HTTPException(status_code=404, detail="User not found") @@ -75,26 +73,24 @@ git add backend/src/v1/users/ && git commit -m "feat(users): add get user by id **Files:** - Modify: `apps/lib/features/users/data/users_api.dart` +- Modify: `apps/lib/core/di/injection.dart` -**Step 1: 添加 getById 方法** +**Step 1: 添加 UserBasicInfo 类和 getById 方法** -修改 `apps/lib/features/users/data/users_api.dart`,添加: +修改 `apps/lib/features/users/data/users_api.dart`: ```dart -class UsersApi { - // ... existing code - - Future getById(String userId) async { - final response = await _client.get('$_prefix/$userId'); - return UserBasicInfo.fromJson(response.data); - } -} - class UserBasicInfo { final String id; final String username; final String? avatarUrl; + UserBasicInfo({ + required this.id, + required this.username, + this.avatarUrl, + }); + factory UserBasicInfo.fromJson(Map json) { return UserBasicInfo( id: json['id'] as String, @@ -103,6 +99,20 @@ class UserBasicInfo { ); } } + +class UsersApi { + final IApiClient _client; + static const _prefix = '/api/v1/users'; + + UsersApi(this._client); + + // ... existing methods + + Future getById(String userId) async { + final response = await _client.get('$_prefix/$userId'); + return UserBasicInfo.fromJson(response.data); + } +} ``` **Step 2: 注册到 DI** @@ -110,7 +120,7 @@ class UserBasicInfo { 修改 `apps/lib/core/di/injection.dart`,添加: ```dart -sl(); +sl.registerLazySingleton(() => UsersApi(sl())); ``` **Step 3: 运行 flutter analyze** @@ -122,7 +132,7 @@ cd apps && flutter analyze lib/features/users/ **Step 4: 提交** ```bash -git add apps/lib/features/users/ apps/lib/core/di/injection.dart && git commit -m "feat(users): add getById API method" +git add apps/lib/features/users/ apps/lib/core/di/injection.dart && git commit -m "feat(users): add getById API and UserBasicInfo" ``` --- @@ -131,7 +141,6 @@ git add apps/lib/features/users/ apps/lib/core/di/injection.dart && git commit - **Files:** - Create: `apps/lib/features/messages/ui/widgets/message_action_sheet.dart` -- Modify: `apps/lib/features/messages/ui/screens/message_invite_list_screen.dart` **Step 1: 创建弹窗组件** @@ -228,10 +237,8 @@ class MessageActionSheet extends StatelessWidget { ), ), ], - if (isReadOnly) ...[ - const SizedBox(height: 24), - ] else ...[ - const SizedBox(height: 24), + const SizedBox(height: 24), + if (!isReadOnly) ...[ Row( children: [ Expanded( @@ -257,7 +264,7 @@ class MessageActionSheet extends StatelessWidget { ], ), ], - const SizedBox(height: AppSpacing.xl), + SizedBox(height: MediaQuery.of(context).padding.bottom + 12), ], ), ); @@ -279,15 +286,14 @@ git add apps/lib/features/messages/ui/widgets/message_action_sheet.dart && git c --- -### Task 4: 重构日历邀请弹窗使用公共组件 +### Task 4: 重构消息列表页面,使用公共组件并删除旧代码 **Files:** - Modify: `apps/lib/features/messages/ui/screens/message_invite_list_screen.dart` -- Modify: `apps/lib/features/messages/ui/widgets/calendar_message_card.dart` -**Step 1: 添加依赖注入** +**Step 1: 添加依赖和字段** -修改 `message_invite_list_screen.dart`,添加: +在文件顶部添加: ```dart import '../../../users/data/users_api.dart'; @@ -306,9 +312,7 @@ late final UsersApi _usersApi; _usersApi = sl(); ``` -**Step 2: 添加获取信息方法** - -在类中添加: +**Step 2: 添加获取日历邀请信息方法** ```dart Future<(String calendarTitle, String senderName)?> _getCalendarInviteInfo( @@ -327,9 +331,9 @@ Future<(String calendarTitle, String senderName)?> _getCalendarInviteInfo( } ``` -**Step 3: 修改 _showCalendarInviteSheet 方法** +**Step 3: 替换日历邀请弹窗方法** -修改 `_showCalendarInviteSheet`,使用公共组件: +删除旧的 `_showCalendarInviteSheet` 方法,替换为: ```dart Future _showCalendarInviteSheet(InboxMessageResponse message) async { @@ -387,8 +391,6 @@ Future _showCalendarInviteSheet(InboxMessageResponse message) async { **Step 4: 添加已读日历邀请弹窗方法** -在类中添加: - ```dart Future _showCalendarInviteReadOnlySheet(InboxMessageResponse message) async { final itemId = message.scheduleItemId; @@ -419,9 +421,55 @@ Future _showCalendarInviteReadOnlySheet(InboxMessageResponse message) asyn } ``` -**Step 5: 修改 _handleMessageTap 方法** +**Step 5: 替换好友请求弹窗方法** -修改日历邀请部分的处理逻辑: +删除旧的 `_showFriendRequestReadOnlySheet` 和 `_showFriendRequestActionSheet` 方法,替换为: + +```dart +void _showFriendRequestSheet(MessageWithFriend item, {bool isReadOnly = false}) { + final message = item.message; + final friendRequest = item.friendRequest; + if (friendRequest == null) return; + + final title = '${friendRequest.sender.username} 请求添加您为好友'; + final description = message.content; + final statusText = isReadOnly + ? (friendRequest.status == 'accepted' + ? '已接受' + : friendRequest.status == 'rejected' + ? '已拒绝' + : '已处理') + : null; + + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (ctx) => MessageActionSheet( + title: title, + description: description, + statusText: statusText, + isReadOnly: isReadOnly, + icon: Icons.person_add_outlined, + iconColor: AppColors.emerald500, + onAccept: isReadOnly + ? null + : () async { + await _processFriendRequest(item, accept: true); + }, + onDecline: isReadOnly + ? null + : () async { + await _processFriendRequest(item, accept: false); + }, + ), + ); +} +``` + +**Step 6: 修改 _handleMessageTap 方法** + +修改为调用新的统一方法: ```dart case InboxMessageType.calendar: @@ -433,7 +481,6 @@ case InboxMessageType.calendar: if (message.status.value == 'pending') { await _showCalendarInviteSheet(message); } else { - // 已读:显示弹窗,点击跳转日历 await _showCalendarInviteReadOnlySheet(message); if (message.scheduleItemId != null && context.mounted) { context.push('/calendar/events/${message.scheduleItemId}'); @@ -445,23 +492,57 @@ case InboxMessageType.calendar: } } return; +case InboxMessageType.friendRequest: + if (item.friendRequest == null) { + Toast.show(context, '发送者信息加载失败,请下拉重试', type: ToastType.error); + return; + } + _showFriendRequestSheet(item, isReadOnly: message.isRead); + return; ``` -**Step 6: 运行 flutter analyze** +**Step 7: 删除旧的 _FriendRequestSheet 类** + +删除文件末尾的整个 `_FriendRequestSheet` 类(约605-749行)。 + +**Step 8: 运行 flutter analyze** ```bash cd apps && flutter analyze lib/features/messages/ui/screens/message_invite_list_screen.dart ``` -**Step 7: 提交** +**Step 9: 提交** ```bash -git add apps/lib/features/messages/ && git commit -m "refactor(messages): use MessageActionSheet for calendar invites" +git add apps/lib/features/messages/ && git commit -m "refactor(messages): use MessageActionSheet for all message types" ``` --- -### Task 5: 验证和测试 +### Task 5: 删除日历消息卡片中的旧弹窗代码 + +**Files:** +- Modify: `apps/lib/features/messages/ui/widgets/calendar_message_card.dart` + +**Step 1: 修改 CalendarInviteCard** + +CalendarInviteCard 是用于列表展示的卡片,不需要显示弹窗。检查是否有不必要的硬编码,如果有则清理。 + +**Step 2: 运行 flutter analyze** + +```bash +cd apps && flutter analyze lib/features/messages/ui/widgets/calendar_message_card.dart +``` + +**Step 3: 提交** + +```bash +git add apps/lib/features/calendar_message_card.dart && git commit/messages/ui/widgets -f "chore(messages): clean up calendar message card" +``` + +--- + +### Task 6: 验证和测试 **Step 1: 运行完整测试** @@ -474,10 +555,11 @@ cd backend && uv run pytest tests/unit/v1/users/ -v 1. 用户 A 发送日历邀请给用户 B 2. 用户 B 打开未读消息,点击日历邀请 -3. 弹窗显示:"XXX 邀请你加入 [日历标题]"(发送者名称 + 日历标题) +3. 弹窗显示:"XXX 邀请你加入 [日历标题]" 4. 点击接受/拒绝 5. 用户 B 打开已读消息,点击日历邀请 -6. 弹窗显示状态标签,点击弹窗外部跳转到日历详情页 +6. 弹窗显示状态标签 +7. 好友请求未读/已读都使用相同弹窗组件 --- @@ -488,8 +570,9 @@ cd backend && uv run pytest tests/unit/v1/users/ -v | 1 | 后端添加用户信息查询接口 `/api/v1/users/{user_id}` | | 2 | 前端添加 UsersApi.getById 方法 | | 3 | 创建公共弹窗组件 MessageActionSheet | -| 4 | 重构日历邀请弹窗使用公共组件,获取发送者名称和日历标题 | -| 5 | 验证测试 | +| 4 | 重构消息列表页面,删除旧弹窗代码,统一使用 MessageActionSheet | +| 5 | 清理日历消息卡片旧代码 | +| 6 | 验证测试 | **Plan complete and saved to `docs/plans/2026-03-11-calendar-invite-sheet.md`. Two execution options:**