feat(client): 登录/注册页重设计(居中卡片 + 暖光晕)
- auth_screen.dart 整体重排:暖色径向光晕背景 + 居中卡片;hero 用 clay 渐变 app-icon(带光晕)+ 品牌 + 标语;胶囊分段 tab(滑块)替下划线;输入框聚焦态 (accent 边 + ring 光环);主按钮微光;进入错落淡入动效。全部走 token。 - 注册第二步:已验证邮箱胶囊 + 「改邮箱」返回 + 设密码;验证码框无占位点。 - 逻辑零回归:登录/注册两段式、发码、邮箱预填、密码显隐、错误提示全保留。 - design/preview/auth_redesign.html:HTML 视觉原型(评审用,不进构建)。 flutter analyze 0 error;114 tests passed(auth 不在 golden 集,无 golden 变化)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
// auth_screen.dart — 登录 / 注册页(邮箱 + 验证码流程)
|
||||
// auth_screen.dart — 登录 / 注册页(居中卡片 + 暖光晕重设计)
|
||||
//
|
||||
// 视觉:暖色径向光晕背景 + 居中卡片;hero 用 clay 渐变 app-icon + 品牌;胶囊分段
|
||||
// tab;输入框聚焦态(accent 边 + 光环);主按钮微光;进入错落淡入。全部走 token。
|
||||
// 逻辑零回归:登录/注册两段式、发码、邮箱预填、密码显隐、错误提示均保留。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
@@ -16,6 +20,8 @@ const _kApiUrl = String.fromEnvironment(
|
||||
defaultValue: 'http://localhost:8080',
|
||||
);
|
||||
|
||||
const _easeOut = Cubic(0.22, 1, 0.36, 1);
|
||||
|
||||
enum _AuthMode { login, register }
|
||||
|
||||
class AuthScreen extends ConsumerStatefulWidget {
|
||||
@@ -27,7 +33,8 @@ class AuthScreen extends ConsumerStatefulWidget {
|
||||
ConsumerState<AuthScreen> createState() => _AuthScreenState();
|
||||
}
|
||||
|
||||
class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
_AuthMode _mode = _AuthMode.login;
|
||||
int _step = 0;
|
||||
bool _sent = false;
|
||||
@@ -39,6 +46,15 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
final _code = TextEditingController();
|
||||
final _pw = TextEditingController();
|
||||
|
||||
final _emailFocus = FocusNode();
|
||||
final _pwFocus = FocusNode();
|
||||
final _codeFocus = FocusNode();
|
||||
|
||||
late final AnimationController _intro = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 620),
|
||||
);
|
||||
|
||||
late final AuthApi _api = AuthApi(baseUrl: _kApiUrl);
|
||||
|
||||
bool get _emailValid => RegExp(r'\S+@\S+\.\S+').hasMatch(_email.text);
|
||||
@@ -46,12 +62,17 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 聚焦态驱动输入框边框变化。
|
||||
for (final f in [_emailFocus, _pwFocus, _codeFocus]) {
|
||||
f.addListener(() => setState(() {}));
|
||||
}
|
||||
// 预填上次登录邮箱。
|
||||
ref.read(tokenStoreProvider).loadLastEmail().then((email) {
|
||||
if (email != null && email.isNotEmpty && mounted) {
|
||||
setState(() => _email.text = email);
|
||||
}
|
||||
});
|
||||
_intro.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -59,11 +80,15 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
_email.dispose();
|
||||
_code.dispose();
|
||||
_pw.dispose();
|
||||
_emailFocus.dispose();
|
||||
_pwFocus.dispose();
|
||||
_codeFocus.dispose();
|
||||
_intro.dispose();
|
||||
_api.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ── 认证操作 ──────────────────────────────────────────────────
|
||||
// ── 认证操作(逻辑不变)──────────────────────────────────────────
|
||||
|
||||
Future<void> _sendCode() async {
|
||||
setState(() { _loading = true; _errorZh = null; });
|
||||
@@ -106,101 +131,212 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
void _setMode(_AuthMode m) => setState(() {
|
||||
_mode = m;
|
||||
_step = 0;
|
||||
_sent = false;
|
||||
_errorZh = null;
|
||||
});
|
||||
|
||||
// ── UI ──────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.pangolin;
|
||||
final t = widget.t;
|
||||
final dark = Theme.of(context).brightness == Brightness.dark;
|
||||
final glow = c.accent.withValues(alpha: dark ? 0.16 : 0.13);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: c.bg,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
Column(children: [
|
||||
const PangolinMark(size: 52),
|
||||
const SizedBox(height: 12),
|
||||
Text(t.brand, style: PangolinText.h1.copyWith(color: c.fg1)),
|
||||
const SizedBox(height: 4),
|
||||
Text(t.authTagline, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w600)),
|
||||
]),
|
||||
const SizedBox(height: 28),
|
||||
Row(children: [
|
||||
_tab(_AuthMode.login, t.tabLogin, c),
|
||||
_tab(_AuthMode.register, t.tabRegister, c),
|
||||
]),
|
||||
Divider(height: 1, color: c.border),
|
||||
const SizedBox(height: 22),
|
||||
if (_errorZh != null) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: c.danger.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.md),
|
||||
border: Border.all(color: c.danger.withAlpha(80)),
|
||||
),
|
||||
child: Row(children: [
|
||||
Icon(PangolinIcons.x, size: 16, color: c.danger),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(_errorZh!, style: PangolinText.sm.copyWith(color: c.danger))),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
Expanded(child: _mode == _AuthMode.login ? _login(c, t) : _register(c, t)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Text(t.tos, textAlign: TextAlign.center, style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)),
|
||||
body: Stack(children: [
|
||||
_atmosphere(glow),
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
_reveal(0.0, 0.55, _hero(c, t)),
|
||||
const SizedBox(height: 26),
|
||||
_reveal(0.22, 1.0, _card(c, t)),
|
||||
const SizedBox(height: 18),
|
||||
_reveal(0.42, 1.0, Text(t.tos,
|
||||
textAlign: TextAlign.center,
|
||||
style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400))),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tab(_AuthMode m, String label, PangolinScheme c) {
|
||||
final on = _mode == m;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
_mode = m;
|
||||
_step = 0;
|
||||
_sent = false;
|
||||
_errorZh = null;
|
||||
}),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: on ? c.accent : Colors.transparent, width: 2))),
|
||||
child: Center(child: Text(label, style: PangolinText.body.copyWith(color: on ? c.fg1 : c.fg3, fontWeight: on ? FontWeight.w700 : FontWeight.w500))),
|
||||
// 暖色径向光晕(顶部 + 右下两团)。
|
||||
Widget _atmosphere(Color glow) => Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: Stack(children: [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: const Alignment(0, -1.15),
|
||||
radius: 1.0,
|
||||
colors: [glow, Colors.transparent],
|
||||
stops: const [0, 0.72],
|
||||
),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: const Alignment(0.85, 1.05),
|
||||
radius: 0.7,
|
||||
colors: [glow, Colors.transparent],
|
||||
stops: const [0, 0.72],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
// 进入错落淡入 + 上移。
|
||||
Widget _reveal(double start, double end, Widget child) {
|
||||
final anim = CurvedAnimation(parent: _intro, curve: Interval(start, end, curve: _easeOut));
|
||||
return AnimatedBuilder(
|
||||
animation: anim,
|
||||
builder: (_, c) => Opacity(
|
||||
opacity: anim.value.clamp(0.0, 1.0),
|
||||
child: Transform.translate(offset: Offset(0, 14 * (1 - anim.value)), child: c),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _field(PangolinScheme c, {required IconData icon, required String label, required Widget child}) {
|
||||
Widget _hero(PangolinScheme c, AppText t) => Column(children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.xl),
|
||||
boxShadow: [
|
||||
BoxShadow(color: c.accent.withValues(alpha: 0.30), blurRadius: 44, spreadRadius: 2),
|
||||
...PangolinShadow.lg,
|
||||
],
|
||||
),
|
||||
child: const PangolinAppIcon(size: 64),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(t.brand, style: PangolinText.h1.copyWith(color: c.fg1)),
|
||||
const SizedBox(height: 6),
|
||||
Text(t.authTagline, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w600)),
|
||||
]);
|
||||
|
||||
Widget _card(PangolinScheme c, AppText t) => Container(
|
||||
padding: const EdgeInsets.fromLTRB(24, 26, 24, 24),
|
||||
decoration: BoxDecoration(
|
||||
color: c.surface,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.xxl),
|
||||
border: Border.all(color: c.border),
|
||||
boxShadow: [...PangolinShadow.md, ...PangolinShadow.lg],
|
||||
),
|
||||
child: Column(children: [
|
||||
_segTabs(c, t),
|
||||
const SizedBox(height: 22),
|
||||
if (_errorZh != null) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: c.dangerSubtle,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.md),
|
||||
border: Border.all(color: c.danger),
|
||||
),
|
||||
child: Row(children: [
|
||||
Icon(PangolinIcons.x, size: 16, color: c.danger),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(_errorZh!, style: PangolinText.sm.copyWith(color: c.danger))),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
_mode == _AuthMode.login ? _login(c, t) : _register(c, t),
|
||||
]),
|
||||
);
|
||||
|
||||
// 胶囊分段 tab(滑块滑动)。
|
||||
Widget _segTabs(PangolinScheme c, AppText t) {
|
||||
final reg = _mode == _AuthMode.register;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(color: c.bgSubtle, borderRadius: BorderRadius.circular(PangolinRadius.full)),
|
||||
child: Stack(children: [
|
||||
Positioned.fill(
|
||||
child: AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 260),
|
||||
curve: _easeOut,
|
||||
alignment: reg ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: c.accent,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.full),
|
||||
boxShadow: PangolinShadow.md,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(children: [
|
||||
_seg(c, t.tabLogin, !reg, () => _setMode(_AuthMode.login)),
|
||||
_seg(c, t.tabRegister, reg, () => _setMode(_AuthMode.register)),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _seg(PangolinScheme c, String label, bool active, VoidCallback onTap) => Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 9),
|
||||
alignment: Alignment.center,
|
||||
child: Text(label,
|
||||
style: PangolinText.body.copyWith(
|
||||
color: active ? c.fgOnAccent : c.fg3, fontWeight: FontWeight.w600, fontSize: 15)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 输入字段:标签 + 图标 + 聚焦态(accent 边 + 光环)。
|
||||
Widget _field(PangolinScheme c, {required IconData icon, required String label, required bool focused, required Widget child}) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(label, style: PangolinText.caption.copyWith(color: c.fg2, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 7),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 2),
|
||||
decoration: BoxDecoration(color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.md), border: Border.all(color: c.borderStrong, width: 1.5)),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 160),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: c.surface,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.md),
|
||||
border: Border.all(color: focused ? c.accent : c.borderStrong, width: 1.5),
|
||||
boxShadow: focused ? [BoxShadow(color: c.ring, spreadRadius: 3)] : null,
|
||||
),
|
||||
child: Row(children: [Icon(icon, size: 18, color: c.fg3), const SizedBox(width: 10), Expanded(child: child)]),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
InputDecoration get _bare =>
|
||||
const InputDecoration(border: InputBorder.none, isCollapsed: true, contentPadding: EdgeInsets.symmetric(vertical: 12));
|
||||
const InputDecoration(border: InputBorder.none, isCollapsed: true, contentPadding: EdgeInsets.symmetric(vertical: 13));
|
||||
|
||||
// 密码输入 + 显示/隐藏切换(眼睛按钮)。
|
||||
Widget _pwInput(PangolinScheme c, String hint) => Row(children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _pw,
|
||||
focusNode: _pwFocus,
|
||||
obscureText: !_pwVisible,
|
||||
decoration: _bare.copyWith(hintText: hint),
|
||||
onChanged: (_) => setState(() {}),
|
||||
@@ -215,34 +351,44 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
),
|
||||
]);
|
||||
|
||||
Widget _primaryBtn(PangolinScheme c, {required String label, required VoidCallback? onPressed}) {
|
||||
final enabled = onPressed != null;
|
||||
return Container(
|
||||
decoration: enabled
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.full),
|
||||
boxShadow: [BoxShadow(color: c.accent.withValues(alpha: 0.30), blurRadius: 20, offset: const Offset(0, 6))],
|
||||
)
|
||||
: null,
|
||||
child: PangolinButton(label: label, expand: true, onPressed: onPressed),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _login(PangolinScheme c, AppText t) {
|
||||
return Column(children: [
|
||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel,
|
||||
child: TextField(controller: _email, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
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(() {}))),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, child: _pwInput(c, t.pwPh)),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.pwPh)),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(t.forgotPw, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
PangolinButton(
|
||||
label: _loading ? '...' : t.doLogin,
|
||||
expand: true,
|
||||
onPressed: (!_loading && _emailValid && _pw.text.isNotEmpty) ? _doLogin : null,
|
||||
),
|
||||
_primaryBtn(c, label: _loading ? '...' : t.doLogin,
|
||||
onPressed: (!_loading && _emailValid && _pw.text.isNotEmpty) ? _doLogin : null),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _register(PangolinScheme c, AppText t) {
|
||||
if (_step == 0) {
|
||||
return Column(children: [
|
||||
_field(c, icon: PangolinIcons.mail, label: t.emailLabel,
|
||||
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, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
Expanded(child: TextField(controller: _email, focusNode: _emailFocus, decoration: _bare.copyWith(hintText: t.emailPh), onChanged: (_) => setState(() {}))),
|
||||
GestureDetector(
|
||||
onTap: (!_loading && _emailValid) ? _sendCode : null,
|
||||
child: Text(_sent ? t.resend : t.sendCode,
|
||||
@@ -259,25 +405,36 @@ class _AuthScreenState extends ConsumerState<AuthScreen> {
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel,
|
||||
child: TextField(controller: _code, keyboardType: TextInputType.number, maxLength: 6,
|
||||
_field(c, icon: PangolinIcons.shieldCheck, label: t.codeLabel, focused: _codeFocus.hasFocus,
|
||||
child: TextField(controller: _code, focusNode: _codeFocus, keyboardType: TextInputType.number, maxLength: 6,
|
||||
style: PangolinText.mono.copyWith(letterSpacing: 6, color: c.fg1),
|
||||
decoration: _bare.copyWith(counterText: '', hintText: ''), onChanged: (_) => setState(() {}))),
|
||||
const SizedBox(height: 8),
|
||||
PangolinButton(label: t.doNext, expand: true, onPressed: (_sent && _code.text.length == 6) ? () => setState(() => _step = 1) : null),
|
||||
const SizedBox(height: 16),
|
||||
_primaryBtn(c, label: t.doNext, onPressed: (_sent && _code.text.length == 6) ? () => setState(() => _step = 1) : null),
|
||||
]);
|
||||
}
|
||||
// Step 1: set password
|
||||
return Column(children: [
|
||||
Row(children: [Icon(PangolinIcons.checkCircle, size: 15, color: c.success), const SizedBox(width: 7), Text(_email.text, style: PangolinText.sm.copyWith(color: c.fg2))]),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, child: _pwInput(c, t.setPwPh)),
|
||||
const SizedBox(height: 18),
|
||||
PangolinButton(
|
||||
label: _loading ? '...' : t.doCreate,
|
||||
expand: true,
|
||||
onPressed: (!_loading && _pw.text.length >= 6) ? _doRegister : null,
|
||||
// Step 1: 已验证邮箱(可返回改邮箱)+ 设密码
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
||||
decoration: BoxDecoration(color: c.bgSubtle, borderRadius: BorderRadius.circular(PangolinRadius.md)),
|
||||
child: Row(children: [
|
||||
Icon(PangolinIcons.shieldCheck, size: 16, color: c.success),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(_email.text, overflow: TextOverflow.ellipsis,
|
||||
style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600))),
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _step = 0),
|
||||
child: Text(t.lang == AppLang.zh ? '改邮箱' : 'Change',
|
||||
style: PangolinText.caption.copyWith(color: c.accent, fontWeight: FontWeight.w600, fontSize: 13)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field(c, icon: PangolinIcons.lock, label: t.pwLabel, focused: _pwFocus.hasFocus, child: _pwInput(c, t.setPwPh)),
|
||||
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