## 收藏(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>
143 lines
4.3 KiB
Dart
143 lines
4.3 KiB
Dart
import 'package:networks_sdk/networks_sdk.dart';
|
||
|
||
import 'package:im_app/core/foundation/api_paths.dart';
|
||
import 'package:im_app/domain/entities/call_log.dart';
|
||
|
||
// ── 通话记录 DTO ──────────────────────────────────────────────────────────────
|
||
|
||
/// 服务端单条通话记录 DTO
|
||
///
|
||
/// 字段对齐 `im-client-im-dev` Call.fromJson:
|
||
/// - `rtc_channel_id` → [id](主键,String)
|
||
/// - `inviter_id` / `caller_id` → [callerId]
|
||
/// - `video_call` → [videoCall](0=语音, 1=视频)
|
||
/// - `status` = 3/4/5/6 → 未接来电(配合客户端侧判断)
|
||
class CallLogDto {
|
||
final String id;
|
||
final int? callerId;
|
||
final int? receiverId;
|
||
final int? chatId;
|
||
final int? duration;
|
||
final int? videoCall;
|
||
final int? createdAt;
|
||
final int? updatedAt;
|
||
final int? endedAt;
|
||
final int? status;
|
||
final int? isDeleted;
|
||
final int? deletedAt;
|
||
final int? isRead;
|
||
|
||
const CallLogDto({
|
||
required this.id,
|
||
this.callerId,
|
||
this.receiverId,
|
||
this.chatId,
|
||
this.duration,
|
||
this.videoCall,
|
||
this.createdAt,
|
||
this.updatedAt,
|
||
this.endedAt,
|
||
this.status,
|
||
this.isDeleted,
|
||
this.deletedAt,
|
||
this.isRead,
|
||
});
|
||
|
||
factory CallLogDto.fromJson(Map<String, dynamic> json) => CallLogDto(
|
||
id: (json['rtc_channel_id'] ?? json['channel_id'] ?? json['id'] ?? '')
|
||
.toString(),
|
||
callerId:
|
||
(json['inviter_id'] ?? json['caller_id'] as num?)?.toInt(),
|
||
receiverId: (json['receiver_id'] as num?)?.toInt(),
|
||
chatId: (json['chat_id'] as num?)?.toInt(),
|
||
duration: (json['duration'] as num?)?.toInt(),
|
||
videoCall: (json['video_call'] as num?)?.toInt(),
|
||
createdAt: (json['created_at'] as num?)?.toInt(),
|
||
updatedAt: (json['updated_at'] as num?)?.toInt(),
|
||
endedAt: (json['ended_at'] as num?)?.toInt(),
|
||
status: (json['status'] as num?)?.toInt(),
|
||
isDeleted: (json['is_deleted'] as num?)?.toInt(),
|
||
deletedAt: (json['deleted_at'] as num?)?.toInt(),
|
||
isRead: (json['is_read'] as num?)?.toInt(),
|
||
);
|
||
|
||
CallLog toEntity() => CallLog(
|
||
id: id,
|
||
callerId: callerId,
|
||
receiverId: receiverId,
|
||
chatId: chatId,
|
||
duration: duration,
|
||
videoCall: videoCall,
|
||
createdAt: createdAt,
|
||
updatedAt: updatedAt,
|
||
endedAt: endedAt,
|
||
status: status,
|
||
isDeleted: isDeleted,
|
||
deletedAt: deletedAt,
|
||
isRead: isRead,
|
||
);
|
||
}
|
||
|
||
// ── FetchCallLogsResponse ────────────────────────────────────────────────────
|
||
|
||
class FetchCallLogsResponse {
|
||
final List<CallLogDto> records;
|
||
|
||
const FetchCallLogsResponse({required this.records});
|
||
|
||
factory FetchCallLogsResponse.fromJson(Map<String, dynamic> json) {
|
||
final list = json['list'] ?? json['records'] ?? json['data'] ?? [];
|
||
return FetchCallLogsResponse(
|
||
records: (list as List)
|
||
.map((item) => CallLogDto.fromJson(item as Map<String, dynamic>))
|
||
.toList(),
|
||
);
|
||
}
|
||
}
|
||
|
||
// ── FetchCallLogsRequest ─────────────────────────────────────────────────────
|
||
|
||
/// POST /app/api/call/records — 拉取通话记录
|
||
///
|
||
/// [startFrom] Unix 时间戳(秒),增量拉取用;0 表示全量。
|
||
/// [status] -1 = 全部状态。
|
||
class FetchCallLogsRequest
|
||
extends ApiRequestable<FetchCallLogsResponse> {
|
||
final int startFrom;
|
||
final int status;
|
||
|
||
const FetchCallLogsRequest({
|
||
this.startFrom = 0,
|
||
this.status = -1,
|
||
});
|
||
|
||
@override
|
||
String get path => ApiPaths.callRecords;
|
||
|
||
@override
|
||
HttpMethod get method => HttpMethod.post;
|
||
|
||
@override
|
||
Map<String, dynamic> get parameters => {
|
||
'start_from': startFrom,
|
||
'status': status,
|
||
};
|
||
|
||
@override
|
||
FetchCallLogsResponse? decodeResponse(dynamic response) {
|
||
final data = (response as dynamic).data;
|
||
if (data == null) return FetchCallLogsResponse(records: []);
|
||
if (data is List) {
|
||
return FetchCallLogsResponse(
|
||
records: data
|
||
.map((item) => CallLogDto.fromJson(item as Map<String, dynamic>))
|
||
.toList(),
|
||
);
|
||
}
|
||
if (data is Map<String, dynamic>) {
|
||
return FetchCallLogsResponse.fromJson(data);
|
||
}
|
||
return FetchCallLogsResponse(records: []);
|
||
}
|
||
}
|