diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index 7baafae..c90fcf7 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -1326,20 +1326,27 @@ class _ConnectFormState extends State<_ConnectForm> { @override Widget build(BuildContext context) { final l10n = AppL10n.of(context); - // Wrap the form in a GestureDetector that unfocuses any focused - // TextField when the user taps an empty area of the form. This is - // the second half of the flutter/flutter#181474 workaround: - // _kickFocus handles the case where the user re-taps the same - // field after the keyboard auto-dismissed, and this outer - // gesture handler handles the more common case where the user - // taps outside (e.g. on the column padding) to dismiss the - // keyboard. By forcing an unfocus on tap-outside we guarantee - // the FocusNode is in the unfocused state when the next field - // tap arrives, so the focus transition is always false\u2192true - // (which is what iOS needs to re-open TextInput on the very - // first tap). + // Tap-outside-to-unfocus. + // + // Initial implementation used HitTestBehavior.translucent which + // dispatched the tap to BOTH this GestureDetector and any + // descendant TextField. That meant every field tap fired + // FocusScope.unfocus() at the same time the TextField's own + // gesture path was attaching TextInput \u2014 the two animations + // raced each other, producing the user-visible 500\u20131000 ms lag + + // 'stuck' keyboard behaviour reported on iPhone iOS 18. + // + // Fixed by switching to HitTestBehavior.opaque. With opaque, the + // gesture arena still routes a tap on a TextField to that + // TextField's own recognizer (only one recognizer wins), so the + // outer onTap does NOT fire when the user taps a field. The + // outer onTap only fires when the user taps somewhere this + // GestureDetector receives that no child claimed \u2014 i.e. the + // empty padding between fields, the spacers, or below the + // bottom button row. That's exactly the tap-outside semantic we + // want, with zero interference on field taps. return GestureDetector( - behavior: HitTestBehavior.translucent, + behavior: HitTestBehavior.opaque, onTap: () => FocusScope.of(context).unfocus(), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch,