refactor(apps): 重构数据层目录结构并新增启动预热编排器

This commit is contained in:
zl-q
2026-03-29 20:26:30 +08:00
parent 33340de8f9
commit 4db9a13bfe
108 changed files with 1653 additions and 1320 deletions
@@ -1,4 +1,4 @@
import 'package:social_app/core/network/i_api_client.dart';
import 'package:social_app/data/network/i_api_client.dart';
class FriendsApi {
final IApiClient _client;
@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:social_app/core/network/i_api_client.dart';
import 'package:social_app/data/models/user_profile.dart';
import 'package:social_app/data/network/i_api_client.dart';
import '../models/user_profile.dart';
class UserBasicInfo {
final String id;
@@ -0,0 +1,63 @@
enum FriendRequestStatus { pending, accepted, rejected }
class FriendUser {
final String id;
final String username;
final String? avatarUrl;
const FriendUser({
required this.id,
required this.username,
required this.avatarUrl,
});
factory FriendUser.fromJson(Map<String, dynamic> json) {
return FriendUser(
id: json['id'] as String,
username: json['username'] as String,
avatarUrl: json['avatar_url'] as String?,
);
}
}
class FriendRequest {
final String id;
final FriendUser sender;
final FriendUser recipient;
final String? content;
final FriendRequestStatus status;
final DateTime createdAt;
const FriendRequest({
required this.id,
required this.sender,
required this.recipient,
required this.content,
required this.status,
required this.createdAt,
});
factory FriendRequest.fromJson(Map<String, dynamic> json) {
return FriendRequest(
id: json['id'] as String,
sender: FriendUser.fromJson(json['sender'] as Map<String, dynamic>),
recipient: FriendUser.fromJson(json['recipient'] as Map<String, dynamic>),
content: json['content'] as String?,
status: _friendRequestStatusFromApi(json['status'] as String),
createdAt: DateTime.parse(json['created_at'] as String),
);
}
}
FriendRequestStatus _friendRequestStatusFromApi(String raw) {
switch (raw) {
case 'pending':
return FriendRequestStatus.pending;
case 'accepted':
return FriendRequestStatus.accepted;
case 'rejected':
return FriendRequestStatus.rejected;
default:
throw StateError('Unsupported friend request status: $raw');
}
}
@@ -0,0 +1,41 @@
class UserProfile {
final String id;
final String username;
final String? phone;
final String? avatarUrl;
final String? bio;
const UserProfile({
required this.id,
required this.username,
this.phone,
this.avatarUrl,
this.bio,
});
factory UserProfile.fromJson(Map<String, dynamic> json) {
return UserProfile(
id: json['id'] as String,
username: json['username'] as String,
phone: json['phone'] as String?,
avatarUrl: json['avatar_url'] as String?,
bio: json['bio'] as String?,
);
}
}
class UserUpdateRequest {
final String? username;
final String? avatarUrl;
final String? bio;
const UserUpdateRequest({this.username, this.avatarUrl, this.bio});
Map<String, dynamic> toJson() {
return {
if (username != null) 'username': username,
if (avatarUrl != null) 'avatar_url': avatarUrl,
if (bio != null) 'bio': bio,
};
}
}
@@ -0,0 +1,19 @@
class UserSummary {
final String id;
final String username;
final String? avatarUrl;
const UserSummary({
required this.id,
required this.username,
required this.avatarUrl,
});
factory UserSummary.fromJson(Map<String, dynamic> json) {
return UserSummary(
id: json['id'] as String,
username: json['username'] as String,
avatarUrl: json['avatar_url'] as String?,
);
}
}
@@ -0,0 +1,142 @@
import '../../../../data/network/i_api_client.dart';
import '../../../../data/cache/cache_policy.dart';
import '../../../../data/cache/cached_repository.dart';
import '../models/friend_request.dart';
abstract class FriendRepository {
Future<List<FriendUser>> getFriends();
Future<FriendRequest> getRequestById(String friendshipId);
Future<Map<String, FriendRequest>> getRequestsByIds(
List<String> friendshipIds,
);
Future<FriendRequest> acceptRequest(String friendshipId);
Future<FriendRequest> declineRequest(String friendshipId);
}
class FriendRepositoryImpl extends CachedRepository<List<FriendUser>>
implements FriendRepository {
final IApiClient _apiClient;
static const _prefix = '/api/v1/friends';
FriendRepositoryImpl({required IApiClient apiClient, required super.store})
: _apiClient = apiClient,
super(
policy: const CachePolicy(
softTtl: Duration(seconds: 30),
hardTtl: Duration(minutes: 5),
minRefreshInterval: Duration(seconds: 15),
),
encodeValue: _encodeFriendUsers,
decodeValue: _decodeFriendUsers,
);
@override
Future<List<FriendUser>> getFriends() async {
return getOrLoad(key: _friendsKey, loadFromRemote: _loadFriendsFromRemote);
}
Future<List<FriendUser>> _loadFriendsFromRemote() async {
final response = await _apiClient.get<List<dynamic>>(_prefix);
final data = response.data;
if (data == null) {
throw StateError('Invalid getFriends response: empty payload');
}
return data
.map((item) => item as Map<String, dynamic>)
.map(
(item) => FriendUser.fromJson(item['friend'] as Map<String, dynamic>),
)
.toList(growable: false);
}
@override
Future<FriendRequest> getRequestById(String friendshipId) async {
return _loadRequestById(friendshipId);
}
Future<FriendRequest> _loadRequestById(String friendshipId) async {
final response = await _apiClient.get<Map<String, dynamic>>(
'$_prefix/requests/$friendshipId',
);
final data = response.data;
if (data == null) {
throw StateError('Invalid getRequestById response: empty payload');
}
return FriendRequest.fromJson(data);
}
@override
Future<Map<String, FriendRequest>> getRequestsByIds(
List<String> friendshipIds,
) async {
if (friendshipIds.isEmpty) {
return const <String, FriendRequest>{};
}
final pairs = await Future.wait(
friendshipIds.map((id) async {
final request = await getRequestById(id);
return MapEntry(id, request);
}),
);
return Map<String, FriendRequest>.fromEntries(pairs);
}
@override
Future<FriendRequest> acceptRequest(String friendshipId) async {
final response = await _apiClient.post<Map<String, dynamic>>(
'$_prefix/requests/$friendshipId/accept',
);
final data = response.data;
if (data == null) {
throw StateError('Invalid acceptRequest response: empty payload');
}
final request = FriendRequest.fromJson(data);
await _invalidateFriendCaches(friendshipId);
return request;
}
@override
Future<FriendRequest> declineRequest(String friendshipId) async {
final response = await _apiClient.post<Map<String, dynamic>>(
'$_prefix/requests/$friendshipId/decline',
);
final data = response.data;
if (data == null) {
throw StateError('Invalid declineRequest response: empty payload');
}
final request = FriendRequest.fromJson(data);
await _invalidateFriendCaches(friendshipId);
return request;
}
Future<void> _invalidateFriendCaches(String friendshipId) {
final _ = friendshipId;
return removeCacheKey(_friendsKey);
}
static const _friendsKey = 'friends:list';
static Object? _encodeFriendUsers(List<FriendUser> users) {
return users.map(_encodeFriendUser).toList(growable: false);
}
static List<FriendUser> _decodeFriendUsers(Object? raw) {
if (raw is! List) {
throw const FormatException('Invalid cached friend list payload');
}
return raw
.whereType<Map>()
.map((item) => FriendUser.fromJson(Map<String, dynamic>.from(item)))
.toList(growable: false);
}
static Map<String, Object?> _encodeFriendUser(FriendUser user) {
return <String, Object?>{
'id': user.id,
'username': user.username,
'avatar_url': user.avatarUrl,
};
}
}
@@ -0,0 +1,36 @@
import '../../../../data/network/i_api_client.dart';
import '../models/user_summary.dart';
abstract class UserRepository {
Future<UserSummary> getById(String userId);
Future<UserSummary> getMe();
}
class UserRepositoryImpl implements UserRepository {
final IApiClient _apiClient;
static const _prefix = '/api/v1/users';
UserRepositoryImpl(this._apiClient);
@override
Future<UserSummary> getById(String userId) async {
final response = await _apiClient.get<Map<String, dynamic>>(
'$_prefix/$userId',
);
final user = response.data;
if (user == null) {
throw StateError('Invalid getById response: empty payload');
}
return UserSummary.fromJson(user);
}
@override
Future<UserSummary> getMe() async {
final response = await _apiClient.get<Map<String, dynamic>>('$_prefix/me');
final user = response.data;
if (user == null) {
throw StateError('Invalid getMe response: empty payload');
}
return UserSummary.fromJson(user);
}
}
@@ -1,4 +1,4 @@
import '../../../../data/models/user_profile.dart';
import '../models/user_profile.dart';
abstract class UsersRepository {
Future<UserProfile> getMe();
@@ -1,6 +1,6 @@
import 'users_api.dart';
import '../apis/users_api.dart';
import 'users_repository.dart';
import '../../../../data/models/user_profile.dart';
import '../models/user_profile.dart';
class UsersRepositoryImpl implements UsersRepository {
final UsersApi _api;
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../data/models/user_profile.dart';
import '../../data/models/user_profile.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/toast/index.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
import '../../../contacts/data/friends_api.dart';
import '../../../contacts/data/users/users_api.dart';
import '../../../contacts/data/apis/friends_api.dart';
import '../../../contacts/data/apis/users_api.dart';
class ContactsScreen extends StatefulWidget {
const ContactsScreen({super.key});