1)修改数据库命名,不和业务使用重合。

2)修改user 表,uid为unique, 聊天室文件夹name unique移除
This commit is contained in:
Happi (哈比)
2026-03-07 17:29:32 +08:00
parent 36a4cdab03
commit a066e9d2dc
30 changed files with 531 additions and 208 deletions

View File

@@ -0,0 +1,31 @@
{
"code": 0,
"message": "OK",
"data": {
"account_id": "1713925030yFMUBu",
"profile": {
"uid": 2137067,
"uuid": "1713925030yFMUBu",
"last_online": 1772819822,
"profile_pic": "Image/7e/f5/7ef5b60dd83a34a74c164a21fbd1f098/7ef5b60dd83a34a74c164a21fbd1f098.jpg",
"profile_pic_gaussian": "",
"nickname": "Happi哈比",
"contact": "86552205",
"country_code": "+65",
"email": "happi@winwayinfo.com",
"recovery_email": "",
"username": "happi",
"bio": "",
"relationship": 2,
"user_alias": null,
"channel_id": 1,
"channel_group_id": 1,
"hint": "1111"
},
"nonce": "",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIxMzcwNjcsInVkaWQiOjI2NjI2MzcsInNpZCI6IjE3NzI4NjU0NDc0Z1c2VWc9PSIsImlzcyI6ImFiYy5jb20iLCJhdWQiOlsidXNlciJdLCJleHAiOjE3NzQxNjE0NDcsIm5iZiI6MTc3Mjg2NTQ0NywiaWF0IjoxNzcyODY1NDQ3fQ.gUL6hyKgyPP8Tw9y7kRSq-ndNKfV9uGFhU4YKiQzg0I",
"refresh_token": "ps0FF3XayvnJB_P8Cnfu7w-uD781b1-vfmUjbrONZxI=",
"device_id": "SP1A.210812",
"login_data": ""
}
}

View File

