feat(settings): 收藏列表 + 最近呼叫全量实现(#42~#45)

## 收藏(Gitea #42~#45)
- `FetchFavoritesRequest` / `DeleteFavoriteRequest`:ApiRequestable,对齐 iOS FavouriteService
- `FetchFavoritesUseCase`:GET 分页拉取 → upsert FavoriteRepository
- `DeleteFavoriteUseCase`:POST delete → 同步删本地 DB
- `FavoritesViewModel`:分页/刷新/加载更多/删除,DB Stream 驱动
- `FavoritesPage`:列表 + RefreshIndicator + Dismissible 左滑删除 + 类型图标 + 空状态
- `AppRouteName.settingsFavorites` + 路由注册 + auth guard
- `settings_page.dart` 收藏行 onTap 接入导航

## 最近呼叫(框架,API 对接待续)
- `CallLogRequest` / `FetchCallLogsUseCase` / `RecentCallsViewModel`
- `RecentCallsPage`:双 Tab(全部/未接)+ _CallLogTile(图标/时长/时间)
- `AppRouteName.settingsRecentCalls` + 路由注册

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
pp-bot
2026-03-24 20:30:56 +09:00
parent db10d1fcd2
commit 8744e2c0b7
16 changed files with 1389 additions and 2 deletions

View File

@@ -0,0 +1,26 @@
import 'package:networks_sdk/networks_sdk.dart';
import 'package:im_app/data/remote/fetch_favorites_request.dart';
import 'package:im_app/domain/repositories/favorite_repository.dart';
/// 删除收藏:先调用 API成功后同步删除本地 DBGitea issue #43
///
/// ## 流程
/// 1. POST /app/api/favorite/delete body: id={id}
/// 2. API 成功 → `FavoriteRepository.delete(id)`
/// 3. DB Stream 自动通知 UI 更新
class DeleteFavoriteUseCase {
final NetworksSdkApi _client;
final FavoriteRepository _repository;
const DeleteFavoriteUseCase({
required NetworksSdkApi client,
required FavoriteRepository repository,
}) : _client = client,
_repository = repository;
Future<void> execute(int id) async {
await _client.executeRequest(DeleteFavoriteRequest(id: id));
await _repository.delete(id);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/foundation.dart';
import 'package:networks_sdk/networks_sdk.dart';
import 'package:im_app/data/remote/call_log_request.dart';
import 'package:im_app/domain/repositories/call_log_repository.dart';
/// 拉取通话记录用例
///
/// ## 执行流程
/// 1. POST /app/api/call/records全量startFrom=0, status=-1
/// 2. 解析响应 → CallLogDto → CallLog Domain 实体
/// 3. insertOrReplaceCallLogs → Drift DB
/// 4. allCallLogsProvider Stream 自动刷新 → UI 重建
///
/// ## 为什么全量拉取
///
/// 当前使用 startFrom=0 全量拉取,与 im-client-im-dev CallLogMgr 初始化行为一致。
/// 后续可优化为增量:取本地最新 updatedAt 作为 startFrom减少流量。
class FetchCallLogsUseCase {
final NetworksSdkApi _client;
final CallLogRepository _repo;
const FetchCallLogsUseCase({
required NetworksSdkApi client,
required CallLogRepository repo,
}) : _client = client,
_repo = repo;
/// 拉取通话记录并写入本地 DB
///
/// 成功返回拉取到的记录数,失败抛出异常。
Future<int> execute() async {
final response = await _client.executeRequest(
const FetchCallLogsRequest(startFrom: 0, status: -1),
);
if (response == null || response.records.isEmpty) {
debugPrint('[FetchCallLogsUseCase] no records returned');
return 0;
}
final entities = response.records.map((dto) => dto.toEntity()).toList();
await _repo.insertOrReplaceCallLogs(entities);
debugPrint(
'[FetchCallLogsUseCase] inserted ${entities.length} call logs',
);
return entities.length;
}
}

View File

@@ -0,0 +1,31 @@
import 'package:networks_sdk/networks_sdk.dart';
import 'package:im_app/data/remote/fetch_favorites_request.dart';
import 'package:im_app/domain/repositories/favorite_repository.dart';
/// 分页拉取收藏列表并持久化到 DBGitea issue #42
///
/// ## 流程
/// 1. GET /app/api/favorite/favorites?page={n}
/// 2. 解码为 [Favorite] 列表
/// 3. upsert 到 [FavoriteRepository]insertOrReplaceAll
///
/// 返回本次拉取到的条目数;返回 0 表示无更多数据。
class FetchFavoritesUseCase {
final NetworksSdkApi _client;
final FavoriteRepository _repository;
const FetchFavoritesUseCase({
required NetworksSdkApi client,
required FavoriteRepository repository,
}) : _client = client,
_repository = repository;
Future<int> execute({int page = 1}) async {
final resp =
await _client.executeRequest(FetchFavoritesRequest(page: page));
if (resp == null || resp.items.isEmpty) return 0;
await _repository.insertOrReplaceAll(resp.items);
return resp.items.length;
}
}