docs: add calendar invite sheet implementation plan
This commit is contained in:
@@ -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<UserBasicInfo> 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<String, dynamic> 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<UserBasicInfo> 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<UsersApi>();
|
||||
sl.registerLazySingleton(() => UsersApi(sl<IApiClient>()));
|
||||
```
|
||||
|
||||
**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<UsersApi>();
|
||||
```
|
||||
|
||||
**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<void> _showCalendarInviteSheet(InboxMessageResponse message) async {
|
||||
@@ -387,8 +391,6 @@ Future<void> _showCalendarInviteSheet(InboxMessageResponse message) async {
|
||||
|
||||
**Step 4: 添加已读日历邀请弹窗方法**
|
||||
|
||||
在类中添加:
|
||||
|
||||
```dart
|
||||
Future<void> _showCalendarInviteReadOnlySheet(InboxMessageResponse message) async {
|
||||
final itemId = message.scheduleItemId;
|
||||
@@ -419,9 +421,55 @@ Future<void> _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<void>(
|
||||
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:**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user