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(() {
|
void _setMode(_AuthMode m) => setState(() {
|
||||||
_mode = m;
|
_mode = m;
|
||||||
_step = 0;
|
_step = 0;
|
||||||
@@ -335,12 +362,14 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
|||||||
kBareInput.copyWith(contentPadding: const EdgeInsets.symmetric(vertical: 13));
|
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(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _pw,
|
controller: _pw,
|
||||||
focusNode: _pwFocus,
|
focusNode: _pwFocus,
|
||||||
obscureText: !_pwVisible,
|
obscureText: !_pwVisible,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: onSubmit == null ? null : (_) => onSubmit(),
|
||||||
decoration: _bare.copyWith(hintText: hint),
|
decoration: _bare.copyWith(hintText: hint),
|
||||||
onChanged: (_) => setState(() {}),
|
onChanged: (_) => setState(() {}),
|
||||||
),
|
),
|
||||||
@@ -370,9 +399,12 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
|||||||
Widget _login(PangolinScheme c, AppText t) {
|
Widget _login(PangolinScheme c, AppText t) {
|
||||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
|
_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),
|
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(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -391,7 +423,10 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
|||||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
|
_field(c, icon: PangolinIcons.mail, label: t.emailLabel, focused: _emailFocus.hasFocus,
|
||||||
child: Row(children: [
|
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(
|
GestureDetector(
|
||||||
onTap: (!_loading && _emailValid) ? _sendCode : null,
|
onTap: (!_loading && _emailValid) ? _sendCode : null,
|
||||||
child: Text(_sent ? t.resend : t.sendCode,
|
child: Text(_sent ? t.resend : t.sendCode,
|
||||||
@@ -410,6 +445,8 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel, focused: _codeFocus.hasFocus,
|
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel, focused: _codeFocus.hasFocus,
|
||||||
child: TextField(controller: _code, focusNode: _codeFocus, keyboardType: TextInputType.number, maxLength: 6,
|
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),
|
style: PangolinText.mono.copyWith(letterSpacing: 6, color: c.fg1),
|
||||||
decoration: _bare.copyWith(counterText: '', hintText: ''), onChanged: (_) => setState(() {}))),
|
decoration: _bare.copyWith(counterText: '', hintText: ''), onChanged: (_) => setState(() {}))),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -434,7 +471,7 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
|||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
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),
|
const SizedBox(height: 18),
|
||||||
_primaryBtn(c, label: _loading ? '...' : t.doCreate,
|
_primaryBtn(c, label: _loading ? '...' : t.doCreate,
|
||||||
onPressed: (!_loading && _pw.text.length >= 6) ? _doRegister : null),
|
onPressed: (!_loading && _pw.text.length >= 6) ? _doRegister : null),
|
||||||
|
|||||||
Reference in New Issue
Block a user