新增 Doc/recent_calls_architecture.md: - 功能范围、目录结构、数据流图 - API 端点 /app/api/call/records 字段映射表 - 通话状态枚举(0-6)与未接来电判断逻辑 - ViewModel 设计、UI 结构、路由说明 - 设计决策(无用户名映射、全量拉取、离线可用) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.5 KiB
4.5 KiB
最近呼叫(Recent Calls)— 架构文档
对应 Gitea issues #46-#48(内部编号 #42-#44) 参考实现:
im-client-im-devlib/views/call_log/ + lib/managers/call_log_mgr.dart
1. 功能范围
| Issue | 功能 | 状态 |
|---|---|---|
| #42 | 最近呼叫列表页 UI(全部/未接 Tab + CallLogTile) | ✅ 已实现 |
| #43 | 通话记录 API 接入(FetchCallLogsRequest + UseCase) | ✅ 已实现 |
| #44 | RecentCallsViewModel + 路由接入 | ✅ 已实现 |
2. 目录结构
features/settings/
├── di/
│ └── settings_providers.dart # 新增 fetchCallLogsUseCaseProvider
├── presentation/
│ └── recent_calls_view_model.dart # RecentCallsViewModel + RecentCallsState
├── usecases/
│ └── fetch_call_logs_usecase.dart # POST /app/api/call/records → DB
└── view/
└── recent_calls_page.dart # RecentCallsPage + TabBar + _CallLogTile
data/remote/
└── call_log_request.dart # FetchCallLogsRequest + CallLogDto
app/router/
├── app_route_name.dart # 新增 settingsRecentCalls
├── app_router.dart # 新增 GoRoute
└── guards/auth_guard.dart # 新增 case settingsRecentCalls
3. 数据流
RecentCallsPage (build)
└─ ref.watch(allCallLogsProvider) ← DB Stream(实时)
└─ RecentCallsViewModel.loadCallLogs()
└─ FetchCallLogsUseCase.execute()
└─ POST /app/api/call/records (start_from=0, status=-1)
├─ 响应 List<CallLogDto> → CallLog Domain
└─ CallLogRepository.insertOrReplaceCallLogs()
└─ Drift DB → allCallLogsProvider 自动刷新 → UI
进入页面:
└─ RecentCallsViewModel.markAllRead()
└─ CallLogRepository.markAllAsRead()
4. API 端点
| 操作 | Method | Path | 参数 |
|---|---|---|---|
| 拉取通话记录 | POST | /app/api/call/records |
start_from (int, 时间戳), status (-1=全部) |
响应字段映射
| 服务端字段 | Dart 字段 | 类型 |
|---|---|---|
rtc_channel_id |
id (String) |
String |
inviter_id / caller_id |
callerId |
int? |
receiver_id |
receiverId |
int? |
chat_id |
chatId |
int? |
duration |
duration |
int? (秒) |
video_call |
videoCall |
int? (0=语音, 1=视频) |
created_at |
createdAt |
int? (秒级时间戳) |
updated_at |
updatedAt |
int? |
ended_at |
endedAt |
int? |
status |
status |
int? |
is_read |
isRead |
int? (0/1) |
5. 通话状态枚举
// status 值含义(参考 im-client-im-dev CallEvent)
// 0 = Ringing/Pending
// 1 = Connected
// 2 = Ended(正常结束)
// 3 = Busy(CallOptBusy)
// 4 = Cancelled(CallOptCancel)
// 5 = Timeout(CallTimeOut)
// 6 = Declined(CallBusy)
// 未接来电:callerId != currentUid AND status in {3, 4, 5, 6}
6. ViewModel 设计
class RecentCallsState {
final int tabIndex; // 0=全部, 1=未接来电
final bool isLoading;
final String? error;
}
class RecentCallsViewModel extends Notifier<RecentCallsState> {
void setTab(int index) // 切换 Tab
Future<void> loadCallLogs() // 拉取 + 写入 DB
Future<void> markAllRead() // 标记已读
}
7. UI 结构
RecentCallsPage
Scaffold
AppBar: 「最近呼叫」
Column
TabBar(全部 / 未接来电)
Expanded
TabBarView
AllCallsTab → allCallLogsProvider 全部
MissedTab → 按 isMissed 过滤
_CallLogTile
ListTile
leading: _CallTypeIcon(电话/摄像头/红色, 40pt 圆形)
title: 对方 UID 显示("@J{uid}")
subtitle: 通话类型文字 + 时长
trailing: 相对时间
未接来电判断:callerId != currentUid AND status in {3,4,5,6}
8. 路由
/settings/recent-calls → RecentCallsPage(parentNavigatorKey=_rootKey)
auth_guard.dart switch 已补充 settingsRecentCalls case。
9. 设计决策
- 无用户名映射:当前 CallLog 实体只有 UID,无昵称。显示
@J{uid}占位, 后续可接入联系人 Repository 做名称查询。 - 增量拉取策略:当前实现
start_from=0(全量),首次打开可能慢; 后续可从本地最新updatedAt做增量同步(参考 CallLogMgr.loadRemoteCallLog)。 - 离线可用:数据先写 DB,UI 监听
allCallLogsProvider(DB Stream), 无网络时仍能展示缓存记录。