import 'package:freezed_annotation/freezed_annotation.dart'; part 'login_state.freezed.dart'; /// 登录流程的当前步骤 enum LoginStep { /// 步骤 1:输入手机号 phone, /// 步骤 2:输入验证码 otp, } /// 登录页面状态(@freezed 自动生成 copyWith / == / toString) /// /// ViewModel 通过 `state = state.copyWith(...)` 更新状态, /// View 通过 `ref.watch(loginViewModelProvider)` 自动响应变化。 /// /// ## 状态流转 /// /// ``` /// 初始 /// → LoginState() step: phone, isLoading: false /// 点击"获取验证码" /// → state.copyWith(isLoading: true) /// → 成功: state.copyWith(step: otp, contact: phone, isLoading: false) /// → 失败: state.copyWith(error: '...', isLoading: false) /// 点击"登录" /// → state.copyWith(isLoading: true) /// → 成功: authNotifierProvider.login() → 路由守卫重定向 /// → 失败: state.copyWith(error: '...', isLoading: false) /// ``` @freezed sealed class LoginState with _$LoginState { const LoginState._(); const factory LoginState({ /// 当前步骤(手机号输入 or 验证码输入) @Default(LoginStep.phone) LoginStep step, /// 国家代码(默认 +65,暂不支持切换) @Default('+65') String countryCode, /// 已提交的手机号(步骤 2 用于显示和构建请求) @Default('') String contact, /// 是否正在请求中 @Default(false) bool isLoading, /// 错误信息(null = 无错误) String? error, }) = _LoginState; /// 步骤 2 显示的脱敏手机号,如 "138****0000" String get maskedContact { if (contact.length <= 4) return contact; final tail = contact.substring(contact.length - 4); final stars = '*' * (contact.length - 4); return '$stars$tail'; } }