fix(client): 修复登录页候选词点击后未填入输入框
点击候选词时 pointer-down 先触发 TextField 失焦,focus listener 立即移除 Overlay,导致 InkWell.onTap (pointer-up) 执行时 widget 已不存在。改为失焦后延迟 150ms 关闭下拉框,给 onTap 足够时间填值。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
_openDropdown(_hotelLayerLink, _hotelCodeHistory, _shopCodeCtrl,
|
_openDropdown(_hotelLayerLink, _hotelCodeHistory, _shopCodeCtrl,
|
||||||
isHotel: true);
|
isHotel: true);
|
||||||
} else if (!_hotelCodeFocus.hasFocus) {
|
} else if (!_hotelCodeFocus.hasFocus) {
|
||||||
_closeHotel();
|
// 延迟关闭:让候选词的 onTap(pointer-up)先执行,再关闭下拉框
|
||||||
|
Future.delayed(const Duration(milliseconds: 150), _closeHotel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_usernameFocus.addListener(() {
|
_usernameFocus.addListener(() {
|
||||||
@@ -53,7 +54,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
_openDropdown(_usernameLayerLink, _usernameHistory, _usernameCtrl,
|
_openDropdown(_usernameLayerLink, _usernameHistory, _usernameCtrl,
|
||||||
isHotel: false);
|
isHotel: false);
|
||||||
} else if (!_usernameFocus.hasFocus) {
|
} else if (!_usernameFocus.hasFocus) {
|
||||||
_closeUsername();
|
Future.delayed(const Duration(milliseconds: 150), _closeUsername);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,29 +118,10 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
itemBuilder: (_, i) => GestureDetector(
|
itemBuilder: (_, i) => _HistoryMenuItem(
|
||||||
behavior: HitTestBehavior.opaque,
|
value: items[i],
|
||||||
onTapDown: (_) {
|
onSelected: () =>
|
||||||
ctrl.text = items[i];
|
_selectHistoryItem(items[i], ctrl, isHotel: isHotel),
|
||||||
ctrl.selection = TextSelection.collapsed(
|
|
||||||
offset: items[i].length);
|
|
||||||
if (isHotel) { _closeHotel(); } else { _closeUsername(); }
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12, vertical: 10),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.history,
|
|
||||||
size: 14, color: AppTheme.textSecondary),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(items[i],
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppTheme.textPrimary)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -158,6 +140,27 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
setState(() {}); // refresh arrow icon
|
setState(() {}); // refresh arrow icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _selectHistoryItem(
|
||||||
|
String value,
|
||||||
|
TextEditingController ctrl, {
|
||||||
|
required bool isHotel,
|
||||||
|
}) {
|
||||||
|
ctrl.value = TextEditingValue(
|
||||||
|
text: value,
|
||||||
|
selection: TextSelection.collapsed(offset: value.length),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isHotel) {
|
||||||
|
_closeHotel();
|
||||||
|
_hotelCodeFocus.unfocus();
|
||||||
|
} else {
|
||||||
|
_closeUsername();
|
||||||
|
_usernameFocus.unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
void _closeHotel() {
|
void _closeHotel() {
|
||||||
_hotelEntry?.remove();
|
_hotelEntry?.remove();
|
||||||
_hotelEntry = null;
|
_hotelEntry = null;
|
||||||
@@ -472,3 +475,52 @@ class _BackgroundPainter extends CustomPainter {
|
|||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HistoryMenuItem extends StatefulWidget {
|
||||||
|
final String value;
|
||||||
|
final VoidCallback onSelected;
|
||||||
|
|
||||||
|
const _HistoryMenuItem({
|
||||||
|
required this.value,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HistoryMenuItem> createState() => _HistoryMenuItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryMenuItemState extends State<_HistoryMenuItem> {
|
||||||
|
bool _hovered = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _hovered = true),
|
||||||
|
onExit: (_) => setState(() => _hovered = false),
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: Material(
|
||||||
|
color: _hovered ? const Color(0xFFF0F4FF) : Colors.white,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: widget.onSelected,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.history,
|
||||||
|
size: 14, color: AppTheme.textSecondary),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
widget.value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppTheme.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user