feat(flutter): 修复登录输入并升级登录页

This commit is contained in:
Zhou Weidong committed 2026-10-20 16:21:06 +08:00
1 parent f72cb41670
commit 81bb1543bb
5 files changed
+233 -141

No files matched your search

+103 -71
View File
@@ -11,6 +11,7 @@ import '../../core/platform/haptics.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/subpage_header.dart';
import '../../l10n/app_localizations.dart';
import 'auth_logic.dart';
import 'phone_auth_sheet.dart';
@@ -19,18 +20,21 @@ part 'auth_widgets.dart';
part 'auth_form_widgets.dart';
const double _minTapTarget = 44;
const double _brandMarkSize = 48;
const double _brandMarkSize = 72;
const double _compactBrandMarkSize = 48;
const double _brandMarkRadius = 20;
const double _compactBrandMarkRadius = 14;
const double _brandIconSize = 36;
const double _compactBrandIconSize = 26;
const double _contentMaxWidth = 440;
// Auth keeps its approved 20px card silhouette.
const double _cardRadius = AppDimens.authCardRadius;
const double _cardPadding = AppDimens.cardPadding;
const double _cardBorderWidth = 1;
const double _cardPadding = 20;
const double _fieldHeight = 52;
const double _fieldRadius = 14;
const double _fieldBorderWidth = 1.5;
const double _buttonHeight = 52;
const double _buttonRadius = 26;
const double _glowOpacity = 0.18;
const double _fieldBorderWidth = 1;
const double _buttonHeight = 50;
const double _buttonRadius = 25;
const double _errorOpacity = 0.10;
const double _errorBorderOpacity = 0.28;
const double _enterSlideDistance = 16;
@@ -39,11 +43,48 @@ const double _bottomContentPadding = 28;
const double _compactTopContentPadding = 48;
const double _compactHeightBreakpoint = 720;
const double _compactBrandGap = 16;
const double _warmGlowSize = 260;
const double _warmGlowOpacity = 0.06;
const double _glowSize = 280;
const double _warmGlowOpacity = 0.18;
const double _entryButtonHeight = 50;
const double _entryButtonRadius = 25;
const double _entryButtonFontSize = 15;
const double _entryIconSize = 20;
const Duration _enterDuration = Duration(milliseconds: 220);
const Duration _segmentDuration = Duration(milliseconds: 160);
Widget _brandMarkWidget({
Key? key,
required double size,
required double radius,
required double iconSize,
}) {
return Container(
key: key,
width: size,
height: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.blue, AppColors.secondary],
),
boxShadow: [
BoxShadow(
color: AppColors.blue.withValues(alpha: 0.24),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Icon(
Icons.play_arrow_rounded,
color: AppColors.onPrimary,
size: iconSize,
),
);
}
/// 登录 / 注册页:邮箱 + 密码。
class AuthPage extends StatefulWidget {
const AuthPage({super.key, this.signUpMode = false});
@@ -147,34 +188,39 @@ class _AuthPageState extends State<AuthPage> {
backgroundColor: AppColors.ink,
body: Stack(
children: [
const Positioned.fill(
child: IgnorePointer(
child: DecoratedBox(
key: Key('auth-page-background'),
decoration: BoxDecoration(gradient: AppTheme.pageGradient),
),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
height: 420,
top: -90,
right: -70,
width: _glowSize,
height: _glowSize,
child: IgnorePointer(
child: Container(
key: const Key('auth-background-glow'),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
AppColors.blue.withValues(alpha: _glowOpacity),
AppColors.blue.withValues(alpha: 0.05),
AppColors.ink.withValues(alpha: 0),
AppColors.surfaceTop,
AppColors.surfaceTop.withValues(alpha: 0),
],
stops: const [0, 0.55, 1],
),
),
),
),
),
Positioned(
top: 72,
right: -80,
width: _warmGlowSize,
height: _warmGlowSize,
left: -110,
bottom: -90,
width: _glowSize,
height: _glowSize,
child: IgnorePointer(
child: Container(
key: const Key('auth-background-warm-glow'),
@@ -239,23 +285,16 @@ class _AuthPageState extends State<AuthPage> {
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(left: 4, top: 4),
child: IconButton(
key: const Key('auth-back'),
tooltip: MaterialLocalizations.of(
context,
).backButtonTooltip,
onPressed: () {
padding: const EdgeInsets.only(left: 14, top: 8),
child: AppSubpageBackButton(
backKey: const Key('auth-back'),
onBack: () {
if (_emailEntryOpen) {
setState(() => _emailEntryOpen = false);
} else {
Navigator.of(context).pop();
}
},
icon: const Icon(
Icons.arrow_back_rounded,
color: AppColors.onPrimary,
),
),
),
),
@@ -298,58 +337,44 @@ class _AuthPageState extends State<AuthPage> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
child: _brandMarkWidget(
key: const Key('auth-brand-logo'),
width: 64,
height: 64,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.blue, AppColors.secondary],
),
),
child: const Icon(
Icons.play_arrow_rounded,
color: AppColors.onPrimary,
size: 32,
),
size: _brandMarkSize,
radius: _brandMarkRadius,
iconSize: _brandIconSize,
),
),
const SizedBox(height: 14),
const SizedBox(height: 16),
Text(
l10n.fruitShortsBrand,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.onSurface,
fontSize: 20,
fontWeight: FontWeight.w800,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 6),
Text(
l10n.authLandingSubtitle,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.subtle, fontSize: 11),
style: const TextStyle(color: AppColors.muted, fontSize: 13),
),
const SizedBox(height: 22),
const SizedBox(height: 28),
_AuthEntryButton(
key: const Key('auth-entry-phone'),
label: l10n.authPhoneSignIn,
icon: Icons.phone_android_rounded,
background: AppColors.input,
foreground: AppColors.onSurface,
primary: true,
onTap: _openPhoneSheet,
),
const SizedBox(height: 11),
const SizedBox(height: 12),
_AuthEntryButton(
key: const Key('auth-entry-email'),
label: l10n.authEmailSignIn,
icon: Icons.mail_outline_rounded,
background: AppColors.input,
foreground: AppColors.onSurface,
primary: false,
onTap: () => setState(() => _emailEntryOpen = true),
),
const SizedBox(height: 18),
@@ -417,33 +442,40 @@ class _AuthEntryButton extends StatelessWidget {
const _AuthEntryButton({
super.key,
required this.label,
required this.background,
required this.foreground,
required this.primary,
required this.onTap,
this.icon,
});
final String label;
final Color background;
final Color foreground;
final bool primary;
final VoidCallback onTap;
final IconData? icon;
@override
Widget build(BuildContext context) {
final background = primary ? AppColors.blue : AppColors.card;
final foreground = primary ? AppColors.onPrimary : AppColors.onSurface;
return Material(
color: background,
borderRadius: BorderRadius.circular(9),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_entryButtonRadius),
side: primary
? BorderSide.none
: const BorderSide(color: AppColors.border),
),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(_entryButtonRadius),
child: SizedBox(
height: 44,
height: _entryButtonHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) Icon(icon, size: 16, color: foreground),
if (icon != null) const SizedBox(width: 7),
if (icon != null)
Icon(icon, size: _entryIconSize, color: foreground),
if (icon != null) const SizedBox(width: 8),
Flexible(
child: Text(
label,
@@ -451,8 +483,8 @@ class _AuthEntryButton extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: foreground,
fontSize: 13,
fontWeight: FontWeight.w600,
fontSize: _entryButtonFontSize,
fontWeight: FontWeight.w800,
),
),
),