Skip to content

Commit 0522d97

Browse files
committed
android fix bottom sheet blocking ui
1 parent 464d652 commit 0522d97

File tree

18 files changed

+344
-402
lines changed

18 files changed

+344
-402
lines changed

apps/mobile/app.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
},
3030
"android": {
3131
"edgeToEdgeEnabled": true,
32+
"softwareKeyboardLayoutMode": "pan",
3233
"adaptiveIcon": {
3334
"foregroundImage": "./assets/images/adaptive-icon.png",
3435
"backgroundColor": "#ffffff"

apps/mobile/app/home.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useQueryClient } from '@tanstack/react-query';
1111
import type { Agent } from '@/api/types';
1212
import type { Conversation } from '@/components/menu/types';
1313
import { FeedbackDrawer } from '@/components/chat/tool-views/complete-tool/FeedbackDrawer';
14+
import { useFeedbackDrawerStore } from '@/stores/feedback-drawer-store';
1415

1516
export default function AppScreen() {
1617
const { colorScheme } = useColorScheme();
@@ -20,6 +21,7 @@ export default function AppScreen() {
2021
const { threadId } = useLocalSearchParams<{ threadId?: string }>();
2122
const chat = useChat();
2223
const pageNav = usePageNavigation();
24+
const { isOpen: isFeedbackDrawerOpen } = useFeedbackDrawerStore();
2325
const homePageRef = React.useRef<HomePageRef>(null);
2426

2527
// Worker config drawer state for MenuPage
@@ -175,7 +177,7 @@ export default function AppScreen() {
175177
/>
176178
)}
177179
</Drawer>
178-
<FeedbackDrawer />
180+
{isFeedbackDrawerOpen && <FeedbackDrawer />}
179181
</>
180182
);
181183
}

