fix(auth): 登录页靠上排列并支持外部失焦

This commit is contained in:
Zhou Weidong committed 2026-10-23 01:10:59 +08:00
1 parent 6318ca24ec
commit f3d64f6fb5
2 files changed
+44 -22

No files matched your search

+40 -22
View File
@@ -40,9 +40,9 @@ const double _buttonRadius = 25;
const double _errorOpacity = 0.10; const double _errorOpacity = 0.10;
const double _errorBorderOpacity = 0.28; const double _errorBorderOpacity = 0.28;
const double _enterSlideDistance = 16; const double _enterSlideDistance = 16;
const double _topContentPadding = 64; const double _topContentPadding = 56;
const double _bottomContentPadding = 28; const double _bottomContentPadding = 28;
const double _compactTopContentPadding = 48; const double _compactTopContentPadding = 40;
const double _compactHeightBreakpoint = 720; const double _compactHeightBreakpoint = 720;
const double _compactBrandGap = 16; const double _compactBrandGap = 16;
const double _glowSize = 280; const double _glowSize = 280;
@@ -159,6 +159,19 @@ class _AuthPageState extends State<AuthPage> {
} }
} }
/// 点在当前邮箱或密码框之外时取消焦点。
void _releaseFieldFocus(PointerDownEvent event) {
final focus = FocusManager.instance.primaryFocus;
if (focus != _emailFocus && focus != _passwordFocus) return;
final renderObject = focus?.context?.findRenderObject();
if (renderObject is! RenderBox || !renderObject.hasSize) {
focus?.unfocus();
return;
}
final bounds = renderObject.localToGlobal(Offset.zero) & renderObject.size;
if (!bounds.contains(event.position)) focus?.unfocus();
}
void _scheduleRevealFocusedField() { void _scheduleRevealFocusedField() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _revealFocusedField(); if (mounted) _revealFocusedField();
@@ -325,26 +338,31 @@ class _AuthPageState extends State<AuthPage> {
effectiveKeyboardInset(context, _keyboardInset); effectiveKeyboardInset(context, _keyboardInset);
final available = constraints.maxHeight - top - bottom; final available = constraints.maxHeight - top - bottom;
final minHeight = available > 0 ? available : 0.0; final minHeight = available > 0 ? available : 0.0;
return AutofillGroup( return Listener(
child: SingleChildScrollView( behavior: HitTestBehavior.translucent,
key: const Key('auth-scroll'), onPointerDown: _releaseFieldFocus,
controller: _scrollController, child: AutofillGroup(
keyboardDismissBehavior: child: SingleChildScrollView(
ScrollViewKeyboardDismissBehavior.onDrag, key: const Key('auth-scroll'),
padding: EdgeInsets.fromLTRB( controller: _scrollController,
AppDimens.pageHorizontal, keyboardDismissBehavior:
top, ScrollViewKeyboardDismissBehavior.onDrag,
AppDimens.pageHorizontal, padding: EdgeInsets.fromLTRB(
bottom, AppDimens.pageHorizontal,
), top,
child: ConstrainedBox( AppDimens.pageHorizontal,
constraints: BoxConstraints(minHeight: minHeight), bottom,
child: Center( ),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints( constraints: BoxConstraints(minHeight: minHeight),
maxWidth: _contentMaxWidth, child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: _contentMaxWidth,
),
child: _content(l10n, compact: compact),
), ),
child: _content(l10n, compact: compact),
), ),
), ),
), ),
@@ -410,7 +428,7 @@ class _AuthPageState extends State<AuthPage> {
Widget _landing(AppLocalizations l10n) => Column( Widget _landing(AppLocalizations l10n) => Column(
key: const Key('auth-landing'), key: const Key('auth-landing'),
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Center( Center(
+4
View File
@@ -52,6 +52,10 @@ void main() {
expect(find.byKey(const Key('auth-entry-phone')), findsOneWidget); expect(find.byKey(const Key('auth-entry-phone')), findsOneWidget);
expect(find.byKey(const Key('auth-entry-email')), findsOneWidget); expect(find.byKey(const Key('auth-entry-email')), findsOneWidget);
expect(find.byKey(const Key('auth-legal')), findsOneWidget); expect(find.byKey(const Key('auth-legal')), findsOneWidget);
expect(
tester.getTopLeft(find.byKey(const Key('auth-landing'))).dy,
lessThan(80),
);
await tester.tap(find.byKey(const Key('auth-entry-phone'))); await tester.tap(find.byKey(const Key('auth-entry-phone')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();