从登录入口的 "Continue with email" 进入(TAPD #1003129),顶部一个 Sign in / Create account 分段控件切两种模式:Create(默认——WiFi 配网漏斗多数是首次注册)要求两次输入密码并过 PasswordPolicy 校验,成功后跳 邮箱验证码 页;Sign in 直接登录,成功即完成漏斗。若在 Create 模式下提交的邮箱已经注册过(可能是之前用 Google 注册的),页面会自动切到 Sign in 分段并保留邮箱,不需要用户自己发现"这个邮箱已存在"再手动切换。
lib/features/auth/presentation/email_auth_page.dart(260 行)authControllerProvider(register / login / cancelPendingOAuth)PasswordPolicy.validateAuthScaffold / AuthField / PrimaryAuthButton / PolicyFooterdeviceId(可空,原样透传给下一步)/email-auth(EmailAuthPage.routeName)lib/app.dart 的 onGenerateRoute顶部 Sign in / Create 分段切换控件;下方邮箱输入框、密码输入框(Create 模式下多一个"确认密码"输入框 + 密码规则提示文字);主按钮 "Continue";底部居中一行 "Forgot password?" 链接。
initState 首帧回调,无需用户操作authControllerProvider.notifier.cancelPendingOAuth()_ModeToggle.onChangedsetState 切换表单字段(Sign in 只显示邮箱+密码;Create 额外显示确认密码框+规则提示),同时清空密码相关的错误提示_mode == _Mode.create_submit()
→ 邮箱非空 + 含 '@' 校验;不过则显示 authInvalidEmail
→ PasswordPolicy.validate(password) 不通过 → 显示对应错误文案
→ 两次密码不一致 → 显示 authPwMismatch
→ authControllerProvider.notifier.register(email, password)
→ 成功 → Navigator.pushNamed(EmailCodePage.routeName, arguments: deviceId)
→ 若失败且 error == AuthError.emailInUse → setState(_mode = signIn)
(邮箱已注册——大概率是同邮箱的 Google 账号——切到 Sign in 分段,邮箱保留,
用户可以直接输密码登录,或退回上一屏改走 Google)
register 成功但后端关闭了邮箱确认时,AuthController 会检测到已存在的 session 直接把状态翻成 signedIn(这种情况下即使调用方以为"成功"要跳验证码页,实际上下一次 build 时用户已经是登录态了);开启邮箱确认(当前 spec 默认)时才真正需要走验证码页_mode == _Mode.signInlogin(emailOrUsername: email, password: password) → 成功 → finishAuthFunnel(context, deviceId)Navigator.pushNamed(ForgotPasswordPage.routeName, arguments: (email.trim(), deviceId))watch authControllerProvider 读 state.busy 驱动按钮 loading;ref.listen 统一弹错误 SnackBar。_mode 与三个文本控制器(email/password/confirm)均是纯本地状态,dispose() 里释放三个 controller。
无——纯 Supabase Auth REST 调用,无平台分支(Google/Apple 平台差异在上一屏登录入口已经处理完,到本页时只剩邮箱路径)。
@,真正的有效性由服务端在 register/login 调用时判断。