更新测试案例

This commit is contained in:
Happi (哈比)
2026-03-09 13:03:44 +08:00
parent 56112e1fe3
commit 7b78da86e7
11 changed files with 721 additions and 278 deletions

View File

@@ -3,13 +3,12 @@
/// 全局共享实体,被 auth / chat / contact 等多个 Feature 共用。
/// 纯 Dart 类,零 Flutter / 零网络 / 零 DB 依赖。
///
/// ## 数据流位置
///
/// ## 数据流
/// ```
/// 服务端 JSON
/// → LoginDataResponse DTOdata/remote/login_request.dart
/// → LoginData.toEntity()
/// → ★ User ★ ← 你在这里
/// → User.fromJson() ← 直接从网络创建
/// → ★ User ★ ← 你在这里
/// → userRepo.saveUser(user) ← 可选持久化
/// → ViewModel.state
/// → View 渲染
/// ```
@@ -20,16 +19,32 @@ class User {
final String? profilePic;
final String? profilePicGaussian;
final String? nickname;
final String? depositName;
final int? hasSetDepositName;
final String? contact;
final String? countryCode;
final String? username;
final int? role;
final int? relationship;
final int? friendStatus;
final String? bio;
final String? userAlias;
final int? requestAt;
final int? deletedAt;
final String? email;
final String? recoveryEmail;
final String? username;
final String? bio;
final int? relationship;
final String? userAlias;
final int? channelId;
final int? channelGroupId;
final String? remark;
final String? source;
final int? addIndex;
final int? incomingSoundId;
final int? outgoingSoundId;
final int? notificationSoundId;
final int? sendMessageSoundId;
final int? groupNotificationSoundId;
final String? groupTags;
final String? friendTags;
final String? publicKey;
final int? configBits;
final String? hint;
const User({
@@ -39,19 +54,73 @@ class User {
this.profilePic,
this.profilePicGaussian,
this.nickname,
this.depositName,
this.hasSetDepositName,
this.contact,
this.countryCode,
this.username,
this.role,
this.relationship,
this.friendStatus,
this.bio,
this.userAlias,
this.requestAt,
this.deletedAt,
this.email,
this.recoveryEmail,
this.username,
this.bio,
this.relationship,
this.userAlias,
this.channelId,
this.channelGroupId,
this.remark,
this.source,
this.addIndex,
this.incomingSoundId,
this.outgoingSoundId,
this.notificationSoundId,
this.sendMessageSoundId,
this.groupNotificationSoundId,
this.groupTags,
this.friendTags,
this.publicKey,
this.configBits,
this.hint,
});
/// 直接从网络 JSON 创建 Domain 实体
factory User.fromJson(Map<String, dynamic> json) => User(
uid: json['uid'] as int,
uuid: json['uuid'],
lastOnline: json['last_online'],
profilePic: json['profile_pic'],
profilePicGaussian: json['profile_pic_gaussian'],
nickname: json['nickname'],
depositName: json['deposit_name'],
hasSetDepositName: json['has_set_deposit_name'],
contact: json['contact'],
countryCode: json['country_code'],
username: json['username'],
role: json['role'],
relationship: json['relationship'],
friendStatus: json['friend_status'],
bio: json['bio'],
userAlias: json['user_alias'],
requestAt: json['request_at'],
deletedAt: json['deleted_at'],
email: json['email'],
recoveryEmail: json['recovery_email'],
remark: json['remark'],
source: json['source'],
addIndex: json['__add_index'],
incomingSoundId: json['incoming_sound_id'],
outgoingSoundId: json['outgoing_sound_id'],
notificationSoundId: json['notification_sound_id'],
sendMessageSoundId: json['send_message_sound_id'],
groupNotificationSoundId: json['group_notification_sound_id'],
groupTags: json['group_tags'],
friendTags: json['friend_tags'],
publicKey: json['public_key'],
configBits: json['config_bits'],
hint: json['hint'],
);
/// 仅更新部分字段
User copyWith({
int? uid,
String? uuid,
@@ -59,16 +128,32 @@ class User {
String? profilePic,
String? profilePicGaussian,
String? nickname,
String? depositName,
int? hasSetDepositName,
String? contact,
String? countryCode,
String? username,
int? role,
int? relationship,
int? friendStatus,
String? bio,
String? userAlias,
int? requestAt,
int? deletedAt,
String? email,
String? recoveryEmail,
String? username,
String? bio,
int? relationship,
String? userAlias,
int? channelId,
int? channelGroupId,
String? remark,
String? source,
int? addIndex,
int? incomingSoundId,
int? outgoingSoundId,
int? notificationSoundId,
int? sendMessageSoundId,
int? groupNotificationSoundId,
String? groupTags,
String? friendTags,
String? publicKey,
int? configBits,
String? hint,
}) {
return User(
@@ -78,17 +163,34 @@ class User {
profilePic: profilePic ?? this.profilePic,
profilePicGaussian: profilePicGaussian ?? this.profilePicGaussian,
nickname: nickname ?? this.nickname,
depositName: depositName ?? this.depositName,
hasSetDepositName: hasSetDepositName ?? this.hasSetDepositName,
contact: contact ?? this.contact,
countryCode: countryCode ?? this.countryCode,
username: username ?? this.username,
role: role ?? this.role,
relationship: relationship ?? this.relationship,
friendStatus: friendStatus ?? this.friendStatus,
bio: bio ?? this.bio,
userAlias: userAlias ?? this.userAlias,
requestAt: requestAt ?? this.requestAt,
deletedAt: deletedAt ?? this.deletedAt,
email: email ?? this.email,
recoveryEmail: recoveryEmail ?? this.recoveryEmail,
username: username ?? this.username,
bio: bio ?? this.bio,
relationship: relationship ?? this.relationship,
userAlias: userAlias ?? this.userAlias,
channelId: channelId ?? this.channelId,
channelGroupId: channelGroupId ?? this.channelGroupId,
remark: remark ?? this.remark,
source: source ?? this.source,
addIndex: addIndex ?? this.addIndex,
incomingSoundId: incomingSoundId ?? this.incomingSoundId,
outgoingSoundId: outgoingSoundId ?? this.outgoingSoundId,
notificationSoundId: notificationSoundId ?? this.notificationSoundId,
sendMessageSoundId: sendMessageSoundId ?? this.sendMessageSoundId,
groupNotificationSoundId:
groupNotificationSoundId ?? this.groupNotificationSoundId,
groupTags: groupTags ?? this.groupTags,
friendTags: friendTags ?? this.friendTags,
publicKey: publicKey ?? this.publicKey,
configBits: configBits ?? this.configBits,
hint: hint ?? this.hint,
);
}
}
}

View File

@@ -0,0 +1,28 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:im_app/app/di/db_provider.dart';
import 'package:im_app/data/repositories/user_repository_impl.dart';
import 'package:im_app/domain/entities/user.dart';
import 'package:im_app/domain/repositories/user_repository.dart';
part 'user_provider.g.dart';
// ── Repository ────────────────────────────────────────────────────────────────
@riverpod
UserRepository userRepository(Ref ref) {
return UserRepositoryImpl(ref.watch(storageSdkProvider));
}
// ── Multiple Users ────────────────────────────────────────────────────────────
@riverpod
Stream<List<User>> users(Ref ref, Set<int> uids) {
return ref.watch(userRepositoryProvider).watchUsers(uids.toList());
}
// ── All Users ─────────────────────────────────────────────────────────────────
@riverpod
Stream<List<User>> allUsers(Ref ref) {
return ref.watch(userRepositoryProvider).watchAllUsers();
}

View File

@@ -0,0 +1,69 @@
import 'package:im_app/domain/presentation/di/user_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:im_app/data/local/drift/app_database.dart';
import 'package:im_app/domain/entities/user.dart';
import 'package:im_app/domain/repositories/user_repository.dart';
part 'user_notifier.g.dart';
/// 单个用户状态管理 (family — 每个 uid 独立 notifier)
///
/// ## 用法
/// ```dart
/// // 监听 — 自动重建
/// final userAsync = ref.watch(userNotifierProvider(123));
///
/// // 即时读取,无需 await
/// final user = ref.read(userNotifierProvider(123).notifier).current;
///
/// // 部分更新
/// ref.read(userNotifierProvider(123).notifier).updateFields(
/// UsersCompanion(nickname: Value('New Name')),
/// );
/// ```
@riverpod
class UserNotifier extends _$UserNotifier {
User? _cached;
UserRepository get _repo => ref.watch(userRepositoryProvider);
@override
Future<User?> build(int uid) async {
ref.onDispose(() => _cached = null);
// Stream starts automatically — uid is the family arg
_repo.watchUser(uid).listen((user) {
_cached = user;
state = AsyncData(user);
});
// Return initial DB value
return _repo.getUser(uid);
}
// ── 即时访问,无需 await ──────────────────────────────────────────────────
User? get current => _cached;
// ── 写入 ─────────────────────────────────────────────────────────────────
Future<void> saveUser(User user) async {
await _repo.saveUser(user);
}
Future<void> updateFields(UsersCompanion companion) async {
await _repo.updateFields(uid, companion); // uid from build arg
}
Future<void> updateUser(User user) async {
await _repo.saveUser(user);
}
// ── 删除 ─────────────────────────────────────────────────────────────────
Future<void> deleteUser() async {
await _repo.deleteUser(uid);
_cached = null;
state = const AsyncData(null);
}
}

View File

@@ -0,0 +1,78 @@
import 'package:im_app/data/local/drift/app_database.dart';
import 'package:im_app/domain/entities/user.dart';
/// 用户仓储接口
///
/// ## 职责
/// - StorageSdkApi ↔ Domain User 映射
/// - CRUD 操作(通过 StorageSdkApi不直接接触 DB
/// - 实时监听(单个 / 多个 / 全部)
///
/// ## 数据流
/// ```
/// 写入Domain User → _toCompanion() → StorageSdkApi → DB
/// 读取DB row (DriftUser) → _toEntity() → Domain User
/// 监听DB 变化 → stream → Domain User → UI
/// ```
abstract class UserRepository {
// ── 监听 ─────────────────────────────────────────────────────────────────
/// 监听单个用户DB 变化自动反映
Stream<User?> watchUser(int uid);
/// 监听指定 uid 列表,任一变化自动反映
Stream<List<User>> watchUsers(List<int> uids);
/// 监听所有用户,任一变化自动反映
Stream<List<User>> watchAllUsers();
// ── 读取 ─────────────────────────────────────────────────────────────────
/// 从 DB 读取单个用户,不存在返回 null
Future<User?> getUser(int uid);
/// 从 DB 读取所有用户
Future<List<User>> getAllUsers();
/// 分页读取用户
///
/// [offset] 起始偏移量
/// [limit] 每页数量
///
/// 示例:
/// ```dart
/// // 第一页
/// final page1 = await repo.getUsers(offset: 0, limit: 50);
/// // 第二页
/// final page2 = await repo.getUsers(offset: 50, limit: 50);
/// ```
Future<List<User>> getUsers({required int offset, required int limit});
/// 统计 DB 中用户总数
Future<int> countUsers();
// ── 写入 ─────────────────────────────────────────────────────────────────
/// 保存完整用户insert or replace
/// 调用方决定是否持久化
Future<void> saveUser(User user);
/// 批量保存用户insert or replace
Future<void> saveUsers(List<User> users);
/// 仅更新指定字段,不影响其他列
///
/// 示例:
/// ```dart
/// await repo.updateFields(uid, UsersCompanion(
/// nickname: Value('New Name'),
/// lastOnline: Value(DateTime.now().millisecondsSinceEpoch),
/// ));
/// ```
Future<void> updateFields(int uid, UsersCompanion companion);
// ── 删除 ─────────────────────────────────────────────────────────────────
/// 删除指定用户
Future<void> deleteUser(int uid);
}