feat(client/auth): 登录/注册页 Enter 键导航与提交
登录:邮箱框回车 → 跳到密码框;密码框回车 → 直接登录(校验条件同主按钮)。 注册:邮箱框回车 → 发送验证码并聚焦验证码框;验证码满 6 位回车 → 进入设密码步 并聚焦密码框;密码框(≥6 位)回车 → 直接注册。 经 TextField.onSubmitted + FocusNode.requestFocus 实现;桌面端回车即触发。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,33 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
}
|
||||
}
|
||||
|
||||
// ── Enter 键导航 / 提交 ───────────────────────────────────────────
|
||||
// 登录:邮箱回车 → 跳密码;密码回车 → 直接登录(校验条件同主按钮 enabled)。
|
||||
void _submitLogin() {
|
||||
if (!_loading && _emailValid && _pw.text.isNotEmpty) _doLogin();
|
||||
}
|
||||
|
||||
// 注册:邮箱回车 → 发码并跳验证码;验证码(6位)回车 → 进设密码步并聚焦密码;
|
||||
// 密码(≥6位)回车 → 直接注册。
|
||||
void _onRegisterEmailSubmit() {
|
||||
if (_loading || !_emailValid) return;
|
||||
if (!_sent) _sendCode();
|
||||
_codeFocus.requestFocus();
|
||||
}
|
||||
|
||||
void _onRegisterCodeSubmit() {
|
||||
if (!_sent || _code.text.length != 6) return;
|
||||
setState(() => _step = 1);
|
||||
// step 1 的密码框在本帧之后才挂载,延到下一帧再聚焦。
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _pwFocus.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void _submitRegister() {
|
||||
if (!_loading && _pw.text.length >= 6) _doRegister();
|
||||
}
|
||||
|
||||
void _setMode(_AuthMode m) => setState(() {
|
||||
_mode = m;
|
||||
_step = 0;
|
||||
@@ -335,12 +362,14 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
kBareInput.copyWith(contentPadding: const EdgeInsets.symmetric(vertical: 13));
|
||||
|
||||
// 密码输入 + 显示/隐藏切换(眼睛按钮)。
|
||||
Widget _pwInput(PangolinScheme c, String hint) => Row(children: [
|
||||
Widget _pwInput(PangolinScheme c, String hint, {VoidCallback? onSubmit}) => Row(children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _pw,
|
||||
focusNode: _pwFocus,
|
||||
obscureText: !_pwVisible,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: onSubmit == null ? null : (_) => onSubmit(),
|
||||
decoration: _bare.copyWith(hintText: hint),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
@@ -370,9 +399,12 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
Widget _login(PangolinScheme c, AppText t) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
|
||||
child: TextField(controller: _email, focusNode: _emailFocus, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
child: TextField(controller: _email, focusNode: _emailFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
onSubmitted: (_) => _pwFocus.requestFocus(),
|
||||
decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.pwPh)),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.pwPh, onSubmit: _submitLogin)),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
@@ -391,7 +423,10 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
|
||||
child: Row(children: [
|
||||
Expanded(child: TextField(controller: _email, focusNode: _emailFocus, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
Expanded(child: TextField(controller: _email, focusNode: _emailFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
onSubmitted: (_) => _onRegisterEmailSubmit(),
|
||||
decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
GestureDetector(
|
||||
onTap: (!_loading && _emailValid) ? _sendCode : null,
|
||||
child: Text(_sent ? t.resend : t.sendCode,
|
||||
@@ -410,6 +445,8 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel, focused: _codeFocus.hasFocus,
|
||||
child: TextField(controller: _code, focusNode: _codeFocus, keyboardType: TextInputType.number, maxLength: 6,
|
||||
textInputAction: TextInputAction.next,
|
||||
onSubmitted: (_) => _onRegisterCodeSubmit(),
|
||||
style: PangolinText.mono.copyWith(letterSpacing: 6, color: c.fg1),
|
||||
decoration: _bare.copyWith(counterText: '', hintText: ''), onChanged: (_) => setState(() {}))),
|
||||
const SizedBox(height: 16),
|
||||
@@ -434,7 +471,7 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.setPwPh)),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.setPwPh, onSubmit: _submitRegister)),
|
||||
const SizedBox(height: 18),
|
||||
_primaryBtn(c, label: _loading ? '...' : t.doCreate,
|
||||
onPressed: (!_loading && _pw.text.length >= 6) ? _doRegister : null),
|
||||
|
||||
Reference in New Issue
Block a user