@@ -29,25 +29,25 @@ class AppDatabase extends _$AppDatabase {
return {};
}
return {
Favourite: database.favourites,
Sound: database.sounds,
Tag: database.tags,
PendingFriendRequestHistory: database.pendingFriendRequestHistories,
Message: database.messages,
RecentMiniApp: database.recentMiniApps,
Retry: database.retries,
Group: database.groups,
FavoriteMiniApp: database.favoriteMiniApps,
DiscoverMiniApp: database.discoverMiniApps,
ChatCategory: database.chatCategories,
ChatBot: database.chatBots,
FavouriteDetail: database.favouriteDetails,
UserRequestHistory: database.userRequestHistories,
Workspace: database.workspaces,
User: database.users,
ExploreMiniApp: database.exploreMiniApps,
CallLog: database.callLogs,
Chat: database.chats,
DriftFavourite: database.favourites,
DriftSound: database.sounds,
DriftTag: database.tags,
DriftPendingFriendRequestHistory: database.pendingFriendRequestHistories,
DriftMessage: database.messages,
DriftRecentMiniApp: database.recentMiniApps,
DriftRetry: database.retries,
DriftGroup: database.groups,
DriftFavoriteMiniApp: database.favoriteMiniApps,
DriftDiscoverMiniApp: database.discoverMiniApps,
DriftChatCategory: database.chatCategories,
DriftChatBot: database.chatBots,
DriftFavouriteDetail: database.favouriteDetails,
DriftUserRequestHistory: database.userRequestHistories,
DriftWorkspace: database.workspaces,
DriftUser: database.users,
DriftExploreMiniApp: database.exploreMiniApps,
DriftCallLog: database.callLogs,
DriftChat: database.chats,
};
}

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('CallLog')
@DataClassName('DriftCallLog')
class CallLogs extends Table {
TextColumn get id => text()();
IntColumn get callerId => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('ChatBot')
@DataClassName('DriftChatBot')
class ChatBots extends Table {
IntColumn get id => integer()();
TextColumn get name => text().nullable()();

View File

@@ -1,9 +1,9 @@
import 'package:drift/drift.dart';
@DataClassName('ChatCategory')
@DataClassName('DriftChatCategory')
class ChatCategories extends Table {
IntColumn get id => integer()();
TextColumn get name => text().nullable().unique()();
TextColumn get name => text().nullable()();
TextColumn get includedChatIds => text().nullable()();
TextColumn get excludedChatIds => text().nullable()();
IntColumn get seq => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Chat')
@DataClassName('DriftChat')
class Chats extends Table {
IntColumn get id => integer()();
IntColumn get typ => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('DiscoverMiniApp')
@DataClassName('DriftDiscoverMiniApp')
class DiscoverMiniApps extends Table {
TextColumn get id => text()();
TextColumn get name => text().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('ExploreMiniApp')
@DataClassName('DriftExploreMiniApp')
class ExploreMiniApps extends Table {
TextColumn get id => text()();
TextColumn get name => text().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('FavoriteMiniApp')
@DataClassName('DriftFavoriteMiniApp')
class FavoriteMiniApps extends Table {
TextColumn get id => text()();
TextColumn get name => text().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('FavouriteDetail')
@DataClassName('DriftFavouriteDetail')
class FavouriteDetails extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get relatedId => text().withDefault(const Constant(''))();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Favourite')
@DataClassName('DriftFavourite')
class Favourites extends Table {
IntColumn get id => integer()();
TextColumn get parentId => text().withDefault(const Constant(''))();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Group')
@DataClassName('DriftGroup')
class Groups extends Table {
IntColumn get id => integer()();
IntColumn get userJoinDate => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Message')
@DataClassName('DriftMessage')
class Messages extends Table {
IntColumn get id => integer()();
IntColumn get messageId => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('PendingFriendRequestHistory')
@DataClassName('DriftPendingFriendRequestHistory')
class PendingFriendRequestHistories extends Table {
IntColumn get id => integer()();
IntColumn get uid => integer()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('RecentMiniApp')
@DataClassName('DriftRecentMiniApp')
class RecentMiniApps extends Table {
TextColumn get id => text()();
TextColumn get name => text().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Retry')
@DataClassName('DriftRetry')
class Retries extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get uid => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Sound')
@DataClassName('DriftSound')
class Sounds extends Table {
IntColumn get id => integer()();
TextColumn get filePath => text().withDefault(const Constant(''))();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Tag')
@DataClassName('DriftTag')
class Tags extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get uid => integer().nullable()();

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('UserRequestHistory')
@DataClassName('DriftUserRequestHistory')
class UserRequestHistories extends Table {
IntColumn get id => integer()();
IntColumn get status => integer().nullable()();

View File

@@ -1,9 +1,9 @@
import 'package:drift/drift.dart';
@DataClassName('User')
@DataClassName('DriftUser')
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get uid => integer().nullable()();
IntColumn get uid => integer().unique()();
TextColumn get uuid => text().nullable()();
IntColumn get lastOnline => integer().nullable()();
TextColumn get profilePic => text().nullable()();
@@ -36,6 +36,7 @@ class Users extends Table {
TextColumn get publicKey => text().nullable()();
IntColumn get configBits => integer().withDefault(const Constant(0))();
TextColumn get hint => text().nullable()();
@override
String get tableName => 'user';
}

View File

@@ -1,6 +1,6 @@
import 'package:drift/drift.dart';
@DataClassName('Workspace')
@DataClassName('DriftWorkspace')
class Workspaces extends Table {
IntColumn get id => integer()();
TextColumn get name => text().nullable()();

View File

@@ -1,68 +1,148 @@
import 'package:json_annotation/json_annotation.dart';
import '../../domain/entities/user.dart';
part 'user_dto.g.dart';
import 'package:drift/drift.dart';
import 'package:im_app/data/local/drift/app_database.dart';
import 'package:im_app/domain/entities/user.dart';
/// 用户 DTOData Transfer Object
///
/// local / remote 共用的数据传输对象,放在 data/models/。
/// 提供与 Domain Entity [User] 之间的双向转换。
///
/// ## 数据流位置(本地存储场景)
///
/// ```
/// 写入本地:
/// LoginData.toEntity() → User
/// → UserDto.fromEntity(user) → ★ UserDto ★ ← 你在这里
/// → toJson() → SQLite / SharedPreferences
///
/// 读取本地:
/// SQLite / SharedPreferences → JSON
/// → ★ UserDto.fromJson() ★ ← 你在这里
/// → UserDto.toEntity() → User
/// → ViewModel.state → View
/// ```
///
/// 注意:登录接口的 Response DTO 是 [LoginData](含 token
/// 本类用于纯用户信息的本地持久化,不含 token。
@JsonSerializable()
class UserDto {
@JsonKey(name: 'user_id')
final String userId;
final String email;
final int uid;
final String? uuid;
final int? lastOnline;
final String? profilePic;
final String? profilePicGaussian;
final String? nickname;
final String? avatar;
final String? contact;
final String? countryCode;
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? hint;
const UserDto({
required this.userId,
required this.email,
required this.uid,
this.uuid,
this.lastOnline,
this.profilePic,
this.profilePicGaussian,
this.nickname,
this.avatar,
this.contact,
this.countryCode,
this.email,
this.recoveryEmail,
this.username,
this.bio,
this.relationship,
this.userAlias,
this.channelId,
this.channelGroupId,
this.hint,
});
factory UserDto.fromJson(Map<String, dynamic> json) =>
_$UserDtoFromJson(json);
factory UserDto.fromJson(Map<String, dynamic> json) => UserDto(
uid: json['uid'] as int,
uuid: json['uuid'],
lastOnline: json['last_online'],
profilePic: json['profile_pic'],
profilePicGaussian: json['profile_pic_gaussian'] ?? '',
nickname: json['nickname'],
contact: json['contact'],
countryCode: json['country_code'],
email: json['email'],
recoveryEmail: json['recovery_email'] ?? '',
username: json['username'],
bio: json['bio'] ?? '',
relationship: json['relationship'],
userAlias: json['user_alias'],
channelId: json['channel_id'],
channelGroupId: json['channel_group_id'],
hint: json['hint'],
);
Map<String, dynamic> toJson() => _$UserDtoToJson(this);
Map<String, dynamic> toJson() => {
'uid': uid,
'uuid': uuid,
'last_online': lastOnline,
'profile_pic': profilePic,
'profile_pic_gaussian': profilePicGaussian,
'nickname': nickname,
'contact': contact,
'country_code': countryCode,
'email': email,
'recovery_email': recoveryEmail,
'username': username,
'bio': bio,
'relationship': relationship,
'user_alias': userAlias,
'channel_id': channelId,
'channel_group_id': channelGroupId,
'hint': hint,
};
/// DTO → Domain Entity
User toEntity() {
return User(
id: userId,
email: email,
User toEntity() => User(
uid: uid,
uuid: uuid,
lastOnline: lastOnline,
profilePic: profilePic,
profilePicGaussian: profilePicGaussian,
nickname: nickname,
avatar: avatar,
contact: contact,
countryCode: countryCode,
email: email,
recoveryEmail: recoveryEmail,
username: username,
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
}
/// Domain Entity → DTO
factory UserDto.fromEntity(User user) {
return UserDto(
userId: user.id,
email: user.email,
factory UserDto.fromEntity(User user) => UserDto(
uid: user.uid,
uuid: user.uuid,
lastOnline: user.lastOnline,
profilePic: user.profilePic,
profilePicGaussian: user.profilePicGaussian,
nickname: user.nickname,
avatar: user.avatar,
contact: user.contact,
countryCode: user.countryCode,
email: user.email,
recoveryEmail: user.recoveryEmail,
username: user.username,
bio: user.bio,
relationship: user.relationship,
userAlias: user.userAlias,
channelId: user.channelId,
channelGroupId: user.channelGroupId,
hint: user.hint,
);
/// DTO → Drift Companion (for DB insert/update)
UsersCompanion toCompanion() => UsersCompanion(
uid: Value(uid),
uuid: Value(uuid),
lastOnline: Value(lastOnline),
profilePic: Value(profilePic),
profilePicGaussian: Value(profilePicGaussian ?? ''),
nickname: Value(nickname),
contact: Value(contact),
countryCode: Value(countryCode),
email: Value(email),
recoveryEmail: Value(recoveryEmail),
username: Value(username),
bio: Value(bio),
relationship: Value(relationship),
userAlias: Value(userAlias),
hint: Value(hint),
);
}
}

View File

@@ -28,31 +28,75 @@ part 'get_profile_request.g.dart';
/// 用户资料响应 DTO只需反序列化禁止生成无用的 toJson
@JsonSerializable(createToJson: false)
class ProfileData {
@JsonKey(name: 'user_id')
final String userId;
final int uid;
final String uuid;
@JsonKey(name: 'last_online')
final int lastOnline;
@JsonKey(name: 'profile_pic')
final String profilePic;
@JsonKey(name: 'profile_pic_gaussian')
final String profilePicGaussian;
final String nickname;
final String contact;
@JsonKey(name: 'country_code')
final String countryCode;
final String email;
final String? nickname;
final String? avatar;
@JsonKey(name: 'recovery_email')
final String recoveryEmail;
final String username;
final String bio;
final int relationship;
@JsonKey(name: 'user_alias')
final String? userAlias;
@JsonKey(name: 'channel_id')
final int channelId;
@JsonKey(name: 'channel_group_id')
final int channelGroupId;
final String hint;
const ProfileData({
required this.userId,
required this.uid,
required this.uuid,
required this.lastOnline,
required this.profilePic,
required this.profilePicGaussian,
required this.nickname,
required this.contact,
required this.countryCode,
required this.email,
this.nickname,
this.avatar,
required this.recoveryEmail,
required this.username,
required this.bio,
required this.relationship,
this.userAlias,
required this.channelId,
required this.channelGroupId,
required this.hint,
});
factory ProfileData.fromJson(Map<String, dynamic> json) =>
_$ProfileDataFromJson(json);
/// DTO → Domain Entity
User toEntity() {
return User(
id: userId,
email: email,
User toEntity() => User(
uid: uid,
uuid: uuid,
lastOnline: lastOnline,
profilePic: profilePic,
profilePicGaussian: profilePicGaussian,
nickname: nickname,
avatar: avatar,
contact: contact,
countryCode: countryCode,
email: email,
recoveryEmail: recoveryEmail,
username: username,
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
}
}
// ─────────────────────────────────────────────

View File

@@ -8,75 +8,155 @@ part 'login_request.g.dart';
/// # /auth/login — 登录接口
///
/// 一个端点 = 一个文件Response DTO + Request 放在同一文件中。
///
/// ## 数据流位置
///
/// ```
/// AuthRepositoryImpl.login(email, password)
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
/// → 服务端 POST /auth/login
/// → 响应 JSON → ★ LoginData ★ ← 也在这里
/// → LoginData.toEntity() → User
/// → 响应 JSON → ★ LoginResponse ★ ← 也在这里
/// → LoginResponse.toEntity() → User
/// ```
// ─────────────────────────────────────────────
// Response DTO
// ─────────────────────────────────────────────
/// 登录响应 DTO
///
/// 服务端返回的登录数据,包含 token 和用户信息。
/// 通过 [toEntity] 转换为 Domain Entity [User]。
@JsonSerializable()
class LoginData {
final String token;
@JsonKey(name: 'user_id')
final String userId;
@JsonSerializable(createToJson: false)
class LoginProfile {
final int uid;
final String uuid;
@JsonKey(name: 'last_online')
final int lastOnline;
@JsonKey(name: 'profile_pic')
final String profilePic;
@JsonKey(name: 'profile_pic_gaussian')
final String profilePicGaussian;
final String nickname;
final String contact;
@JsonKey(name: 'country_code')
final String countryCode;
final String email;
final String? nickname;
final String? avatar;
@JsonKey(name: 'recovery_email')
final String recoveryEmail;
final String username;
final String bio;
final int relationship;
@JsonKey(name: 'user_alias')
final String? userAlias;
@JsonKey(name: 'channel_id')
final int channelId;
@JsonKey(name: 'channel_group_id')
final int channelGroupId;
final String hint;
const LoginProfile({
required this.uid,
required this.uuid,
required this.lastOnline,
required this.profilePic,
required this.profilePicGaussian,
required this.nickname,
required this.contact,
required this.countryCode,
required this.email,
required this.recoveryEmail,
required this.username,
required this.bio,
required this.relationship,
this.userAlias,
required this.channelId,
required this.channelGroupId,
required this.hint,
});
factory LoginProfile.fromJson(Map<String, dynamic> json) =>
_$LoginProfileFromJson(json);
User toEntity() => User(
uid: uid,
uuid: uuid,
lastOnline: lastOnline,
profilePic: profilePic,
profilePicGaussian: profilePicGaussian,
nickname: nickname,
contact: contact,
countryCode: countryCode,
email: email,
recoveryEmail: recoveryEmail,
username: username,
bio: bio,
relationship: relationship,
userAlias: userAlias,
channelId: channelId,
channelGroupId: channelGroupId,
hint: hint,
);
}
@JsonSerializable(createToJson: false, explicitToJson: true)
class LoginData {
@JsonKey(name: 'account_id')
final String accountId;
final LoginProfile profile;
final String nonce;
@JsonKey(name: 'access_token')
final String accessToken;
@JsonKey(name: 'refresh_token')
final String refreshToken;
@JsonKey(name: 'device_id')
final String deviceId;
@JsonKey(name: 'login_data')
final String loginData;
const LoginData({
required this.token,
required this.userId,
required this.email,
this.nickname,
this.avatar,
required this.accountId,
required this.profile,
required this.nonce,
required this.accessToken,
required this.refreshToken,
required this.deviceId,
required this.loginData,
});
factory LoginData.fromJson(Map<String, dynamic> json) =>
_$LoginDataFromJson(json);
Map<String, dynamic> toJson() => _$LoginDataToJson(this);
User toEntity() => profile.toEntity();
}
/// DTO → Domain Entity
User toEntity() {
return User(
id: userId,
email: email,
nickname: nickname,
avatar: avatar,
);
}
/// Top-level envelope: { "code": 0, "message": "OK", "data": { ... } }
@JsonSerializable(createToJson: false, explicitToJson: true)
class LoginResponse {
final int code;
final String message;
final LoginData data;
const LoginResponse({
required this.code,
required this.message,
required this.data,
});
factory LoginResponse.fromJson(Map<String, dynamic> json) =>
_$LoginResponseFromJson(json);
User toEntity() => data.toEntity();
}
// ─────────────────────────────────────────────
// Request
// ─────────────────────────────────────────────
/// 登录请求
///
/// `@ApiRequest` 自动生成 `_$LoginRequestApi` mixin
/// 提供 path / method / requestType / includeToken / fromJson 自动注册。
@ApiRequest(
path: ApiPaths.authLogin,
method: HttpMethod.post,
responseType: LoginData,
responseType: LoginResponse,
requestType: ApiRequestType.login,
)
@JsonSerializable()
class LoginRequest extends ApiRequestable<LoginData> with _$LoginRequestApi {
class LoginRequest extends ApiRequestable<LoginResponse>
with _$LoginRequestApi {
final String email;
final String password;

View File

@@ -30,18 +30,18 @@ class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({required NetworksSdkApi client, required void Function(String?) onTokenUpdate,}) : _client = client, _onTokenUpdate = onTokenUpdate;
@override
Future<User> login({required String email, required String password,}) async
{
final LoginData? loginData = await _client.executeRequest(LoginRequest(email: email, password: password),);
Future<User> login({required String email, required String password}) async {
final LoginResponse? loginResponse = await _client.executeRequest(
LoginRequest(email: email, password: password),
);
if (loginData == null) {
throw Exception('Login failed: empty response'); // TODO: 接入国际化
if (loginResponse == null) {
throw Exception('Login failed: empty response');
}
// 回调写入 Token内存 + 持久化由 Provider 层组合)
_onTokenUpdate(loginData.token);
_onTokenUpdate(loginResponse.data.accessToken);
return loginData.toEntity(); // DTO → Domain Entity
return loginResponse.toEntity();
}
@override

View File

@@ -14,15 +14,81 @@
/// → View 渲染
/// ```
class User {
final String id;
final String email;
final int uid;
final String? uuid;
final int? lastOnline;
final String? profilePic;
final String? profilePicGaussian;
final String? nickname;
final String? avatar;
final String? contact;
final String? countryCode;
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? hint;
const User({
required this.id,
required this.email,
required this.uid,
this.uuid,
this.lastOnline,
this.profilePic,
this.profilePicGaussian,
this.nickname,
this.avatar,
this.contact,
this.countryCode,
this.email,
this.recoveryEmail,
this.username,
this.bio,
this.relationship,
this.userAlias,
this.channelId,
this.channelGroupId,
this.hint,
});
User copyWith({
int? uid,
String? uuid,
int? lastOnline,
String? profilePic,
String? profilePicGaussian,
String? nickname,
String? contact,
String? countryCode,
String? email,
String? recoveryEmail,
String? username,
String? bio,
int? relationship,
String? userAlias,
int? channelId,
int? channelGroupId,
String? hint,
}) {
return User(
uid: uid ?? this.uid,
uuid: uuid ?? this.uuid,
lastOnline: lastOnline ?? this.lastOnline,
profilePic: profilePic ?? this.profilePic,
profilePicGaussian: profilePicGaussian ?? this.profilePicGaussian,
nickname: nickname ?? this.nickname,
contact: contact ?? this.contact,
countryCode: countryCode ?? this.countryCode,
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,
hint: hint ?? this.hint,
);
}
}

View File

@@ -1,7 +1,6 @@
import 'dart:math';
import 'package:drift/drift.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:im_app/app/di/db_provider.dart';
import 'package:im_app/data/local/drift/app_database.dart';
@@ -87,12 +86,12 @@ class ChatDbTestViewModel extends _$ChatDbTestViewModel {
final chunk = List.generate(
chunkSize.clamp(0, count - i),
(j) => UsersCompanion.insert(
uid: Value(i + j),
uid: i + j,
nickname: Value('User ${i + j}'),
),
);
await db.batchInsertOrReplace<User>(chunk);
await db.batchInsertOrReplace<DriftUser>(chunk);
completed += chunk.length;
// 让出主线程

View File

@@ -1,4 +1,8 @@
import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:im_app/data/models/user_dto.dart';
import 'package:im_app/data/remote/login_request.dart';
import 'package:networks_sdk/networks_sdk.dart';
import 'package:im_app/app/di/db_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -56,10 +60,22 @@ class LoginViewModel extends _$LoginViewModel {
/// 正式 [login] 成功后同样需要调用 [AuthNotifier.login] 更新守卫状态。
Future<void> demoLogin() async {
final storageApi = ref.read(storageSdkProvider);
///TODO: StorageSDKLifeCycle 需要只在主项目暴露
final storageLifeCycle = storageApi as StorageSdkLifecycle;
ref.read(authNotifierProvider).login();
await storageLifeCycle.openDatabase(1234567);
final provider = ref.read(authNotifierProvider);
// Read mock response from assets
final String raw = await rootBundle.loadString('assets/loginData.json');
final Map<String, dynamic> json = jsonDecode(raw);
// Parse into LoginData (nested under 'data' key)
final loginResponse = LoginResponse.fromJson(json);
final user = loginResponse.data.toEntity();
provider.login();
// Open database for the user
await storageLifeCycle.openDatabase(user.uid);
final userCompanion = UserDto.fromEntity(user).toCompanion();
storageApi.insert(userCompanion);
}
/// 执行登录

View File

@@ -62,3 +62,5 @@ dev_dependencies:
flutter:
uses-material-design: true
assets:
- assets/

View File

@@ -23,7 +23,7 @@ class DatabaseRepositoryImpl implements DatabaseRepository {
) async {
final db = _db;
if (db == null) return;
await db.into(table).insertOnConflictUpdate(companion);
await db.into(table).insert(companion, mode: InsertMode.insertOrReplace);
}
@override
@@ -44,7 +44,11 @@ class DatabaseRepositoryImpl implements DatabaseRepository {
final db = _db;
if (db == null) return;
await db.batch(
(batch) => batch.insertAllOnConflictUpdate(table, companions),
(batch) => batch.insertAll(
table,
companions,
mode: InsertMode.insertOrReplace,
),
);
}