feat(ui): 登录页重设计并统一弹框主题渐变外壳
This commit is contained in:
1 parent
c027acc91f
commit
de7da64886
25 files changed
+905
-317
No files matched your search
@@ -24,18 +24,14 @@ part 'auth_widgets.dart';
|
||||
part 'auth_form_widgets.dart';
|
||||
|
||||
const double _minTapTarget = 44;
|
||||
const double _brandMarkSize = 72;
|
||||
const double _brandMarkSize = 88;
|
||||
const double _compactBrandMarkSize = 48;
|
||||
const double _brandMarkRadius = 20;
|
||||
const double _brandMarkRadius = 26;
|
||||
const double _compactBrandMarkRadius = 14;
|
||||
const double _landingLogoTopGap = 28;
|
||||
const String _appLogoAsset = 'assets/icon/fruitshorts.png';
|
||||
const double _contentMaxWidth = 440;
|
||||
// Auth keeps its approved 20px card silhouette.
|
||||
const double _cardRadius = AppDimens.authCardRadius;
|
||||
const double _cardPadding = 20;
|
||||
const double _fieldHeight = 52;
|
||||
const double _fieldRadius = 14;
|
||||
const double _fieldRadius = 12;
|
||||
const double _fieldBorderWidth = 1;
|
||||
const double _buttonHeight = 50;
|
||||
const double _buttonRadius = 25;
|
||||
@@ -47,14 +43,11 @@ const double _bottomContentPadding = 28;
|
||||
const double _compactTopContentPadding = 40;
|
||||
const double _compactHeightBreakpoint = 720;
|
||||
const double _compactBrandGap = 16;
|
||||
const double _glowSize = 280;
|
||||
const double _warmGlowOpacity = 0.18;
|
||||
const double _entryButtonHeight = 50;
|
||||
const double _entryButtonRadius = 25;
|
||||
const double _entryButtonHeight = 52;
|
||||
const double _entryButtonRadius = 26;
|
||||
const double _entryButtonFontSize = 15;
|
||||
const double _entryIconSize = 20;
|
||||
const Duration _enterDuration = Duration(milliseconds: 220);
|
||||
const Duration _segmentDuration = Duration(milliseconds: 160);
|
||||
|
||||
Widget _brandMarkWidget({
|
||||
Key? key,
|
||||
@@ -267,45 +260,53 @@ class _AuthPageState extends State<AuthPage> {
|
||||
child: IgnorePointer(
|
||||
child: DecoratedBox(
|
||||
key: Key('auth-page-background'),
|
||||
decoration: BoxDecoration(gradient: AppTheme.pageGradient),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: -90,
|
||||
right: -70,
|
||||
width: _glowSize,
|
||||
height: _glowSize,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
key: const Key('auth-background-glow'),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.surfaceTop,
|
||||
AppColors.surfaceTop.withValues(alpha: 0),
|
||||
AppColors.followingGlow,
|
||||
AppColors.followingGlowMid,
|
||||
AppColors.appBackground,
|
||||
],
|
||||
stops: [0, 0.14, 0.3],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -110,
|
||||
bottom: -90,
|
||||
width: _glowSize,
|
||||
height: _glowSize,
|
||||
// 全屏海报背景:压暗保证文字可读,顶部再叠品牌红光。
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(child: _AuthPosterBackdrop()),
|
||||
),
|
||||
const Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
key: const Key('auth-background-warm-glow'),
|
||||
child: DecoratedBox(
|
||||
key: Key('auth-poster-scrim'),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.gold.withValues(alpha: _warmGlowOpacity),
|
||||
AppColors.gold.withValues(alpha: 0),
|
||||
Color(0x66000000),
|
||||
Color(0x14000000),
|
||||
Color(0xE0000000),
|
||||
],
|
||||
stops: [0, 0.42, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: DecoratedBox(
|
||||
key: Key('auth-red-glow'),
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: Alignment(0, -1.15),
|
||||
radius: 1.3,
|
||||
colors: [AppColors.primaryBorder, AppColors.transparent],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -347,7 +348,11 @@ class _AuthPageState extends State<AuthPage> {
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: _contentMaxWidth,
|
||||
),
|
||||
child: _content(l10n, compact: compact),
|
||||
child: _content(
|
||||
l10n,
|
||||
compact: compact,
|
||||
minHeight: minHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -387,7 +392,11 @@ class _AuthPageState extends State<AuthPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _content(AppLocalizations l10n, {required bool compact}) =>
|
||||
Widget _content(
|
||||
AppLocalizations l10n, {
|
||||
required bool compact,
|
||||
required double minHeight,
|
||||
}) =>
|
||||
TweenAnimationBuilder<double>(
|
||||
key: const Key('auth-enter-animation'),
|
||||
tween: Tween(begin: 0, end: 1),
|
||||
@@ -409,15 +418,18 @@ class _AuthPageState extends State<AuthPage> {
|
||||
_authCard(l10n),
|
||||
],
|
||||
)
|
||||
: _landing(l10n),
|
||||
: _landing(l10n, minHeight: minHeight),
|
||||
);
|
||||
|
||||
Widget _landing(AppLocalizations l10n) => Column(
|
||||
/// 落地页内容需要有限高度才能用 Spacer 三段分割,这里显式钉住可用高度。
|
||||
Widget _landing(AppLocalizations l10n, {required double minHeight}) =>
|
||||
SizedBox(
|
||||
height: minHeight,
|
||||
child: Column(
|
||||
key: const Key('auth-landing'),
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: _landingLogoTopGap),
|
||||
const Spacer(flex: 5),
|
||||
Center(
|
||||
child: _brandMarkWidget(
|
||||
key: const Key('auth-brand-logo'),
|
||||
@@ -425,24 +437,19 @@ class _AuthPageState extends State<AuthPage> {
|
||||
radius: _brandMarkRadius,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
l10n.fruitShortsBrand,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Outfit',
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
l10n.authLandingSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.muted, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
const Spacer(flex: 3),
|
||||
_AuthEntryButton(
|
||||
key: const Key('auth-entry-phone'),
|
||||
label: l10n.authPhoneSignIn,
|
||||
@@ -458,10 +465,12 @@ class _AuthPageState extends State<AuthPage> {
|
||||
primary: false,
|
||||
onTap: () => setState(() => _emailEntryOpen = true),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const AuthLegalLinks(),
|
||||
],
|
||||
);
|
||||
const SizedBox(height: 14),
|
||||
const Opacity(opacity: 0.75, child: AuthLegalLinks()),
|
||||
const Spacer(flex: 6),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _openPhoneSheet() async {
|
||||
final signedIn = await showPhoneAuthSheet(
|
||||
@@ -526,15 +535,15 @@ class _AuthEntryButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = primary ? AppColors.blue : AppColors.card;
|
||||
// 主按钮纯品牌红;次按钮为无边框深色玻璃(白 6%),Apple 风格。
|
||||
final background = primary
|
||||
? AppColors.brandRed
|
||||
: Colors.white.withValues(alpha: 0.06);
|
||||
final foreground = primary ? AppColors.onPrimary : AppColors.onSurface;
|
||||
return Material(
|
||||
color: background,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_entryButtonRadius),
|
||||
side: primary
|
||||
? BorderSide.none
|
||||
: const BorderSide(color: AppColors.border),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
@@ -556,7 +565,7 @@ class _AuthEntryButton extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: foreground,
|
||||
fontSize: _entryButtonFontSize,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -567,3 +576,18 @@ class _AuthEntryButton extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录页全屏海报背景(单张,无拼缝)。
|
||||
class _AuthPosterBackdrop extends StatelessWidget {
|
||||
const _AuthPosterBackdrop();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Image.asset(
|
||||
key: const Key('auth-poster-backdrop'),
|
||||
'assets/auth/bg_3.jpg',
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user