Skip to content

Commit dbc3b9b

Browse files
committed
Fix bug where escape key handler was causing focus issues on mobile.
1 parent 9cf4b9f commit dbc3b9b

File tree

2 files changed

+74
-47
lines changed

2 files changed

+74
-47
lines changed

gai-frontend/lib/chat/chat.dart

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'dart:math' as math;
44
// Conditionally import JS only when compiling for web
55
import 'package:flutter/services.dart';
66
import 'package:http/http.dart' as http;
7-
import 'package:flutter/foundation.dart' show kIsWeb;
7+
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform, TargetPlatform;
88
import 'package:orchid/api/orchid_eth/chains.dart';
99
import 'package:orchid/api/orchid_eth/orchid_account.dart';
1010
import 'package:orchid/api/orchid_eth/orchid_account_detail.dart';
@@ -43,6 +43,19 @@ class ChatView extends StatefulWidget {
4343
}
4444

4545
class _ChatViewState extends State<ChatView> {
46+
// Platform detection helper
47+
bool get _isMobilePlatform {
48+
// On web, check the target platform
49+
if (kIsWeb) {
50+
// Web on mobile browsers
51+
return defaultTargetPlatform == TargetPlatform.iOS ||
52+
defaultTargetPlatform == TargetPlatform.android;
53+
}
54+
// Native mobile apps
55+
return defaultTargetPlatform == TargetPlatform.iOS ||
56+
defaultTargetPlatform == TargetPlatform.android;
57+
}
58+
4659
// UI state
4760
bool _debugMode = false;
4861
bool _multiSelectMode = false;
@@ -108,12 +121,14 @@ class _ChatViewState extends State<ChatView> {
108121
// Initialize the scripting extension mechanism
109122
_initScripting();
110123

111-
// Initialize keyboard listener
112-
_initKeyboardListener();
113-
114-
// Register a browser-level event listener if we're running on the web
115-
if (kIsWeb) {
116-
_initBrowserKeyboardListeners();
124+
// Initialize keyboard listener only on non-mobile platforms
125+
if (!_isMobilePlatform) {
126+
_initKeyboardListener();
127+
128+
// Register a browser-level event listener if we're running on the web
129+
if (kIsWeb) {
130+
_initBrowserKeyboardListeners();
131+
}
117132
}
118133

119134
// Initialize state manager
@@ -133,21 +148,22 @@ class _ChatViewState extends State<ChatView> {
133148
void _initBrowserKeyboardListeners() {
134149
log('Setting up browser keyboard listener for web');
135150

136-
// Use a simple approach that doesn't rely on dart:js
137-
// Register a global key handler that listens for Escape
138-
RawKeyboard.instance.addListener(_handleRawKeyEvent);
151+
// Use HardwareKeyboard for listening to key events
152+
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
139153

140154
log('Browser keyboard listener initialized');
141155
}
142156

143-
void _handleRawKeyEvent(RawKeyEvent event) {
144-
if (event is RawKeyDownEvent) {
157+
bool _handleKeyEvent(KeyEvent event) {
158+
if (event is KeyDownEvent) {
145159
if (event.logicalKey == LogicalKeyboardKey.escape) {
146160
if (_isProcessingRequest) {
147161
_cancelOngoingRequests();
162+
return true; // Handled
148163
}
149164
}
150165
}
166+
return false; // Not handled
151167
}
152168

153169
void _initKeyboardListener() {
@@ -540,8 +556,8 @@ class _ChatViewState extends State<ChatView> {
540556

541557
log('Starting processing request - ESC to cancel');
542558

543-
// Ensure the app has focus for keyboard events
544-
if (_keyboardFocusNode.canRequestFocus) {
559+
// Ensure the app has focus for keyboard events (only on non-mobile platforms)
560+
if (!_isMobilePlatform && _keyboardFocusNode.canRequestFocus) {
545561
_keyboardFocusNode.requestFocus();
546562
}
547563

@@ -683,16 +699,13 @@ class _ChatViewState extends State<ChatView> {
683699
// Tell the provider manager we have active, cancellable requests
684700
_providerManager.setHasCancellableRequests(true);
685701

686-
// Ensure we have focus for keyboard events
687-
if (_keyboardFocusNode.canRequestFocus) {
702+
// Ensure we have focus for keyboard events (only on non-mobile platforms)
703+
if (!_isMobilePlatform && _keyboardFocusNode.canRequestFocus) {
688704
_keyboardFocusNode.requestFocus();
689705
}
690706

691707
List<ChatMessage> toolResultMessages = [];
692708
String? modelId;
693-
694-
// Store the source of this tool call invocation for debugging
695-
String callSource = "direct";
696709

697710
try {
698711
for (final toolCall in toolCalls) {
@@ -1402,9 +1415,9 @@ class _ChatViewState extends State<ChatView> {
14021415
_accountDetail?.cancel();
14031416
_keyboardFocusNode.dispose();
14041417

1405-
// Clean up global keyboard listener
1406-
if (kIsWeb) {
1407-
RawKeyboard.instance.removeListener(_handleRawKeyEvent);
1418+
// Clean up global keyboard listener (only if it was added on non-mobile platforms)
1419+
if (!_isMobilePlatform && kIsWeb) {
1420+
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
14081421
}
14091422

14101423
super.dispose();
@@ -1416,30 +1429,21 @@ class _ChatViewState extends State<ChatView> {
14161429
var showIcons = AppSize(context).narrowerThanWidth(700);
14171430
var showMinWidth = AppSize(context).narrowerThanWidth(minWidth);
14181431

1419-
// Request focus for keyboard detection on first build
1420-
WidgetsBinding.instance.addPostFrameCallback((_) {
1421-
if (_keyboardFocusNode.canRequestFocus) {
1422-
_keyboardFocusNode.requestFocus();
1423-
}
1424-
});
1432+
// Request focus for keyboard detection on first build (only on non-mobile platforms)
1433+
if (!_isMobilePlatform) {
1434+
WidgetsBinding.instance.addPostFrameCallback((_) {
1435+
if (_keyboardFocusNode.canRequestFocus) {
1436+
_keyboardFocusNode.requestFocus();
1437+
}
1438+
});
1439+
}
14251440

14261441
// Use a global key so we can access this widget from anywhere
14271442
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
14281443

14291444
// Listen for key presses at the document level (more reliable in web)
1430-
return Focus(
1431-
autofocus: true,
1432-
onKeyEvent: (FocusNode node, KeyEvent event) {
1433-
// Check for escape key
1434-
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) {
1435-
if (_isProcessingRequest) {
1436-
_cancelOngoingRequests();
1437-
return KeyEventResult.handled;
1438-
}
1439-
}
1440-
return KeyEventResult.ignored;
1441-
},
1442-
child: Scaffold(
1445+
// Only wrap with Focus on desktop to avoid interfering with mobile text input
1446+
Widget scaffoldWidget = Scaffold(
14431447
key: scaffoldKey,
14441448
body: SafeArea(
14451449
child: Stack(
@@ -1481,7 +1485,7 @@ class _ChatViewState extends State<ChatView> {
14811485
// Processing indicator
14821486
if (_isProcessingRequest)
14831487
GestureDetector(
1484-
onTap: _cancelOngoingRequests, // Allow cancelling by tap as well
1488+
onTap: !_isMobilePlatform ? _cancelOngoingRequests : null, // Disable tap cancellation on mobile
14851489
child: Padding(
14861490
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
14871491
child: Row(
@@ -1498,10 +1502,14 @@ class _ChatViewState extends State<ChatView> {
14981502
),
14991503
),
15001504
const SizedBox(width: 8),
1501-
Text('Processing... Press ESC or tap here to cancel',
1505+
Text(_isMobilePlatform
1506+
? 'Processing...'
1507+
: 'Processing... Press ESC or tap here to cancel',
15021508
style: OrchidText.caption.copyWith(
15031509
color: Colors.white70,
1504-
decoration: TextDecoration.underline,
1510+
decoration: _isMobilePlatform
1511+
? TextDecoration.none
1512+
: TextDecoration.underline,
15051513
),
15061514
),
15071515
],
@@ -1515,8 +1523,27 @@ class _ChatViewState extends State<ChatView> {
15151523
],
15161524
),
15171525
),
1518-
),
1519-
);
1526+
);
1527+
1528+
// Only wrap with Focus on desktop platforms to avoid interfering with mobile text input
1529+
if (!_isMobilePlatform) {
1530+
return Focus(
1531+
autofocus: true,
1532+
onKeyEvent: (FocusNode node, KeyEvent event) {
1533+
// Check for escape key
1534+
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) {
1535+
if (_isProcessingRequest) {
1536+
_cancelOngoingRequests();
1537+
return KeyEventResult.handled;
1538+
}
1539+
}
1540+
return KeyEventResult.ignored;
1541+
},
1542+
child: scaffoldWidget,
1543+
);
1544+
} else {
1545+
return scaffoldWidget;
1546+
}
15201547
}
15211548

15221549
Widget _buildChatPane() {

gai-frontend/lib/chat/chat_prompt.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class _ChatPromptPanelState extends State<ChatPromptPanel> {
4646
child: OrchidTextField(
4747
controller: widget.promptTextController,
4848
hintText: 'Enter a prompt',
49-
contentPadding: EdgeInsets.only(bottom: 26, left: 16),
49+
contentPadding: const EdgeInsets.only(bottom: 26, left: 16),
5050
style: OrchidText.body1,
5151
autoFocus: true,
5252
onSubmitted: (String s) {

0 commit comments

Comments
 (0)