apps/mobile/components/agents/AgentDrawer.tsx

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
} from 'lucide-react-native';
3737
import { useColorScheme } from 'nativewind';
3838
import * as React from 'react';
39-
import { Pressable, View, ScrollView, Keyboard, Alert } from 'react-native';
39+
import { Pressable, View, ScrollView, Keyboard, Alert, TouchableOpacity, Platform } from 'react-native';
4040
import Animated, {
4141
useAnimatedStyle,
4242
withTiming,
@@ -428,29 +428,32 @@ export function AgentDrawer({
428428

429429
{advancedFeaturesEnabled && (
430430
<>
431-
<AnimatedPressable
432-
style={[
433-
integrationsAnimatedStyle,
434-
{
435-
borderColor: hasFreeTier
436-
? colorScheme === 'dark'
437-
? '#22c55e'
438-
: '#16a34a'
439-
: colorScheme === 'dark'
440-
? '#454444'
441-
: '#c2c2c2',
442-
borderWidth: hasFreeTier ? 1.5 : 1,
443-
backgroundColor: hasFreeTier
444-
? colorScheme === 'dark'
445-
? 'rgba(34, 197, 94, 0.1)'
446-
: 'rgba(22, 163, 74, 0.1)'
447-
: 'transparent',
448-
},
449-
]}
450-
className="mt-4 h-16 flex-1 flex-row items-center justify-center gap-2 rounded-2xl"
431+
<TouchableOpacity
432+
style={{
433+
marginTop: 16,
434+
height: 64,
435+
flex: 1,
436+
flexDirection: 'row',
437+
alignItems: 'center',
438+
justifyContent: 'center',
439+
gap: 8,
440+
borderRadius: 16,
441+
borderColor: hasFreeTier
442+
? colorScheme === 'dark'
443+
? '#22c55e'
444+
: '#16a34a'
445+
: colorScheme === 'dark'
446+
? '#454444'
447+
: '#c2c2c2',
448+
borderWidth: hasFreeTier ? 1.5 : 1,
449+
backgroundColor: hasFreeTier
450+
? colorScheme === 'dark'
451+
? 'rgba(34, 197, 94, 0.1)'
452+
: 'rgba(22, 163, 74, 0.1)'
453+
: 'transparent',
454+
}}
451455
onPress={handleIntegrationsPress}
452-
onPressIn={handleIntegrationsPressIn}
453-
onPressOut={handleIntegrationsPressOut}>
456+
activeOpacity={0.7}>
454457
{hasFreeTier ? (
455458
<Lock size={18} color={colorScheme === 'dark' ? '#22c55e' : '#16a34a'} />
456459
) : (
@@ -469,7 +472,7 @@ export function AgentDrawer({
469472
}}>
470473
{t('integrations.connectApps')}
471474
</Text>
472-
</AnimatedPressable>
475+
</TouchableOpacity>
473476
<View
474477
style={{ backgroundColor: colorScheme === 'dark' ? '#232324' : '#e0e0e0' }}
475478
className="my-3 h-px w-full"
@@ -669,6 +672,10 @@ export function AgentDrawer({
669672
width: 36,
670673
height: 5,
671674
borderRadius: 3,
675+
}}
676+
style={{
677+
zIndex: 9999,
678+
elevation: Platform.OS === 'android' ? 9999 : undefined,
672679
}}>
673680
{/* Use BottomSheetFlatList directly for composio, composio-detail, and composio-connector views */}
674681
{['composio', 'composio-detail', 'composio-connector'].includes(currentView) ? (

apps/mobile/components/agents/AgentSelector.tsx

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Text } from '@/components/ui/text';
22
import { Icon } from '@/components/ui/icon';
33
import { ChevronDown } from 'lucide-react-native';
44
import * as React from 'react';
5-
import { Pressable, View } from 'react-native';
5+
import { Pressable, View, Platform, TouchableOpacity } from 'react-native';
66
import Animated, {
77
useAnimatedStyle,
88
useSharedValue,
@@ -13,8 +13,12 @@ import { useAgent } from '@/contexts/AgentContext';
1313
import { KortixLogo } from '@/components/ui/KortixLogo';
1414
import { useColorScheme } from 'nativewind';
1515

16+
// NOTE: AnimatedPressable blocks touches on Android - use TouchableOpacity instead
1617
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
1718

19+
// Android hit slop for better touch targets
20+
const ANDROID_HIT_SLOP = Platform.OS === 'android' ? { top: 10, bottom: 10, left: 10, right: 10 } : undefined;
21+
1822
interface AgentSelectorProps {
1923
onPress?: () => void;
2024
compact?: boolean;
@@ -42,16 +46,11 @@ export function AgentSelector({ onPress, compact = true }: AgentSelectorProps) {
4246

4347
if (!agent) {
4448
return (
45-
<AnimatedPressable
46-
onPressIn={() => {
47-
scale.value = withSpring(0.95, { damping: 15, stiffness: 400 });
48-
}}
49-
onPressOut={() => {
50-
scale.value = withSpring(1, { damping: 15, stiffness: 400 });
51-
}}
49+
<TouchableOpacity
5250
onPress={onPress}
53-
className="flex-row items-center gap-1.5 rounded-full px-3.5 py-2"
54-
style={animatedStyle}
51+
style={{ flexDirection: 'row', alignItems: 'center', gap: 6, borderRadius: 18, paddingHorizontal: 14, paddingVertical: 8 }}
52+
hitSlop={ANDROID_HIT_SLOP}
53+
activeOpacity={0.7}
5554
>
5655
<View className="w-6 h-6 bg-muted rounded-full items-center justify-center">
5756
<Text className="text-muted-foreground text-xs font-roobert-bold">?</Text>
@@ -63,22 +62,16 @@ export function AgentSelector({ onPress, compact = true }: AgentSelectorProps) {
6362
className="text-foreground/60"
6463
strokeWidth={2}
6564
/>
66-
</AnimatedPressable>
65+
</TouchableOpacity>
6766
);
6867
}
6968

7069
if (compact) {
7170
return (
72-
<AnimatedPressable
73-
onPressIn={() => {
74-
scale.value = withSpring(0.95, { damping: 15, stiffness: 400 });
75-
}}
76-
onPressOut={() => {
77-
scale.value = withSpring(1, { damping: 15, stiffness: 400 });
78-
}}
71+
<TouchableOpacity
7972
onPress={onPress}
80-
className="relative"
81-
style={animatedStyle}
73+
hitSlop={ANDROID_HIT_SLOP}
74+
activeOpacity={0.7}
8275
>
8376
<AgentAvatar agent={agent} size={26} />
8477
<View className="absolute -bottom-0.5 -right-0.5 rounded-full items-center justify-center" style={{ width: 13, height: 13 }}>
@@ -89,21 +82,16 @@ export function AgentSelector({ onPress, compact = true }: AgentSelectorProps) {
8982
strokeWidth={2.5}
9083
/>
9184
</View>
92-
</AnimatedPressable>
85+
</TouchableOpacity>
9386
);
9487
}
9588

9689
return (
97-
<AnimatedPressable
98-
onPressIn={() => {
99-
scale.value = withSpring(0.95, { damping: 15, stiffness: 400 });
100-
}}
101-
onPressOut={() => {
102-
scale.value = withSpring(1, { damping: 15, stiffness: 400 });
103-
}}
90+
<TouchableOpacity
10491
onPress={onPress}
105-
className="flex-row items-center gap-1.5 rounded-2xl px-3.5 py-2"
106-
style={animatedStyle}
92+
style={{ flexDirection: 'row', alignItems: 'center', gap: 6, borderRadius: 16, paddingHorizontal: 14, paddingVertical: 8 }}
93+
hitSlop={ANDROID_HIT_SLOP}
94+
activeOpacity={0.7}
10795
>
10896
<AgentAvatar agent={agent} size={19} />
10997
<Text className="text-foreground text-sm font-roobert-medium">{agent.name}</Text>
@@ -113,7 +101,7 @@ export function AgentSelector({ onPress, compact = true }: AgentSelectorProps) {
113101
className="text-foreground/60 pt-0.5"
114102
strokeWidth={2}
115103
/>
116-
</AnimatedPressable>
104+
</TouchableOpacity>
117105
);
118106
}
119107

apps/mobile/components/chat/ChatInput.tsx

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AudioLines, CornerDownLeft, Paperclip, X, Loader2 } from 'lucide-react-
55
import { StopIcon } from '@/components/ui/StopIcon';
66
import { useColorScheme } from 'nativewind';
77
import * as React from 'react';
8-
import { Keyboard, Pressable, ScrollView, TextInput, View, ViewStyle, type ViewProps, type NativeSyntheticEvent, type TextInputContentSizeChangeEventData, type TextInputSelectionChangeEventData } from 'react-native';
8+
import { Keyboard, Pressable, ScrollView, TextInput, View, ViewStyle, Platform, TouchableOpacity, type ViewProps, type NativeSyntheticEvent, type TextInputContentSizeChangeEventData, type TextInputSelectionChangeEventData } from 'react-native';
99
import Animated, {
1010
useAnimatedStyle,
1111
useSharedValue,
@@ -25,6 +25,15 @@ const AnimatedView = Animated.createAnimatedComponent(View);
2525
// Spring config - defined once outside component
2626
const SPRING_CONFIG = { damping: 15, stiffness: 400 };
2727

28+
// Android hit slop for better touch targets
29+
const ANDROID_HIT_SLOP = Platform.OS === 'android' ? { top: 10, bottom: 10, left: 10, right: 10 } : undefined;
30+
31+
// #region agent log
32+
const debugLog = (location: string, message: string, data: any, hypothesisId: string) => {
33+
fetch('http://127.0.0.1:7242/ingest/75c4e084-f8e3-4454-ac60-13feff134172',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location,message,data,timestamp:Date.now(),sessionId:'debug-session',hypothesisId})}).catch(()=>{});
34+
};
35+
// #endregion
36+
2837
export interface ChatInputRef {
2938
focus: () => void;
3039
}
@@ -375,10 +384,11 @@ export const ChatInput = React.memo(React.forwardRef<ChatInputRef, ChatInputProp
375384
<View
376385
className="relative rounded-[30px] overflow-hidden bg-card border border-border"
377386
style={containerStyle}
387+
collapsable={false}
378388
{...props}
379389
>
380390
<View className="absolute inset-0" />
381-
<View className="p-4 flex-1">
391+
<View className="p-4 flex-1" collapsable={false}>
382392
{isRecording ? (
383393
<RecordingMode
384394
audioLevels={audioLevels}
@@ -470,6 +480,7 @@ const RecordingMode = React.memo(({
470480
onPress={onCancelRecording}
471481
className="bg-primary/5 rounded-full items-center justify-center"
472482
style={[{ width: 40, height: 40 }, cancelAnimatedStyle]}
483+
hitSlop={ANDROID_HIT_SLOP}
473484
>
474485
<Icon as={X} size={16} className="text-foreground" strokeWidth={2} />
475486
</AnimatedPressable>
@@ -479,6 +490,7 @@ const RecordingMode = React.memo(({
479490
onPress={onSendAudio}
480491
className="bg-primary rounded-full items-center justify-center"
481492
style={[{ width: 40, height: 40 }, stopAnimatedStyle]}
493+
hitSlop={ANDROID_HIT_SLOP}
482494
>
483495
<Icon as={CornerDownLeft} size={16} className="text-primary-foreground" strokeWidth={2} />
484496
</AnimatedPressable>
@@ -549,6 +561,7 @@ const NormalMode = React.memo(({
549561
<ScrollView
550562
showsVerticalScrollIndicator={false}
551563
keyboardShouldPersistTaps="handled"
564+
nestedScrollEnabled={true}
552565
>
553566
<TextInput
554567
ref={textInputRef}
@@ -567,28 +580,34 @@ const NormalMode = React.memo(({
567580
onContentSizeChange={handleContentSizeChange}
568581
className="text-foreground text-base"
569582
style={textInputStyle}
583+
textAlignVertical="top"
584+
underlineColorAndroid="transparent"
570585
/>
571586
</ScrollView>
572587
</View>
573588

574589
<View className="absolute bottom-4 left-4 right-4 flex-row items-center justify-between">
575590
<View className="flex-row items-center gap-2">
576-
<AnimatedPressable
577-
onPressIn={onAttachPressIn}
578-
onPressOut={onAttachPressOut}
591+
{/* Use TouchableOpacity on Android - AnimatedPressable blocks touches */}
592+
<TouchableOpacity
579593
onPress={() => {
594+
// #region agent log
595+
debugLog('ChatInput.tsx:attachPress', 'ATTACH BUTTON PRESSED', { isAuthenticated, isDisabled }, 'H8');
596+
// #endregion
580597
if (!isAuthenticated) {
581598
console.warn('⚠️ User not authenticated - cannot attach');
582599
return;
583600
}
584601
onAttachPress?.();
585602
}}
586603
disabled={isDisabled}
587-
className="border border-border rounded-[18px] w-10 h-10 items-center justify-center"
588-
style={attachButtonStyle}
604+
style={{ width: 40, height: 40, borderWidth: 1, borderRadius: 18, alignItems: 'center', justifyContent: 'center', opacity: isDisabled ? 0.4 : 1 }}
605+
className="border-border"
606+
hitSlop={ANDROID_HIT_SLOP}
607+
activeOpacity={0.7}
589608
>
590609
<Icon as={Paperclip} size={16} className="text-foreground" />
591-
</AnimatedPressable>
610+
</TouchableOpacity>
592611
</View>
593612

594613
<View className="flex-row items-center gap-2">
@@ -597,13 +616,19 @@ const NormalMode = React.memo(({
597616
compact={false}
598617
/>
599618

600-
<AnimatedPressable
601-
onPressIn={onSendPressIn}
602-
onPressOut={onSendPressOut}
603-
onPress={onButtonPress}
619+
{/* Use TouchableOpacity on Android - AnimatedPressable blocks touches */}
620+
<TouchableOpacity
621+
onPress={() => {
622+
// #region agent log
623+
debugLog('ChatInput.tsx:sendPress', 'SEND/MIC BUTTON PRESSED', { isSendingMessage, isTranscribing, isAgentRunning }, 'H8');
624+
// #endregion
625+
onButtonPress();
626+
}}
604627
disabled={isSendingMessage || isTranscribing}
605-
className={`rounded-[18px] items-center justify-center ${isAgentRunning ? 'bg-foreground' : 'bg-primary'}`}
606-
style={[{ width: 40, height: 40 }, sendAnimatedStyle]}
628+
style={{ width: 40, height: 40, borderRadius: 18, alignItems: 'center', justifyContent: 'center' }}
629+
className={isAgentRunning ? 'bg-foreground' : 'bg-primary'}
630+
hitSlop={ANDROID_HIT_SLOP}
631+
activeOpacity={0.7}
607632
>
608633
{isSendingMessage || isTranscribing ? (
609634
<AnimatedView style={rotationAnimatedStyle}>
@@ -616,7 +641,7 @@ const NormalMode = React.memo(({
616641
<Icon as={ButtonIcon as any} size={buttonIconSize} className={buttonIconClass} strokeWidth={2} />
617642
)
618643
)}
619-
</AnimatedPressable>
644+
</TouchableOpacity>
620645
</View>
621646
</View>
622647
</>

0 commit comments

Comments
 (0)