feat(assets): add chat typing indicator lottie animation

This commit is contained in:
2026-06-08 17:26:09 +08:00
parent f4e1c30051
commit 90fe892995
22 changed files with 988 additions and 3 deletions
+93
View File
@@ -0,0 +1,93 @@
"use client";
/**
* UserStorage 完整实现(KV / localStorage
*
* 对齐 Dart 端 `UserStorage`lib/data/services/storage/user_storage.dart):
* - 三个独立字段:User 对象 / userId / avatarUrl
* - `clearUserData()` 批量清空
*
* 设计沿用 AuthStorage 模式:组合 `LocalStorage`(非继承)+ 单例 + Zod 校验。
*
* 边界:
* - `getUser` 走 `UserSchema` 解析:缺字段时 Zod `.default()` 自动兜底
* - `setUser` 走 `UserSchema` 校验写入:保证存储的总是完整 UserData
* - 单例挂在 class 静态字段,HMR 复用安全
*/
import { UserSchema, type UserData } from "@/data/models";
import { LocalStorage } from "../local_storage";
import { type Result as ResultT } from "../result";
import { StorageKeys } from "../storage_keys";
import type { IUserStorage } from "./iuser_storage";
export class UserStorage implements IUserStorage {
private readonly ls: LocalStorage;
private static _instance: UserStorage | null = null;
constructor(localStorage?: LocalStorage) {
this.ls = localStorage ?? LocalStorage.getInstance();
}
static getInstance(): UserStorage {
if (!UserStorage._instance) UserStorage._instance = new UserStorage();
return UserStorage._instance;
}
/** @internal */
static _resetForTests(): void {
UserStorage._instance = null;
}
// ---- user object ----
getUser(): Promise<ResultT<UserData | null>> {
return this.ls.getJson(StorageKeys.user, UserSchema);
}
setUser(user: UserData): Promise<ResultT<void>> {
return this.ls.setJson(StorageKeys.user, user, UserSchema);
}
clearUser(): Promise<ResultT<void>> {
return this.ls.remove(StorageKeys.user);
}
// ---- userId ----
getUserId(): Promise<ResultT<string | null>> {
return this.ls.getString(StorageKeys.userId);
}
setUserId(id: string): Promise<ResultT<void>> {
return this.ls.setString(StorageKeys.userId, id);
}
clearUserId(): Promise<ResultT<void>> {
return this.ls.remove(StorageKeys.userId);
}
// ---- avatarUrl ----
getAvatarUrl(): Promise<ResultT<string | null>> {
return this.ls.getString(StorageKeys.userAvatar);
}
setAvatarUrl(url: string): Promise<ResultT<void>> {
return this.ls.setString(StorageKeys.userAvatar, url);
}
clearAvatarUrl(): Promise<ResultT<void>> {
return this.ls.remove(StorageKeys.userAvatar);
}
// ---- bulk clear ----
async clearUserData(): Promise<ResultT<void>> {
const r1 = await this.clearUser();
if (r1.kind === "failure") return r1;
const r2 = await this.clearUserId();
if (r2.kind === "failure") return r2;
return this.clearAvatarUrl();
}
}