fix(auth): 登录注册与手机号登录键盘避让

This commit is contained in:
Zhou Weidong committed 2026-10-21 19:26:46 +08:00
1 parent 86588d7faa
commit 6c2f77926b
6 files changed
+332 -9

No files matched your search

+76 -1
View File
@@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/platform/keyboard_inset.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
@@ -101,6 +102,9 @@ class _AuthPageState extends State<AuthPage> {
late final TextEditingController _passwordController;
late final FocusNode _emailFocus;
late final FocusNode _passwordFocus;
late final ScrollController _scrollController;
final _keyboardInset = KeyboardInsetNotifier.instance;
double _lastKeyboardInset = 0;
bool _obscurePassword = true;
bool _emailEntryOpen = false;
@@ -112,17 +116,87 @@ class _AuthPageState extends State<AuthPage> {
_passwordController = TextEditingController();
_emailFocus = FocusNode(debugLabel: 'auth-email-focus');
_passwordFocus = FocusNode(debugLabel: 'auth-password-focus');
_scrollController = ScrollController();
_emailFocus.addListener(_onFocusChanged);
_passwordFocus.addListener(_onFocusChanged);
_keyboardInset.addListener(_onKeyboardInsetChanged);
unawaited(_keyboardInset.start());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final inset = effectiveKeyboardInset(context, _keyboardInset);
if ((inset - _lastKeyboardInset).abs() >= 1) {
_lastKeyboardInset = inset;
_scheduleRevealFocusedField();
}
}
@override
void dispose() {
_keyboardInset.removeListener(_onKeyboardInsetChanged);
_emailFocus.removeListener(_onFocusChanged);
_passwordFocus.removeListener(_onFocusChanged);
_emailController.dispose();
_passwordController.dispose();
_emailFocus.dispose();
_passwordFocus.dispose();
_scrollController.dispose();
super.dispose();
}
void _onKeyboardInsetChanged() {
if (!mounted) return;
setState(() {});
_scheduleRevealFocusedField();
}
void _onFocusChanged() {
if (_emailFocus.hasFocus || _passwordFocus.hasFocus) {
_scheduleRevealFocusedField();
}
}
void _scheduleRevealFocusedField() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _revealFocusedField();
});
}
void _revealFocusedField() {
if (!_scrollController.hasClients) return;
final focus = _passwordFocus.hasFocus
? _passwordFocus
: _emailFocus.hasFocus
? _emailFocus
: null;
final fieldContext = focus?.context;
final renderObject = fieldContext?.findRenderObject();
if (renderObject is! RenderBox || !renderObject.hasSize) return;
final keyboardInset = effectiveKeyboardInset(context, _keyboardInset);
if (keyboardInset <= 0) return;
final fieldBottom = renderObject
.localToGlobal(Offset(0, renderObject.size.height))
.dy;
final visibleBottom =
MediaQuery.sizeOf(context).height - keyboardInset - 24;
final overlap = fieldBottom - visibleBottom;
if (overlap <= 0) return;
final position = _scrollController.position;
final target = (position.pixels + overlap)
.clamp(position.minScrollExtent, position.maxScrollExtent)
.toDouble();
if (target <= position.pixels + 0.5) return;
unawaited(
_scrollController.animateTo(
target,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
),
);
}
String _errorText(AppLocalizations l10n, AuthFieldError error) {
switch (error) {
case AuthFieldError.emailRequired:
@@ -246,12 +320,13 @@ class _AuthPageState extends State<AuthPage> {
: _topContentPadding;
final bottom =
_bottomContentPadding +
MediaQuery.viewInsetsOf(context).bottom;
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(