为什么:客户端此前完全没有账号概念(mine 页的"登录"只是本地 demo 开关), 而受限动作(收藏/评论)要对到具体账号。本批把账号链路接上,为后续互动、进度同步、下载打底。 - AuthApi + AuthController + AuthSessionStore:注册/登录/登出/刷新,令牌与设备号存 shared_preferences (用 SharedPreferencesAsync,与 engagement 仓库同一套 API) - ApiClient:统一带 Authorization 与 X-Device-Id;401 自动刷新一次并重试,刷新失败清登录态走 onUnauthorized - 登录/注册页(pages/auth,沿用 binding/logic/view 与现有视觉):邮箱格式、密码 8-64 位含字母数字本地先校验, 错误提示走 l10n;登录/注册一键切换 - 匿名/登录分叉:首页、发现、详情保持匿名可看;收藏/点赞等受限动作走 ensureSignedIn() 弹底部提示引导登录 - mine 页展示真实账号(昵称 + uid),拿不到才回落到演示文案 - 测试:新增 auth 控制器(注册落盘/401 刷新重试/刷新失败清会话/登出)与登录页(校验/切换)用例; widget_test 改为预置本地会话(账号不再是本地开关);修掉 playback 用例对 host 大小写的环境依赖断言 门禁:flutter analyze 0 问题;flutter test 148 全过。
51 lines
1.6 KiB
Dart
51 lines
1.6 KiB
Dart
/// 登录 / 注册页的表单状态与提交逻辑。
|
|
///
|
|
/// 校验规则与后端一致(邮箱格式、密码 8-64 位且含字母数字),本地先拦一次是为了少一次网络往返、
|
|
/// 也让用户立刻看到哪个字段不合格;后端仍会再校验一遍(前端校验不作为安全边界)。
|
|
class AuthLogic {
|
|
bool signUpMode;
|
|
String email = '';
|
|
String password = '';
|
|
bool busy = false;
|
|
String? errorMessage;
|
|
|
|
AuthLogic({this.signUpMode = false, this.errorMessage});
|
|
|
|
void toggleMode() {
|
|
signUpMode = !signUpMode;
|
|
errorMessage = null;
|
|
}
|
|
|
|
/// 返回 null 表示校验通过,否则返回错误文案(由视图用 l10n 映射)
|
|
AuthFieldError? validate() {
|
|
final mail = email.trim();
|
|
if (mail.isEmpty) return AuthFieldError.emailRequired;
|
|
if (!_emailPattern.hasMatch(mail)) return AuthFieldError.emailInvalid;
|
|
if (password.isEmpty) return AuthFieldError.passwordRequired;
|
|
if (password.length < 8 || password.length > 64) {
|
|
return AuthFieldError.passwordLength;
|
|
}
|
|
if (!_hasLetter.hasMatch(password) || !_hasDigit.hasMatch(password)) {
|
|
return AuthFieldError.passwordWeak;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String get normalizedEmail => email.trim().toLowerCase();
|
|
|
|
static final _emailPattern = RegExp(
|
|
r'^[A-Za-z0-9._%+\-]{1,64}@[A-Za-z0-9]([A-Za-z0-9\-]{0,61}[A-Za-z0-9])?'
|
|
r'(\.[A-Za-z0-9]([A-Za-z0-9\-]{0,61}[A-Za-z0-9])?)*\.[A-Za-z]{2,}$',
|
|
);
|
|
static final _hasLetter = RegExp('[A-Za-z]');
|
|
static final _hasDigit = RegExp('[0-9]');
|
|
}
|
|
|
|
enum AuthFieldError {
|
|
emailRequired,
|
|
emailInvalid,
|
|
passwordRequired,
|
|
passwordLength,
|
|
passwordWeak,
|
|
}
|