diff --git a/lib/pages/auth/auth_view.dart b/lib/pages/auth/auth_view.dart index 5cf37b5..f0906ce 100644 --- a/lib/pages/auth/auth_view.dart +++ b/lib/pages/auth/auth_view.dart @@ -40,9 +40,9 @@ const double _buttonRadius = 25; const double _errorOpacity = 0.10; const double _errorBorderOpacity = 0.28; const double _enterSlideDistance = 16; -const double _topContentPadding = 64; +const double _topContentPadding = 56; const double _bottomContentPadding = 28; -const double _compactTopContentPadding = 48; +const double _compactTopContentPadding = 40; const double _compactHeightBreakpoint = 720; const double _compactBrandGap = 16; const double _glowSize = 280; @@ -159,6 +159,19 @@ class _AuthPageState extends State { } } + /// 点在当前邮箱或密码框之外时取消焦点。 + 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() { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _revealFocusedField(); @@ -325,26 +338,31 @@ class _AuthPageState extends State { effectiveKeyboardInset(context, _keyboardInset); final available = constraints.maxHeight - top - bottom; final minHeight = available > 0 ? available : 0.0; - return AutofillGroup( - child: SingleChildScrollView( - key: const Key('auth-scroll'), - controller: _scrollController, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - padding: EdgeInsets.fromLTRB( - AppDimens.pageHorizontal, - top, - AppDimens.pageHorizontal, - bottom, - ), - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: minHeight), - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: _contentMaxWidth, + return Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: _releaseFieldFocus, + child: AutofillGroup( + child: SingleChildScrollView( + key: const Key('auth-scroll'), + controller: _scrollController, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + padding: EdgeInsets.fromLTRB( + AppDimens.pageHorizontal, + top, + AppDimens.pageHorizontal, + bottom, + ), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: minHeight), + 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 { Widget _landing(AppLocalizations l10n) => Column( key: const Key('auth-landing'), - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( diff --git a/test/auth/auth_landing_test.dart b/test/auth/auth_landing_test.dart index 63b66d8..aa884b9 100644 --- a/test/auth/auth_landing_test.dart +++ b/test/auth/auth_landing_test.dart @@ -52,6 +52,10 @@ void main() { expect(find.byKey(const Key('auth-entry-phone')), findsOneWidget); expect(find.byKey(const Key('auth-entry-email')), 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.pumpAndSettle();